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 を検証して再描画するための一見冗長な呼び出しは、私が提供した 2 番目のリンクに投稿されたアドバイスをなだめるために行われました 。これはおそらく、Java のレンダリング サイクルの最も複雑な部分です。invalidate の呼び出しは、コンポーネントとそのすべての祖先をレイアウトが必要であるとマークします。validate の呼び出しは、コンポーネントとそのすべての子孫のレイアウトを実行します。1 つは「上」で動作し、もう 1 つは「下」で動作します。変更の影響を受けるツリーの最上位のコンポーネントで 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 に答える