使用(重新)验证,重绘后,Swing ContentPane不会更新

时间:2012-11-27 03:33:23

标签: java swing event-dispatch-thread contentpane

有几次问过类似的问题。参见例如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 。 我认为这会导致它起作用,但无济于事。

1 个答案:

答案 0 :(得分:4)

我对您的代码进行了一些修改,抱歉,但它使测试SOooo变得更加容易......

我可以看到的导入更改是在update方法中。基本上我只是在框架上调用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它添加到框架之后似乎允许密钥监听器保持活动状态。

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