setVisible() 和正在绘制的组件之间会发生啥?
Posted
技术标签:
【中文标题】setVisible() 和正在绘制的组件之间会发生啥?【英文标题】:What happens between setVisible() and a component being painted?setVisible() 和正在绘制的组件之间会发生什么? 【发布时间】:2014-04-18 17:27:30 【问题描述】:我有一个JScrollPane,其中包含一个JPanel,其中包含一些自定义组件,这些组件是JPanel 的扩展(它们都是同一类型)。在显示包含 JFrame 之前,此 JScrollPane 被设置为不可见。
当用户选择JCheckBox 时,我使用setVisible()
来显示滚动窗格。第一次显示窗格时,在调用 setVisible()
和绘制窗格之间存在显着延迟。如果用户取消选择并重新选择复选框,则会立即绘制窗格。
我的自定义组件已经构建并添加到面板中。它们是ComponentListeners,但除了componentResized()
之外,它们的实现都是空的。他们也有一个自定义的paintComponent()
方法,但是该方法内部的断点显示延迟发生在 this 被调用之前,因此它不是缓慢绘制的情况。
JScrollPane 或 JPanel 都没有任何其他侦听器。 setVisible()
和 paintComponent()
之间还会发生什么?我还能从哪里确定延迟的来源?
编辑: 在Kevin Workman 的建议下尝试创建和MCVE,我发现我的问题与我想象的略有不同。似乎没有其他任何事情发生,滚动窗格将永远不会被绘制。
在我的最小示例(见下文)中,在我调整框架大小之前不会显示该项目。在初始显示之后,setVisible()
会立即应用,就像在完整程序中一样。下面是演示问题的示例代码:
public class VisibilityDelayExample extends JFrame
private JPanel contentPane;
private JCheckBox chckbxAdvancedView;
private JScrollPane scrollPane;
/**
* Launch the application.
*/
public static void main(String[] args)
EventQueue.invokeLater(new Runnable()
public void run()
try
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
VisibilityDelayExample frame = new VisibilityDelayExample();
frame.setVisible(true);
catch (Exception e)
e.printStackTrace();
);
/**
* Create the frame.
*/
public VisibilityDelayExample()
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
chckbxAdvancedView = new JCheckBox("Advanced View");
chckbxAdvancedView.addPropertyChangeListener(new PropertyChangeListener()
public void propertyChange(PropertyChangeEvent arg0)
System.err.println("Property Changed");
if(chckbxAdvancedView.isSelected() != scrollPane.isVisible())
scrollPane.setVisible(chckbxAdvancedView.isSelected());
scrollPane.invalidate();
scrollPane.repaint();
VisibilityDelayExample.this.invalidate();
VisibilityDelayExample.this.repaint();
);
contentPane.add(chckbxAdvancedView, BorderLayout.NORTH);
scrollPane = new JScrollPane();
scrollPane.setVisible(false);
contentPane.add(scrollPane, BorderLayout.CENTER);
JPanel panel = new JPanel();
scrollPane.setViewportView(panel);
for(int j = 0; j < 100;j++)
panel.add(new JLabel("Label " + j));
现在的问题是,在调整大小的过程中发生了什么导致setVisible()
生效,我怎样才能让它立即发生?
【问题讨论】:
为什么不看源代码? @KevinWorkman 我确实跟踪了 setVisible() 但它似乎只在 JComponent 中设置了一些我假设在某个绘制线程中检查过的标志,但我不知道从哪里开始跟踪该过程.除了尚未调用的paintComponent。 它不仅仅是设置一些标志。 JComponent.setVisible() 调用 Component.setVisible(),后者又调用 Component.show(),后者做了很多事情(实际上太多了,无法粘贴到评论中!)。不过,用 MCVE 来帮助您会更容易,它可以展示这种行为。 @KevinWorkman 哈我已经习惯了在我的代码中不关注 super.whateverMethod() 以至于我在 setVisible() 源代码中忽略了它。我还添加了一个 MCVE,它对这个问题有了新的认识。 【参考方案1】:现在的问题是,在导致调整大小的过程中发生了什么
当您调整框架大小时,会调用布局管理器,因此滚动窗格的大小会从 (0, 0) 更改为适当的值。
我怎样才能让它立即发生?
当您从可见 GUI 添加/删除组件时,您需要在父容器上使用 revalidate()
和 repaint()
。我知道你没有添加组件,但效果是一样的,因为它没有有效的大小。
// scrollPane.revalidate();
// scrollPane.repaint();
// VisibilityDelayExample.this.invalidate();
// VisibilityDelayExample.this.repaint();
contentPane.revalidate();
contentPane.repaint();
【讨论】:
我错误地认为即使它不可见也会改变项目的大小。我发现repaint()
没有必要,因为revalidate()
似乎会触发重绘。
@Fr33dan,是的,很多时候你只需要一个 revalidate()。但是有时您可能需要两者,所以我通常同时使用两者。例如,如果您从面板中删除一个组件,然后添加另一个相同大小的组件,则 revalidate() 不会认为有任何更改,因此 repaint() 不会自动完成。如果您查看 Swing 组件的所有“setter”方法的源代码,它们还会自动调用 revalidate() 和 repaint()。
嗯,我总是尽量少用,但如果 Swing 命令总是同时使用这两个命令,我可能会重新考虑该策略。【参考方案2】:
Conversely,您的示例的这种变体覆盖了滚动窗格的getPreferredSize()
方法以提供任意大小。该复选框使用ItemListener
来切换可见性。请注意pack()
的使用,它“导致此Window
调整大小以适合其子组件的首选大小和布局。”
附录:此更新使滚动窗格最初不可见,并调整框架以适应。
public class VisibilityDelayExample extends JFrame
private JCheckBox chckbxAdvancedView;
private JScrollPane scrollPane;
public static void main(String[] args)
EventQueue.invokeLater(new Runnable()
@Override
public void run()
VisibilityDelayExample frame = new VisibilityDelayExample();
frame.setVisible(true);
);
public VisibilityDelayExample()
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout(0, 0));
chckbxAdvancedView = new JCheckBox("Advanced View");
chckbxAdvancedView.addItemListener(new ItemListener()
@Override
public void itemStateChanged(ItemEvent e)
System.out.println(e);
scrollPane.setVisible(chckbxAdvancedView.isSelected());
);
add(chckbxAdvancedView, BorderLayout.NORTH);
JPanel panel = new JPanel(new GridLayout(0, 1));
for (int j = 0; j < 100; j++)
panel.add(new JLabel("Label " + j));
scrollPane = new JScrollPane(panel)
@Override
public Dimension getPreferredSize()
return new Dimension(450, 300);
;
add(scrollPane, BorderLayout.CENTER);
pack();
scrollPane.setVisible(false);
setSize(scrollPane.getPreferredSize());
【讨论】:
这个例子错过了我的要求之一,即面板最初是不可见的。 哦,我明白了,如果我在pack()
之后设置我的可见性,那么当我稍后再次显示它时,该项目已经调整了大小。这是有道理的。以上是关于setVisible() 和正在绘制的组件之间会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章