【问题标题】:Swing ContentPane not updating after using (re)validate, repaintSwing ContentPane 使用(重新)验证后不更新,重新绘制
【发布时间】:2012-11-14 15:30:31
【问题描述】:

类似的问题已经被问过好几次了。参见例如herehere

但我真的很想了解为什么我的代码不起作用。正如在这个问题的其他版本中所回答的那样, CardLayout 可能就足够了,但在我的情况下,我不确定它是否理想。无论如何,我感兴趣的是从概念上理解为什么这不起作用。

我有一个 JFrame,它的内容窗格监听关键事件。当在内容窗格中按下某个键时,内容窗格会告诉 JFrame 使用新的内容窗格更新自身。这是问题的一个简单示例:

此代码是完全可编译的。您可以复制粘贴它并按原样运行。

这是我的 JFrame:

import javax.swing.*;
import java.awt.*;
import java.util.Random;

public class SimpleSim extends JFrame{

    private static SimpleSim instance = null;

    public static SimpleSim getInstance(){
        if(instance == null){
            instance = new SimpleSim();
        }

        return instance;
    }

    private SimpleSim(){}

    public void initialize(){

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setExtendedState(Frame.MAXIMIZED_BOTH);
        this.pack();
        this.setVisible(true);

        update();
    }

    public void update(){
        System.out.println("SIMPLE_SIM UPDATE THREAD: " + Thread.currentThread().getName());
        Random rand = new Random();

        float r = rand.nextFloat();
        float g = rand.nextFloat();
        float b = rand.nextFloat();

        SimplePanel simplePanel = new SimplePanel(new Color(r, g, b));
        JPanel contentPane = (JPanel) this.getContentPane();

        contentPane.removeAll();
        contentPane.add(simplePanel);
        contentPane.revalidate();
        contentPane.repaint();

        validate();
        repaint();

    }


}

这是我的 JPanel,用作我的内容窗格:

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;


public class SimplePanel extends JPanel implements KeyListener {

    public SimplePanel(Color c){

        setFocusable(true);
        setLayout(null);
        setBackground(c);
        setVisible(true);
        this.addKeyListener(this);
    }
    public void keyTyped(KeyEvent keyEvent) {
        if(keyEvent.getKeyChar() == 'a'){
            System.out.println("a");
            System.out.println("SIMPLE_PANEL KEY PRESS THREAD: "  + Thread.currentThread().getName());
            SimpleSim.getInstance().update();
        }
    }

    public void keyPressed(KeyEvent keyEvent) {
    }
    public void keyReleased(KeyEvent keyEvent) {
    }
}

奇怪的是,第一次按a 时它会起作用,但之后就不行了。我的猜测是这里发生了线程问题。我可以看到,当第一次调用 update 时,它是在主线程上调用的。下次在 EDT 上调用它。我尝试使用 invokeLater() 调用 update() ,但也没有用。我找到了使用不同设计模式的解决方法,但我真的很想了解为什么这不起作用。

另外,运行简单的类:

public class Run {

    public static void main(String[] args){
        SimpleSim.getInstance().initialize();
    }
}

注意: 验证和重绘 JFrame 的看似多余的调用是为了安抚我提供的第二个链接上发布的建议,该链接指出: 在受影响最大的组件上调用 validate()。这可能是 Java 渲染周期中最混乱的部分。对 invalidate 的调用将组件及其所有祖先标记为需要布局。对 validate 的调用执行组件及其所有后代的布局。一个“向上”工作,另一个“向下”工作。您需要在树中将受您的更改影响的最高组件上调用 validate。 我认为这会导致它工作,但无济于事。

【问题讨论】:

  • 虽然我没有相反的证据(除了 main 方法),但与 UI 的所有交互都必须在 EDT 的上下文中执行。您可能正在这样做,但是您发表的一些声明让我感到担忧。如果您一开始是谨慎的,那么请随意忽略此声明...

标签: java swing event-dispatch-thread contentpane


【解决方案1】:

抱歉,我对您的代码进行了一些修改,但这让测试变得非常容易......

我可以看到的导入更改在更新方法中。基本上我只是在框架上调用revalidate

重新验证状态

重新验证组件层次结构直到最近的验证根。

此方法首先使组件层次结构无效,从 这个组件直到最近的验证根。随后,该 从最近的验证开始验证组件层次结构 根。

这是一种方便的方法,旨在帮助应用程序开发人员 避免手动查找验证根。基本上是等价的 首先调用这个组件的 invalidate() 方法,然后 在最近的验证根上调用 validate() 方法

我认为最后一部分是您自己的代码中缺少的部分。

public class SimpleSim extends JFrame {

    private static SimpleSim instance = null;

    public static SimpleSim getInstance() {
        if (instance == null) {
            instance = new SimpleSim();
        }

        return instance;
    }

    private SimpleSim() {
    }

    public void initialize() {

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(400, 400);
        this.setVisible(true);
        setLayout(new BorderLayout());

        update();

    }

    public void update() {
        System.out.println("NEXT: " + Thread.currentThread().getName());
        Random rand = new Random();

        float r = rand.nextFloat();
        float g = rand.nextFloat();
        float b = rand.nextFloat();

        SimplePanel simplePanel = new SimplePanel(new Color(r, g, b));
        JPanel contentPane = (JPanel) this.getContentPane();

        getContentPane().removeAll();
        add(simplePanel);

        revalidate();
    }

    public class SimplePanel extends JPanel {

        public SimplePanel(Color c) {

            setFocusable(true);
            setLayout(null);
            setBackground(c);
            setVisible(true);
            requestFocusInWindow();
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "A");
            am.put("A", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("a");
                    System.out.println("KEY: " + Thread.currentThread().getName());
                    SimpleSim.getInstance().update();
                }
            });

        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                SimpleSim.getInstance().initialize();
            }
        });
    }
}

另外,我建议您利用key bindings API 而不是KeyListener。它将解决一些重点问题;)

更新

经过一段时间对各种排列的测试,我们得出的结论是,主要问题与焦点问题有关。

虽然SimplePanel 是可聚焦的,但没有任何东西给它聚焦,这意味着无法触发关键侦听器。

添加simplePanel.requestFocusInWindow AFTER 添加到框架后似乎允许关键侦听器保持活动状态。

根据我自己的测试,没有调用revalidate,面板没有更新。

【讨论】:

  • +1。但是,我认为问题不在于 revalidate()。您也可以将其添加到我的代码中(事实上,我也尝试过),但它无法修复它。我放了过多的 (re)validate、repaint、pack 等调用来说明我认为问题不在于让 swing 来验证组件层次结构。这里的问题是线程问题,您的代码修复它的原因是因为您最初使用 EventQueue 的 invokeLater 调用运行程序。这就是我想用我的问题来澄清的。为什么要解决它?我的原始代码中的线程有什么问题?
  • 另外,在其他问题中也提到了键绑定 API,这是非常合理的建议。我以后会用这个
  • Swing 不是线程安全的。您可能已经被竞争条件或其他线程异常访问过。在测试你的代码时,revalidate 是唯一真正有效的东西
  • 我刚刚从main 方法中取出了EventQueue.invokeLater 调用,并直接调用了你的初始化程序,它工作正常。第一个电话来自main,其余的来自AWT-EventQueue-0。这可能是对您不利的问题(焦点、关键侦听器、事件队列)的组合
  • 我认为这是一个焦点问题。如果我在添加之后调用simplePanel.requestFocusInWindow,我可以让关键监听器触发,但除非我添加revalidate,否则它永远不会更新
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 1970-01-01
  • 2012-10-04
  • 1970-01-01
  • 2012-06-18
  • 1970-01-01
相关资源
最近更新 更多