2

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

然而,我真的很想了解为什么我的代码不起作用。正如在这个问题的其他版本中所回答的那样, 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。我认为这会导致它工作,但无济于事。

4

1 回答 1

3

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

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

重新验证状态

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

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

这是一种方便的方法,旨在帮助应用程序开发人员避免手动查找验证根。基本上相当于先在这个组件上调用 invalidate() 方法,然后在最近的 validate root 上调用 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();
            }
        });
    }
}

另外,我建议您利用键绑定API 而不是使用KeyListener. 它将解决一些重点问题;)

更新

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

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

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

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

于 2012-11-27T04:11:04.873 回答