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() 和正在绘制的组件之间会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

SetVisible(false) 更改面板中组件的布局

android能否让一个布局或一个组件完全隐藏并且不占位置

修改主题组件 asha 1.0

在 JFrame 之间切换

Java:语句未按顺序执行

为了绘制轻量级组件,需要递归更新或绘制啥?