8

私が最初に尋ねたことは、私の質問/問題を明確に述べていなかったので、よりよく説明します。を可視JButtonに設定するがあります。JDialogJDialog にはWindowListener、イベントで非表示に設定する がありますwindowDeactivated()。これは、ユーザーがダイアログの外側をクリックするたびにトリガーされます。このボタンActionListenerは、ダイアログが表示可能かどうかをチェックし、true の場合は非表示にし、false の場合は表示します。

windowDeactivated()ユーザーがダイアログの外側をクリックしている限り、ボタンをクリックするかどうかにかかわらず、常にトリガーされます。私が抱えている問題は、ユーザーがボタンをクリックしてダイアログを閉じるときです。ダイアログは によって閉じられ、 はWindowListenerそれActionListenerを表示しようとします。

そうwindowDeactivated()でない場合setVisible(false)、ダイアログは開いたままですが、親ウィンドウの背後にあります。私が求めているのは、内部のクリックの場所にアクセスする方法ですwindowDeactivated()。ユーザーがボタンをクリックし、windowDeactivated() がダイアログの非表示をスキップできることがわかっている場合は、ボタンがActionListenerまだ表示されていることを確認して非表示にします。

public PropertiesButton は JButton を拡張します {

    プライベート JDialog theWindow;

    public PropertiesButton() {
        theWindow = new JDialog();
        theWindow.setUndecorated(true);
        theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        theWindow.add(new JMenuCheckBoxItem("何か"));
        theWindow.addWindowListener(new WindowListener() {
            // 単なる例です。他のメソッドを実装する必要があります
            public void windowDeactivated(WindowEvent e) {
                theWindow.setVisible(false);
            }
        });
        this.addActionListener(新しいActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (theWindow.isVisible()) {
                    theWindow.setVisible(false);
                } そうしないと {
                    JButton btn = (JButton)e.getSource();
                    theWindow.setLocation(btn.getLocationOnScreen.x,btn.getLocationOnScreen.x-50);
                    theWindow.setVisible(true);
                }
            }
        });
        theWindow.setVisible(false);
    }

}
4

6 に答える 6

1

興味があったので、この問題を試してみることにしました。WindowAdapterお気づきのとおり、で記述したコードが何であれ、これは常に親ウィンドウとボタンがフォーカスされる前に起動するため、見た目よりも難しくなります。したがって、ダイアログはすでに閉じられています。

解決策は、ダイアログがしばらく閉じられるまでボタンが無効になっていることを確認することだと思います。それが私がやったことです。ダイアログが閉じている間、ボタンを無効にします。2番目の課題は、ボタンを再度有効にする方法を見つけることでしたが、マウスダウンイベントが処理された後でのみ、それ以外の場合はボタンがクリックされ、ダイアログがすぐに再び表示されます。

私の最初の解決策はjavax.swing.Timer、ダイアログがフォーカスを失ったときに100msの遅延で一度トリガーするように設定されたを使用しました。これにより、ボタンが再度有効になります。これが機能したのは、わずかな時間遅延により、クリックイベントがすでにボタンに移動するまでボタンが有効にならず、ボタンがまだ無効になっているため、クリックされなかったためです。

タイマーや遅延が必要ないため、ここに投稿する2番目のソリューションの方が優れています。呼び出しをラップしてボタンを再度有効にしますSwingUtilities.invokeLater。これにより、このイベントがイベントキューのENDにプッシュされます。この時点で、mouse-downイベントはすでにキューにあります。したがって、Swingはイベントを厳密に順番に処理するため、ボタンを有効にするアクションはこの後必ず発生します。ボタンの無効化と有効化は突然発生するため、発生する可能性はほとんどありませんが、ダイアログが表示されなくなるまでボタンをクリックしないようにするだけで十分です。

サンプルコードには、ボタンをに配置するmainメソッドがありますJFrame。ダイアログを開いてから、ボタンをクリックするか、ウィンドウのタイトルバーをクリックして、フォーカスを失うようにすることができます。元のコードをリファクタリングして、ボタンが指定されたダイアログの表示と非表示のみを担当するようにしました。これにより、ボタンを再利用して、必要なダイアログを表示できます。

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class QuickDialogButton extends JButton {

    private final JDialog dialog;

    public QuickDialogButton(String label, JDialog d) {
        super(label);

        dialog = d;

        dialog.addWindowListener(new WindowAdapter() {
            public void windowDeactivated(WindowEvent e) {
                // Button will be disabled when we return.
                setEnabled(false);
                dialog.setVisible(false);
                // Button will be enabled again when all other events on the queue have finished.
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        setEnabled(true);
                    }
                });
            }
        });

        addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Component c = (Component) e.getSource();
                dialog.setLocation(c.getLocationOnScreen().x, c.getLocationOnScreen().y + c.getHeight());
                dialog.setVisible(true);
            }
        });
    }

    public static void main(String[] args) {
        JFrame f = new JFrame("Parent Window");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JDialog d = new JDialog(f, "Child Dialog");
        d.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
        d.add(new JCheckBox("Something"));
        d.setUndecorated(true);
        d.pack();

        f.add(new QuickDialogButton("Button", d));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

}
于 2011-02-23T10:49:25.997 に答える
1

ドロップダウン プロパティ リストに JDialog の代わりに JPanel を使用してみてください。このようなもの:

public class PropertiesButton extends JButton {

    private JPanel theWindow;

    public PropertiesButton() {
        theWindow = new JPanel();
        theWindow.add(new JMenuCheckBoxItem("Something"));

        this.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (theWindow.isVisible()) {
                    theWindow.setVisible(false);
                    getParent().remove(theWindow);
                } else {
                    JButton btn = (JButton)e.getSource();
                    getParent().add(theWindow);             
                    theWindow.setBounds(
                       btn.getX(),
                       btn.getY() + btn.getHeight(), 100, 100);

                    theWindow.setVisible(true);
                }
            }
        });
        theWindow.setVisible(false);
    }

}

JDialog のような重いコンポーネントの代わりに軽量コンポーネントを使用することは、Swing では常に推奨され、報告されたような望ましくない影響が少なくなります。このアプローチの唯一の問題は、パネルの位置とサイズが、親でアクティブなレイアウト マネージャーの影響を受ける可能性があることです。

于 2010-11-16T17:34:26.807 に答える
0

これが実用的な解決策です。基本的に、ウィンドウを非アクティブ化および非表示にするボタンをクリックしてウィンドウを閉じただけの場合は、ウィンドウが表示されないようにします。mouseDownとwindowDeactivateはどちらも同じ入力イベントで処理されますが、イベント時間はわずかに異なります。アクション時間は、mouseUpで生成されるため、はるかに遅くなる可能性があります。WindowAdapterを使用するとWindowListenerに便利であり、@ Overrideアノテーションを使用すると、タイプミスが原因で機能しなくなるのを防ぐことができます。


public class PropertiesButton extends JButton {

    private JDialog theWindow;
    private long deactivateEventTime = System.currentTimeMillis();
    private long mouseDownTime;

    public PropertiesButton(String text, final Frame launcher) {
        super(text);

        theWindow = new JDialog();
        theWindow.getContentPane().add(new JLabel("Properties"));
        theWindow.pack();
//    theWindow.setUndecorated(true);
        theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
//    theWindow.add(new JMenuCheckBoxItem("Something"));
        theWindow.addWindowListener(new WindowAdapter() {
            // just an example, need to implement other methods
            @Override
            public void windowDeactivated(WindowEvent e) {
                deactivateEventTime = EventQueue.getMostRecentEventTime();
                theWindow.setVisible(false);
            }
        });
        this.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                boolean alsoDeactivated = Math.abs(deactivateEventTime - mouseDownTime) < 100;
                if (theWindow.isVisible()) {
                    theWindow.setVisible(false);
                } else if (!alsoDeactivated) {
//                  JButton btn = (JButton)e.getSource();
//                  theWindow.setLocation(btn.getLocationOnScreen().x,btn.getLocationOnScreen().x+50);
                    theWindow.setVisible(true);
                }
            }
        });
        theWindow.setVisible(false);
    }
    public void processMouseEvent(MouseEvent event) {
        if (event.getID() == MouseEvent.MOUSE_PRESSED) {
            mouseDownTime = event.getWhen();
        }
        super.processMouseEvent(event);
    }
}
于 2011-04-22T07:46:22.807 に答える
0

awheel のアドバイスを拡張して、Swing のガラス ペイン機能を使用する次の例を作成しました。このアプローチは少し面倒ですが、Swing である程度高度なものを試している場合は、これは珍しいことではありません。

ボタンをクリックすると透明なオーバーレイ パネル (ウィンドウの内容全体を覆うガラス ペイン) を表示し、ユーザーがウィンドウ内の任意の場所をクリックするか、キーを押すと、それを破棄するという考え方です。

このガラス ペインの上に、別の JPanel (「ポップアップ」) を表示し、その可視性をトリガーするボタンのすぐ上に配置しようとします。

このアプローチには、元のダイアログ ベースのソリューションにない制限が 1 つあります。ガラス ペインの上に描画されるものはすべて、フレームのコンテンツ領域内に収まる必要があります (結局のところ、それはウィンドウではありません)。そのため、以下のコードで popup< の座標がコンテンツ ペインの境界内にあることを確認するためにいくつかの調整を行います (そうしないと、JLabel はフレームの端で単純にトリミングされます)。

また、ガラス ペインによってキャッチされたマウス プレスが、基になるコンポーネントに委任されないという制限もあります。したがって、ガラス ペインが表示されているときにボタンをクリックすると、ガラス ペインは消えますが、クリックも消費され、クリックしたはずのボタンが反応しなくなります。必要に応じてこれを回避することもできますが、さらに複雑になるため、例を比較的単純にしたいと考えました。:-)

java.awt.Color をインポートします。
java.awt.Container をインポートします。
java.awt.FlowLayout をインポートします。
java.awt.KeyEventDispatcher をインポートします。
java.awt.KeyboardFocusManager をインポートします。
import java.awt.Point;
java.awt.Window をインポートします。
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
java.awt.event.MouseAdapter をインポートします。
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
javax.swing.JFrame をインポートします。
javax.swing.JLabel をインポートします。
javax.swing.JPanel をインポートします。
import javax.swing.JRootPane;
javax.swing.SwingUtilities をインポートします。
import javax.swing.border.BevelBorder;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;

public class GlassPaneTest extends JFrame {

    public static class PropertiesButton extends JButton {

        /** 現在表示されているガラス ペイン。
         * 何も表示されない場合は null にする必要があります。*/
        プライベート JPanel theGlassPane;
        /** 接続されたウィンドウのルート ペイン。ガラス板の貼り付けに使用。*/
        private final JRootPane rootPane;
        /** 接続されたウィンドウのコンテンツ ペイン。座標計算に使用されます。*/
        プライベート最終コンテナ contentPane;
        /* ガラス板が表示されているときにキーの押下をインターセプトできるようにする「キー フック」、
         * ガラス板を隠すことができます。*/
        private final KeyEventDispatcher keyHook = new KeyEventDispatcher() {

            public boolean dispatchKeyEvent(KeyEvent e) {
                if (theGlassPane == null || e.getID() != KeyEvent.KEY_PRESSED) {
                    false を返します。
                }
                setGlassPaneVisible(false);
                true を返します。
            }
        };

        public PropertiesButton(ウィンドウの親ウィンドウ) {
            if (!(JFrame の親ウィンドウ インスタンス || JDialog の親ウィンドウ インスタンス)) {
                throw new IllegalArgumentException("JFrame または JDialog インスタンスのみが受け入れられます");
            }
            if (parentWindow instanceof JDialog) {
                rootPane = ((JDialog) parentWindow).getRootPane();
                contentPane = ((JDialog) parentWindow).getContentPane();
            } そうしないと {
                rootPane = ((JFrame) parentWindow).getRootPane();
                contentPane = ((JFrame) parentWindow).getContentPane();
            }

            addActionListener(新しいActionListener() {

                public void actionPerformed(ActionEvent e) {
                    setGlassPaneVisible(theGlassPane == null);
                }
            });
        }

        プライベート JPanel createGlassPane() {
            // ガラス ペインを透明なレイアウトのないパネルとして作成します
            // (絶対配置を許可するため)、コンテンツ ペイン全体をカバーします。
            // マウスを押すと消えます。
            JPanel gp = 新しい JPanel();
            gp = 新しい JPanel();
            gp.setOpaque(false);
            gp.setLayout(null);
            gp.setBounds(contentPane.getBounds());
            gp.addMouseListener(new MouseAdapter() {

                @オーバーライド
                public void mousePressed(MouseEvent e) {
                    setGlassPaneVisible(false);
                }
            });

            // 「ポップアップ」を作成します - 透明に表示されるコンポーネント
            // かぶせる。
            JPanel popup = new JPanel();
            popup.setBorder(新しい CompoundBorder(
                    new BevelBorder(BevelBorder.RAISED),
                    new EmptyBorder(5, 5, 5, 5)));
            popup.setBackground(Color.YELLOW);
            popup.add(new JLabel("\" の情報"" + getText() + "\"."));
            // ガラス ペインにはレイアウト マネージャーがないため必要です。
            popup.setSize(popup.getPreferredSize());

            // トリガーされたボタンのすぐ上にポップアップを配置します
            // その可視性。
            Point buttonLocationInContentPane = SwingUtilities.convertPoint(this, 0, 0, contentPane);
            int x = buttonLocationInContentPane.x;
            int horizOverlap = x + popup.getWidth() - contentPane.getWidth();
            if (horizOverlap > 0) {
                x -= horizOverlap;
            }
            int y = buttonLocationInContentPane.y - popup.getHeight();
            もし (y < 0) {
                y = 0;
            }
            popup.setLocation(x, y);

            gp.add(ポップアップ);

            gp を返します。
        }

        private void setGlassPaneVisible(boolean visible) {
            キーボード フォーカス マネージャー kfm = キーボード フォーカス マネージャー.getCurrentKeyboardFocusManager();
            もし (見える) {
                theGlassPane = createGlassPane();
                rootPane.setGlassPane(theGlassPane);
                theGlassPane.setVisible(真);
                kfm.addKeyEventDispatcher(keyHook);
            } そうしないと {
                theGlassPane.setVisible(false);
                kfm.removeKeyEventDispatcher(keyHook);
                theGlassPane = null;
            }

        }
    }

    // 簡単なテスト プログラム
    public GlassPaneTest() {
        setTitle("ガラス板の例");
        setLayout(新しい FlowLayout(FlowLayout.CENTER));
        for (int i = 1; i <= 10; ++i) {
            PropertiesButton pb = 新しい PropertiesButton(this);
            pb.setText("プロパティ ボタン" + i);
            追加 (pb);
        }
        setSize(400, 300);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                JFrame f = new GlassPaneTest();
                f.setDefaultCloseOperation(EXIT_ON_CLOSE);
                f.setVisible(真);
            }
        });

    }
}
于 2010-11-16T19:47:11.723 に答える
0

これを解決する簡単な方法は、ややハック的ではありますが、PropertiesButton に、次のボタン アクションを処理する必要があるかどうかを示すブール値フラグを持たせることです。windowDeactivated イベントによってダイアログが非表示になっている場合は、このフラグを反転させます。

public PropertiesButton extends JButton {

    private JDialog theWindow;
    private boolean ignoreNextAction;

(をちょきちょきと切る)

    theWindow.addWindowListener(new WindowAdapter() {
        @Override
        public void windowDeactivated(WindowEvent e) {
            ignoreNextAction = true;
            theWindow.setVisible(false);
        }
    });
    this.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            if (ignoreNextAction) {
                ignoreNextAction = false;
                return;
            }
            // ...normal action handling follows
        }
    });

私はこのトリックに 100% 満足しているわけではないことに注意してください: アプローチが失敗するところを見逃した微妙なケースがあるかもしれません。

于 2010-11-16T18:12:06.100 に答える
0

WindowListener を使用する代わりに WindowStateListener を使用し、WINDOW_DEACTIVATED と WINDOW_LOST_FOCUS の両方で渡された WindowEvent をテストすることをお勧めします。これにより、ダイアログが親ウィンドウの背後にある可能性がカバーされます。

于 2011-01-14T16:42:19.253 に答える