使用Nimbus外观在JTable中横向滚动

5

我有一个JTable,它比它所包含的JScrollPane更宽(基本上是这样定义的):

JTable table = new JTable(model);
// I change some things like disallowing reordering, resizing,
// disable column selection, etc.

// I set the default renderer to a DefaultTableCellRenderer
// getTableCellRendererComponent, and then changes the color
// of the cell text depending on the cell value

JPanel panel = new JPanel(new BorderLayout(0, 5));
panel.add(new JScrollPane(table), BorderLayout.CENTER);
// add other stuff to the panel
this.add(panel,  BorderLayout.CENTER);

在我将外观从默认更改为Nimbus之前,我能够在JTable中左右滚动。 (我喜欢Mac LaF,但它在Windows上不受支持,而Windows LaF在我看来很丑陋)

我直接从Java教程中获取了以下代码:

try {
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            break;
        }
    }
} catch (Exception e) {
    // If Nimbus is not available, you can set the GUI to another look
    // and feel.
}

我重新编译并运行了代码,没有更改上面任何表格定义的内容,但现在无法在JTable中水平滚动了。
我找不到任何关于这种情况的信息。这是Nimbus的正常行为吗?还是可以更改?如果可以更改,怎么做?或者我应该尝试使用其他外观?
编辑:
我发现了两件事:
1. 我创建了一个新类扩展JTable来测试这个问题。我从JTable源代码中复制了getScrollableUnitIncrement的代码,并添加了打印语句。传递的方向似乎总是SwingConstants.VERTICAL,而在默认的外观(Mac Aqua或其他),水平和垂直滚动都有效。我不知道这是为什么。
2. 项目的另一部分也依赖于水平滚动。我在两个外观中进行了测试,在默认外观中工作正常,但Nimbus不允许我水平滚动。
这可能是Nimbus的一个错误吗?
无论如何,我想我会使用其他外观...
编辑#2:
我之前应该提到这一点。我能够使用窗口中的滚动条进行水平滚动,但无法使用触摸板或鼠标滚轮进行水平滚动。
2个回答

3
(注:在撰写此文后,我找到了解决方案,见本文附录。)
要重现这个问题,您需要使滚动条必需。这就是为什么有些人很难重现这个bug的原因。这意味着显而易见的解决方法是使您的水平滚动条变成可选的。(这并不总是切实可行的。)
只有当您将窗口宽度拖出到超过1200像素左右时,才会看到这个问题。在那之前,滚动条会正常工作。
而且这个问题只在Nimbus中出现。(它可能会出现在从SynthLookAndFeel创建的其他L&Fs中,但我还没有调查过。)
我发现虚假的滚动条拇指只在您不需要滚动时出现,所以这只是一个视觉错误。当您需要滚动时,滚动条拇指将出现,并且将正常工作,尽管它可能不是正确的大小。这可能是为什么它还没有被修复的原因。
以下是一个示例,您可以在其中比较不同的L&Fs。 在这个示例中,选择Nimbus,然后将宽度向内拖动,并观察滚动条大小的变化。当您的宽度大于背景图像时,虚假的滚动条将可见。一旦您变窄,合法的滚动条拇指将出现,但它会稍微有点小。随着您变得更小,滚动条拇指将保持恒定的大小,直到达到某个点(在视口宽度为1282像素时),然后它将开始缩小,就像应该做的那样。
对于任何其他L&F,只要您的宽度小于背景图像,一个几乎填满其空间的滑块就会出现。随着窗口变窄,它会变小,就像它应该做的那样。
(此操作还将揭示Nimbus绘制比其他任何L&F都慢得多。)
通过使图标变小,可以观察到不同但仍然不正确的行为。尝试800 x 450。当视口宽度>1035时,虚假的滚动条将出现。(视口大小显示在窗口底部。)
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
/**
 * NimbusScrollBug
 * <p/>
 * @author Miguel Muñoz
 */
public class NimbusScrollBug extends JPanel {
  private static final long serialVersionUID = -4235866781219951631L;
  private static JFrame frame;
  private static boolean firstTime = true;
  private static Point location;
  private static final UIManager.LookAndFeelInfo[] INFOS
          = UIManager.getInstalledLookAndFeels();

  private final JLabel viewPortLabel = new JLabel();

  public static void main(final String[] args) {
    makeMainFrame(new NimbusScrollBug(), "System");
  }

  public static void makeMainFrame(final NimbusScrollBug mainPanel,
                                   final String name) {
    if (firstTime) {
      installLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    frame = new JFrame(name);
    final Container contentPane = frame.getContentPane();
    contentPane.setLayout(new BorderLayout());
    contentPane.add(mainPanel, BorderLayout.CENTER);
    contentPane.add(makeButtonPane(mainPanel), BorderLayout.LINE_START);
    frame.setLocationByPlatform(firstTime);
    frame.pack();
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.setVisible(true);
    if (firstTime) {
      location = frame.getLocation();
    } else {
      frame.setLocation(location);
    }
    frame.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentMoved(final ComponentEvent e) {
        location = e.getComponent().getLocation();
      }
    });
    firstTime = false;
  }

  private static JPanel makeButtonPane(final NimbusScrollBug mainPanel) {
    JPanel innerButtonPanel = new JPanel(new GridBagLayout());
    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.BOTH;
    constraints.gridx = 0; // forces vertical layout.
    for (final UIManager.LookAndFeelInfo lAndF : INFOS) {
      final JButton button = new JButton(lAndF.getName());
      button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(final ActionEvent e) {
          frame.dispose();
          installLookAndFeel(lAndF.getClassName());
          makeMainFrame(new NimbusScrollBug(), lAndF.getName());
        }
      });
      innerButtonPanel.add(button, constraints);
    }
    final String version = System.getProperty("java.version");
    JLabel versionLabel = new JLabel("Java Version " + version);
    innerButtonPanel.add(versionLabel, constraints);

    JPanel outerButtonPanel = new JPanel(new BorderLayout());
    outerButtonPanel.add(innerButtonPanel, BorderLayout.PAGE_START);
    return outerButtonPanel;
  }

  private static void installLookAndFeel(final String className) {
    //noinspection OverlyBroadCatchBlock
    try {
      UIManager.setLookAndFeel(className);
    } catch (Exception e) {
      //noinspection ProhibitedExceptionThrown
      throw new RuntimeException(e);
    }
  }

  private NimbusScrollBug() {
    Icon icon = new Icon() {
      @Override
      public void paintIcon(final Component c, final Graphics g, 
                            final int x, final int y) {
        Graphics2D g2 = (Graphics2D) g;
        g2.translate(x, y);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
        Stroke lineStroke = new BasicStroke(6.0f);
        g2.setStroke(lineStroke);
        g2.setColor(Color.white);
        g2.fillRect(0, 0, getIconWidth(), getIconHeight());
        g2.setColor(Color.RED);
        g2.drawLine(0, 0, getIconWidth(), getIconHeight());
        g2.drawLine(0, getIconHeight(), getIconWidth(), 0);
        g2.dispose();
      }

      @Override
      public int getIconWidth() {
        return 1600;
      }

      @Override
      public int getIconHeight() {
        return 900;
      }
    };
    JLabel label = new JLabel(icon);
    setLayout(new BorderLayout());
    final JScrollPane scrollPane = new JScrollPane(label,
            ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    label.addHierarchyBoundsListener(new HierarchyBoundsAdapter() {
      @Override
      public void ancestorResized(final HierarchyEvent e) {
        viewPortLabel.setText("ViewPort Size: " 
                + scrollPane.getViewport().getSize());
      }
    });
    add(scrollPane, BorderLayout.CENTER);
    add(viewPortLabel, BorderLayout.PAGE_END);
  }
}
补充说明: 进一步调查揭示了问题。创建Nimbus的UIDefaults实例的NimbusDefaults类有以下行:

d.put("ScrollBar.maximumThumbSize", new DimensionUIResource(1000, 1000));

对于任何其他外观,两个值都使用4096(因此,对于非常大的监视器,它们将显示相同的行为)。

以下方法可用于安装任何外观,并解决此问题:

private static void installLookAndFeel(final String className) {
  //noinspection OverlyBroadCatchBlock
  try {
    Class<?> lnfClass = Class.forName(className, true,
            Thread.currentThread().getContextClassLoader());
    final LookAndFeel lAndF;
    lAndF = (LookAndFeel) lnfClass.getConstructor().newInstance();

    // Reset the defaults after instantiating, but before
    // calling UIManager.setLookAndFeel(). This fixes the Nimbus bug
    DimensionUIResource dim = new DimensionUIResource(4096, 4096);
    lAndF.getDefaults().put("ScrollBar.maximumThumbSize", dim);
    UIManager.setLookAndFeel(lAndF);
  } catch (Exception e) {
    final String systemName = UIManager.getSystemLookAndFeelClassName();
    // Prevents an infinite recursion that's not very likely...
    // (I like to code defensively)
    if (!className.equals(systemName)) {
      installLookAndFeel(systemName);
    } else {
      // Feel free to handle this any other way.
      //noinspection ProhibitedExceptionThrown
      throw new RuntimeException(e);
    }
  }
}

当然,您可以通过使用更大的值来解决大型监视器的问题。
我确认垂直滚动条存在完全相同的问题,但仅在窗口垂直放大时才能看到。这就是为什么通常只在水平滚动条上看到这个问题的原因。

我刚在另一个基于Synth的外观(Synthetica)中进行了测试,它可以正常工作。因此,问题似乎是Nimbus而不是Synth。(当然,编写Synthetica的人可能已经找到并修复了问题,所以我不能确定。) - Orlando DFree
进一步的测试表明,BoundedRangeModel(JScrollBar的数据模型)具有正确的值,并且似乎正常工作。我曾经想过那可能是问题所在,但现在看起来不是这样。 - Orlando DFree

1
根据您提供的信息,我无法重现您的问题(因此也无法帮助您找出问题所在)。这是一个对我有效的 sscce。您能否使用此示例重现问题?也许问题来自应用程序的其他部分。
public static void main(String[] args){
    try {
        for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                UIManager.setLookAndFeel(info.getClassName());
                break;
            }
        }
    } catch (Exception e) {
        // If Nimbus is not available, you can set the GUI to another look and feel.
    }

    //Create Frame
    JFrame frame = new JFrame("Title");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create Table
    JTable table = new JTable(0, 2);
    ((DefaultTableModel) table.getModel()).addRow(new Object[]{"Sample Text", "Hi Mom!"});
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

    // Wrap table in Scroll pane and add to frame
    frame.add(new JScrollPane(table), BorderLayout.CENTER);

    // Finish setting up the frame and display
    frame.setBounds(0, 0, 600,400);
    frame.setPreferredSize(new Dimension(600, 400));
    frame.pack();       
    frame.setVisible(true);
}

哦,我无法复现这段代码的问题。我确实不得不添加了一堆列,以便JTable可以延伸到面板之外。当我这样做时,它可以正确滚动。我会继续尝试解决它。另外,请查看我的第二次编辑...我应该在之前提到它。 - Matthew Denaburg
好的,我已经拿到你的代码了。添加足够的列,使行在滚动窗格外水平延伸,并添加足够的行,使其垂直延伸。我已经这样做了,现在它只能垂直滚动。 - Matthew Denaburg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接