7

ツールバーの色でドロップダウン コンポーネントを作成しています。そこで、「Swing ハック」の本からアイデアを取り入れ、概念を少し変更して、ドロップダウンに Swing の標準 JColorChooser を追加しました。動作は次のとおりです。ボタンをクリックすると、カラー チューザのあるウィンドウが表示されます。色を選択するとドロップダウン ウィンドウが閉じ、ボタンのテキストの色が選択した色に変わります。全体としてはすべて機能しますが、不快なバグが 1 つあります。これらの操作の後、UI がフリーズし、ボタンは「マウス オーバー」などのマウス イベントを受け入れません。そして、これは私がクリックするまで起こります。その後、UI は必要に応じて動作します。

ここに概念のあるコードがあります。

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.metal.MetalComboBoxIcon;

class DropDownComponent2 {
    private JWindow _window;
    private boolean _windowShouldBeShown = false;
    private JComponent _component;
    private AbstractButton _button;
    private JFrame _ownerFrame;

    public DropDownComponent2(JFrame ownerFrame, JComponent component, AbstractButton button) {
        _ownerFrame = ownerFrame;
        _component = component;
        _button = button;
        _button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                _window.setVisible(false);
                Point pt = _button.getLocationOnScreen();
                pt.translate(0, _button.getHeight());
                _window.setLocation(pt);
                showWindow();
                _windowShouldBeShown = true;
            }
        });

        _button.addAncestorListener(new AncestorListener() {
            public void ancestorAdded(AncestorEvent event){
                _window.setVisible(false);
            }
            public void ancestorRemoved(AncestorEvent event){
                _window.setVisible(false);
            }
            public void ancestorMoved(AncestorEvent event){
                if (event.getSource() != _window) {
                    System.out.println("Ansestor moved");
                    _window.setVisible(false);
                }
            }
        });

        Toolkit.getDefaultToolkit().addAWTEventListener(
                new AWTEventListener() { 
                    public void eventDispatched(AWTEvent event) {
                        if (event.getID() == MouseEvent.MOUSE_CLICKED) {
                            if ( !_window.getBounds().contains( MouseInfo.getPointerInfo().getLocation() )) {
                                if (_windowShouldBeShown)
                                    _windowShouldBeShown = false;
                                else {
                                    _window.setVisible(false);
                                }
                            }
                        }
                    }            
                }, AWTEvent.MOUSE_EVENT_MASK);

        _window = new JWindow(_ownerFrame);
        _window.getContentPane().add(component);
        _window.addWindowFocusListener(new WindowAdapter() {
            public void windowLostFocus(WindowEvent evt) {
                System.out.println("window lost focus");
                _window.setVisible(false);
            }
        });
        _window.pack();        
    }

    private Rectangle getScreenRect() {
        return new Rectangle(java.awt.Toolkit.getDefaultToolkit().getScreenSize());
    }

    public void showWindow() {
        Rectangle screenRect = getScreenRect();
        Rectangle windowRect = _window.getBounds();

        int sx1 = screenRect.x;
        int sx2 = screenRect.x + screenRect.width;
        int sy1 = screenRect.y;
        int sy2 = screenRect.y + screenRect.height;

        int wx1 = windowRect.x;
        int wx2 = windowRect.x + windowRect.width;
        int wy1 = windowRect.y;
        int wy2 = windowRect.y + windowRect.height;

        if (wx2 > sx2) {
            _window.setLocation(wx1-(wx2-sx2), _window.getY());
        }
        if (wx1 < sx1) {
            _window.setLocation(0, _window.getY());
        }
        if (wy2 > sy2) {
            _window.setLocation(_window.getX(), wy1-(wy2-wy1));
        }
        if (wy2 < sy1) {
            _window.setLocation(_window.getX(), 0);
        }

        _window.setVisible(true);
    }

    public void hideWindow() {
        _window.setVisible(false);
    }  
}

public class DropDownFrame extends JFrame {
    JButton _button;
    JColorChooser _colorChooser;
    DropDownComponent2 _dropDown;
    JWindow _window;

    public DropDownFrame() {
        _colorChooser = new JColorChooser();
        _colorChooser.setPreviewPanel(new JPanel());
        _colorChooser.setColor(Color.RED);

        // Remove panels other than Swatches
        AbstractColorChooserPanel[] panels = _colorChooser.getChooserPanels();
        for (int i=0; i<panels.length; i++) {
            if (!panels[i].getDisplayName().equals("Swatches"))
                _colorChooser.removeChooserPanel(panels[i]);
        }
        _colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
            // ### I think the key point is there
            @Override
            public void stateChanged(ChangeEvent e) {
                _dropDown.hideWindow();
                _button.setForeground(_colorChooser.getColor());
            }

        });            

        _button = new JButton("Show JWindow");
        _button.setIcon(new MetalComboBoxIcon());
        _button.setHorizontalTextPosition(SwingConstants.LEFT);
        this.getContentPane().add(_button);

        _dropDown = new DropDownComponent2(DropDownFrame.this, _colorChooser, _button);

        pack();
        setVisible(true);        
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new DropDownFrame();
            }
        });
    }
}

JColorChooserと選択モデルに何かがあると確信しています。しかし、私はその考えを理解できません。requestFocus() と requestFocusInWindow() を試しました。失敗。JWindow の代わりに JDialog を使用しようとしました。ダイアログで[x]を押すと、すべてが思いどおりですが、色を選択するとUIもフリーズします!

もう一点!カラーチューザーの代わりにドロップダウンウィンドウ内でラベルを使用し、ラベルのクリックを処理すると、すべて正常に機能します。ウィンドウが閉じ、フリーズしません!

SwingUtilities.invokeLater() 内に _dropDown.hideWindow() を配置していました。そして成功せずに。

私は何が欠けていますか?

4

1 に答える 1

1

あなたの質問に対する他のコメントと同様に、UI のフリーズを再現できませんでした。Windows 7、Sun JDK 7、および Linux Mint、OpenJDK 7 でコードを試しました。ただし、コードを改善する必要があると思います。まず、やろうとしていることは非常に冗長に見えます。次に、避けるべき方法をいくつか使用しています。

最初の段落で、どこかをクリックするまで UI がフリーズすると述べています。それは矛盾しているように聞こえます。フリーズした場合、クリックして再び機能させることはできません。つまり、あなたは単に焦点の問題を抱えていると思いますか? 私が間違っている場合は、私を修正してください。実際、色を選択した後、ボタンはフォーカスを失うため、カラー チューザーを再度開くには 2 回クリックする必要があります。したがって、変更リスナーは次のようになります。

public void stateChanged(ChangeEvent ce) {
    button.setForeground(colorChooser.getColor());
    DropDownWindow.this.setVisible(false);
    // the drop down window had the focus while being displayed
    // so after it closes, return focus to the button
    button.requestFocus();
}

次に、リスナーを AWT イベント ディスパッチ スレッドに直接登録して、いくつかのマウス イベントをキャッチします。UIコンポーネントの通常のマウスリスナーを使用する代わりに、なぜこれを正確に行うのかわかりません。AWT イベント スレッドは、プロファイリング、テスト、デバッグなどの目的でイベントを監視するためにのみ使用してください。UI の状態を変更したり、高価なコードをプッシュしたりするために使用しないでください。UI の変更では、UI コンポーネントで特定のイベント リスナーを常に使用するかSwingWorkers、より高価な計算を行います。

使用しているプラ​​ットフォームまたは Java ランタイムの実装によっては、AWT イベント スレッドを使用すると、ウィンドウの表示状態を変更しているため、UI が応答しなくなる場合があります。

また、invokeLaterドロップダウン ウィンドウを作成するために を使用しても、コードが AWT イベント スレッドに配置されるだけなので、何も変わりません。メソッドinvokeLaterとそのフレンドinvokeAndWaitは、Java のいわゆる初期スレッド (そのうちの 1 つがmainメソッドを実行するため、「メイン」スレッドと呼ばれます) をイベント ディスパッチ スレッドと同期させるために使用されます。これらを使用して、UI スレッドからコードを非同期的に実行することはありません。たとえば、このようなウィンドウまたはフレームを作成するメイン メソッドを実行すると、

public static void main (String[] args) {
    new JFrame().setVisible(true);
}

これは、Java ランタイム環境によってイベント スレッドに延期されていると常に考えることができます。したがって、基本的に何が起こるかは次のとおりです。

public static void main (String[] args) {
    // JRE 'starts' Swing by creating an event thread and then
    SwingUtilities.invokeLater(new Runnable() {
    public void run (Runnable r) {
        new JFrame().setVisible(true);
    });
}

したがって、メインメソッドはそのコードを別のコードにカプセル化しますが、Runnableこれは基本的に同じ効果があり、問題を解決しません。

あなたのプログラムを書き直して、少し短くしました。あなたがやろうとしていることを正確に行うかどうかはわかりません。このコードを Windows と Linux で問題なく試しました。カラー チューザーを開いているかどうかに関係なく、ボタンのマウス イベントがまだ処理されていることがわかります。

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JWindow;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

class DropDownWindow extends JWindow {
    static void setColorChooserPanels (JColorChooser jcc, String name) {
        for (AbstractColorChooserPanel p : jcc.getChooserPanels()) {
            if (!p.getDisplayName().equals(name)) {
                jcc.removeChooserPanel(p);
            }
        }
    }

    final JColorChooser colorChooser;

    DropDownWindow (JFrame ownerFrame, final JButton button) {
        super(ownerFrame);

        colorChooser = new JColorChooser();
        setColorChooserPanels(colorChooser, "Swatches");
        colorChooser.setVisible(true);
        colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent ce) {
                button.setForeground(colorChooser.getColor());
                DropDownWindow.this.setVisible(false);
                button.requestFocus();
            }
        });

        add(colorChooser);
        setSize(colorChooser.getPreferredSize());
        pack();

        button.addActionListener(new ActionListener() {
            public void actionPerformed (ActionEvent e) {
                Point pt = button.getLocationOnScreen();
                pt.translate(0, button.getHeight());
                DropDownWindow.this.setLocation(pt);
                DropDownWindow.this.setVisible(true);
            }
        });
    }
}

class MyFrame extends JFrame {
    final JButton button;

    MyFrame () {
        super();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 100);
        setLocation(500, 300);

        button = new JButton("Choose Color");

        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered (MouseEvent event) {
                System.out.println("mouse entered at: (" + event.getXOnScreen() +
                        ", " + event.getYOnScreen() + ")");
            }
        });

        button.addMouseListener(new MouseAdapter() {
            public void mouseExited (MouseEvent event) {
                System.out.println("mouse exited at: (" + event.getXOnScreen() +
                        ", " + event.getYOnScreen() + ")");
            }
        });

        add(button);

        DropDownWindow ddw = new DropDownWindow(this, button);

        setVisible(true);
    }
}

public class Test {
    public static void main(String[] args) {
        new MyFrame();
    }
}

このコードを試して、問題が解決するかどうか教えてください。そうでない場合は、経験した効果についてもう少し詳しく説明してください. UI は本当にフリーズしますか? 応答性に問題がありますか? それとも、必要以上にクリックする必要があるのは、単にフォーカスの問題ですか。

于 2013-11-16T15:07:53.373 に答える