8

我最初提出的问题并没有清楚地说明我的问题/问题,所以我会更好地解释它。我有一个JButton将 a 设置JDialog为可见的。JDialog 有一个WindowListener将其设置为在事件中不可见windowDeactivated(),只要用户在对话框外单击,就会触发该事件。该按钮ActionListener检查对话框是否可见,如果为真则隐藏,如果为假则显示。

windowDeactivated()只要用户在对话框外单击,无论是否单击按钮,都将始终触发。我遇到的问题是当用户单击按钮关闭对话框时。对话框由 关闭,WindowListener然后ActionListener尝试显示它。

如果windowDeactivated()没有setVisible(false),则对话框仍处于打开状态,但位于父窗口后面。我要问的是如何访问 click inside 的位置windowDeactivated()。如果我知道用户单击了按钮并且 windowDeactivated() 可以跳过隐藏对话框,那么按钮ActionListener将看到它仍然可见并隐藏它。

公共属性按钮扩展 JButton {

    私有 JDialog theWindow;

    公共属性按钮(){
        theWindow = new JDialog();
        theWindow.setUndecorated(true);
        theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        theWindow.add(new JMenuCheckBoxItem("Something"));
        theWindow.addWindowListener(new WindowListener() {
            // 只是一个例子,需要实现其他方法
            公共无效窗口停用(WindowEvent e){
                theWindow.setVisible(false);
            }
        });
        this.addActionListener(new ActionListener() {
            公共无效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,它都会在父窗口和按钮获得焦点之前触发,因此对话框已经关闭。

我相信解决方案是确保在对话框关闭一段时间之前禁用按钮,这就是我所做的。我在对话框关闭时禁用了按钮。第二个挑战是找到重新启用按钮的方法,但必须在处理完鼠标按下事件之后,否则将单击按钮并立即再次显示对话框。

我的第一个解决方案使用了一个javax.swing.Timer设置为在对话框失去焦点时触发一次,延迟为 100 毫秒,然后重新启用按钮。这是有效的,因为小的时间延迟确保了在单击事件已经到达按钮之后才启用按钮,并且由于按钮仍然被禁用,因此没有被单击。

我在这里发布的第二种解决方案更好,因为不需要计时器或延迟。我只是将调用包装起来以重新启用 中的按钮SwingUtilities.invokeLater,这会将这个事件推送到事件队列的 END 中。此时,鼠标按下事件已经在队列中,因此启用按钮的操作保证在此之后发生,因为 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

您可以尝试使用 JPanel 而不是 aa JDialog 作为下拉属性列表。像这样的东西:

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);
    }

}

在 Swing 中使用轻量级组件而不是像 JDialog 这样的重量级组件总是更可取的,并且像您报告的那样具有较少的不良影响。这种方法的唯一问题是面板位置和大小可能会受到父级中活动的布局管理器的影响。

于 2010-11-16T17:34:26.807 回答
0

这是一个有效的解决方案。基本上,如果窗口刚刚关闭,我们希望避免显示窗口,单击按钮也会停用和隐藏窗口。mouseDown 和 windowDeactivated 都在同一个输入事件上处理,尽管事件时间略有不同。由于它是在 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(“弹出窗口”)并尝试将其放置在触发其可见性的按钮上方。

这种方法有一个您原来的基于对话框的解决方案没有的限制:在玻璃窗格顶部绘制的任何内容都必须适合框架的内容区域(毕竟,它不是窗口)。这就是为什么我在下面的代码中进行一些调整以确保 popup< 的坐标在内容窗格的范围内(否则 JLabel 将简单地在框架的边缘被裁剪)。

它还有一个限制,即被玻璃窗格捕获的鼠标按下不会委托给任何底层组件。因此,如果您在玻璃窗格可见时单击按钮,玻璃窗格将消失但也会消耗单击,并且您认为单击的按钮不会做出反应。如果愿意,可以绕过这个,但它会变得更加混乱,我想让我的例子保持相对简单。:-)

导入 java.awt.Color;
导入 java.awt.Container;
导入 java.awt.FlowLayout;
导入 java.awt.KeyEventDispatcher;
导入 java.awt.KeyboardFocusManager;
导入 java.awt.Point;
导入 java.awt.Window;
导入 java.awt.event.ActionEvent;
导入 java.awt.event.ActionListener;
导入 java.awt.event.KeyEvent;
导入 java.awt.event.MouseAdapter;
导入 java.awt.event.MouseEvent;
导入 javax.swing.JButton;
导入 javax.swing.JDialog;
导入 javax.swing.JFrame;
导入 javax.swing.JLabel;
导入 javax.swing.JPanel;
导入 javax.swing.JRootPane;
导入 javax.swing.SwingUtilities;
导入 javax.swing.border.BevelBorder;
导入 javax.swing.border.CompoundBorder;
导入 javax.swing.border.EmptyBorder;

公共类 GlassPaneTest 扩展 JFrame {

    公共静态类 PropertiesButton 扩展 JButton {

        /** 当前显示的玻璃窗格。
         * 如果不显示任何内容,则应为 null。*/
        私人JPanel theGlassPane;
        /** 连接窗口的根窗格。用于固定玻璃板。*/
        私有最终 JRootPane rootPane;
        /** 连接窗口的内容窗格。用于坐标计算。*/
        私有最终容器内容窗格;
        /* 一个“key hook”,允许我们在玻璃窗格可见时拦截任何按键,
         * 所以我们可以隐藏玻璃窗格。*/
        私有最终 KeyEventDispatcher keyHook = new KeyEventDispatcher() {

            公共布尔dispatchKeyEvent(KeyEvent e){
                if (theGlassPane == null || e.getID() != KeyEvent.KEY_PRESSED) {
                    返回假;
                }
                setGlassPaneVisible(false);
                返回真;
            }
        };

        公共属性按钮(窗口父窗口){
            if (!(parentWindow instanceof JFrame || parentWindow instanceof 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(new ActionListener() {

                公共无效actionPerformed(ActionEvent e){
                    setGlassPaneVisible(theGlassPane == null);
                }
            });
        }

        私人JPanel createGlassPane(){
            // 将玻璃窗格创建为透明的、无布局的面板
            //(允许绝对定位),覆盖整个内容窗格。
            // 让它在任何鼠标按下时消失。
            JPanel gp = new JPanel();
            gp = 新的 JPanel();
            gp.setOpaque(false);
            gp.setLayout(null);
            gp.setBounds(contentPane.getBounds());
            gp.addMouseListener(new MouseAdapter() {

                @覆盖
                公共无效鼠标按下(鼠标事件e){
                    setGlassPaneVisible(false);
                }
            });

            // 创建“popup” - 透明组件上显示的组件
            // 覆盖。
            JPanel 弹出窗口 = 新 JPanel();
            popup.setBorder(新的 CompoundBorder(
                    新的 BevelBorder(BevelBorder.RAISED),
                    新的 EmptyBorder(5, 5, 5, 5)));
            popup.setBackground(Color.YELLOW);
            popup.add(new JLabel("Some info for \"" + getText() + "\"."));
            // 需要,因为玻璃窗格没有布局管理器。
            popup.setSize(popup.getPreferredSize());

            // 将弹出窗口定位在触发按钮的正上方
            // 它的可见性。
            点 buttonLocationInContentPane = SwingUtilities.convertPoint(this, 0, 0, contentPane);
            int x = buttonLocationInContentPane.x;
            int horizOverlap = x + popup.getWidth() - contentPane.getWidth();
            if (horizOverlap > 0) {
                x -= 水平重叠;
            }
            int y = buttonLocationInContentPane.y - popup.getHeight();
            如果(y < 0){
                y = 0;
            }
            popup.setLocation(x, y);

            gp.add(弹出);

            返回 gp;
        }

        私人无效 setGlassPaneVisible(布尔可见){
            KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            如果(可见){
                theGlassPane = 创建GlassPane();
                rootPane.setGlassPane(theGlassPane);
                theGlassPane.setVisible(true);
                kfm.addKeyEventDispatcher(keyHook);
            } 别的 {
                theGlassPane.setVisible(false);
                kfm.removeKeyEventDispatcher(keyHook);
                theGlassPane = null;
            }

        }
    }

    // 一个简单的测试程序
    公共 GlassPaneTest() {
        setTitle("玻璃窗格示例");
        setLayout(new FlowLayout(FlowLayout.CENTER));
        for (int i = 1; i <= 10; ++i) {
            PropertiesButton pb = new PropertiesButton(this);
            pb.setText("属性按钮" + i);
            添加(铅);
        }
        设置大小(400、300);
    }

    公共静态无效主要(字符串[]参数){
        SwingUtilities.invokeLater(new Runnable() {

            公共无效运行(){
                JFrame f = new GlassPaneTest();
                f.setDefaultCloseOperation(EXIT_ON_CLOSE);
                f.setVisible(true);
            }
        });

    }
}
于 2010-11-16T19:47:11.723 回答
0

我可以建议不要使用 WindowListener,而是使用 WindowStateListener,然后为 WINDOW_DEACTIVATED 和 WINDOW_LOST_FOCUS 测试传入的 WindowEvent。这应该涵盖 Dialog 位于父窗口后面的可能性。

于 2011-01-14T16:42:19.253 回答
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 回答