(注:在撰写此文后,我找到了解决方案,见本文附录。)
要重现这个问题,您需要使滚动条必需。这就是为什么有些人很难重现这个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.*;
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;
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) {
try {
UIManager.setLookAndFeel(className);
} catch (Exception e) {
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) {
try {
Class<?> lnfClass = Class.forName(className, true,
Thread.currentThread().getContextClassLoader());
final LookAndFeel lAndF;
lAndF = (LookAndFeel) lnfClass.getConstructor().newInstance();
DimensionUIResource dim = new DimensionUIResource(4096, 4096);
lAndF.getDefaults().put("ScrollBar.maximumThumbSize", dim);
UIManager.setLookAndFeel(lAndF);
} catch (Exception e) {
final String systemName = UIManager.getSystemLookAndFeelClassName();
if (!className.equals(systemName)) {
installLookAndFeel(systemName);
} else {
throw new RuntimeException(e);
}
}
}
当然,您可以通过使用更大的值来解决大型监视器的问题。
我确认垂直滚动条存在完全相同的问题,但仅在窗口垂直放大时才能看到。这就是为什么通常只在水平滚动条上看到这个问题的原因。