(注:これを書いた後、この投稿の補遺に記載されている解決策を見つけました。)
問題を再現するには、スクロール バーを必須にする必要があります。(これが、このバグの再現に苦労する人がいる理由です。) これは、水平スクロール バーをオプションにすることが明らかな回避策であることを意味します。(これは必ずしも実用的ではありません。)
ウィンドウの幅を 1200 ピクセル以上にドラッグした場合にのみバグが表示されます。それまでは、スクロール バーは正常に機能します。
そして、問題は Nimbus でのみ発生します。(SynthLookAndFeel から作成された他の L&F に表示される可能性がありますが、まだ調査していません。)
偽のスクロール バー サムは、スクロールする必要がない場合にのみ表示されることがわかりました。これは単なる視覚的なバグです。スクロールする必要がある場合、スクロール バーのつまみが表示され、適切に機能しますが、サイズが適切でない場合があります。これが、まだ修正されていない理由かもしれません。
さまざまな L&F を比較できる例を次に示します。この例では、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);
}
}
}
もちろん、より大きな値を使用することで、非常に大きなモニターの問題を解決できます。
垂直スクロール バーにもまったく同じ問題があることを確認しましたが、ウィンドウが垂直方向に非常に大きくなった場合にのみ表示されます。これが、この問題が通常、水平スクロール バーでのみ見られる理由です。