Создайте окно свойств проверки, управляемое кнопкой как JDialog - PullRequest
7 голосов
/ 16 ноября 2010

Изначально то, что я спросил, не ясно указывало на мой вопрос / проблему, поэтому я объясню это лучше.У меня есть JButton, который устанавливает JDialog видимым.У JDialog есть WindowListener, который устанавливает его НЕ видимым в событии windowDeactivated(), которое срабатывает каждый раз, когда пользователь нажимает за пределами диалогового окна.Кнопка ActionListener проверяет, является ли диалоговое окно видимым, скрывает его, если оно истинно, показывает, если оно ложно.

windowDeactivated() всегда будет запускать нажатие кнопки или нет, пока пользователь нажимает вне диалогового окна,У меня проблема, когда пользователь нажимает кнопку, чтобы закрыть диалог.Диалоговое окно закрывается с помощью WindowListener, а затем ActionListener пытается его отобразить.

Если windowDeactivated() не setVisible(false), то диалоговое окно все еще открыто, но за родительским окном.Я спрашиваю, как получить доступ к местоположению клика внутри windowDeactivated().Если я знаю, что пользователь нажал на кнопку, и windowDeactivation () может пропустить скрытие диалога, так что кнопка ActionListener увидит, что она все еще видна, и скроет ее.

public PropertiesButton extends JButton {

    private JDialog theWindow;

    public PropertiesButton() {
        theWindow = new JDialog();
        theWindow.setUndecorated(true);
        theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        theWindow.add(new JMenuCheckBoxItem("Something"));
        theWindow.addWindowListener(new WindowListener() {
            // just an example, need to implement other methods
            public void windowDeactivated(WindowEvent e) {
                theWindow.setVisible(false);
            }
        });
        this.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (theWindow.isVisible()) {
                    theWindow.setVisible(false);
                } else {
                    JButton btn = (JButton)e.getSource();
                    theWindow.setLocation(btn.getLocationOnScreen.x,btn.getLocationOnScreen.x-50);
                    theWindow.setVisible(true);
                }
            }
        });
        theWindow.setVisible(false);
    }

}

Ответы [ 6 ]

0 голосов
/ 22 апреля 2011

Вот рабочее решение.По сути, мы хотим не показывать окно, если оно было просто закрыто, нажав на кнопку, которая также деактивирует и скрывает окно.MouseDown и windowDeactivation обрабатываются для одного и того же входного события, хотя время события немного различается.Время действия может быть намного позже, так как оно генерируется в 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);
    }
}
0 голосов
/ 23 февраля 2011

Мне было любопытно, поэтому я решил попробовать эту проблему.Как вы узнали, это сложнее, чем кажется, потому что любой код, который вы пишете в WindowAdapter, всегда будет срабатывать до того, как родительское окно и кнопка получит фокус, и поэтому диалог будет закрыт.

Я считаю, что решение состоит в том, чтобы убедиться, что кнопка отключена, пока диалоговое окно не было закрыто на некоторое время, и это то, что я сделал.Я отключаю кнопку, когда диалог закрывается.Вторая проблема заключалась в том, чтобы найти способ снова включить кнопку, но только после обработки события нажатия кнопки мыши, в противном случае кнопка будет нажата, и диалоговое окно снова появится снова.

Мое первое использованное решение использовалосьjavax.swing.Timer, который был настроен на однократное срабатывание при потере фокуса диалога с задержкой в ​​100 мс, после чего кнопка снова будет активирована.Это сработало, потому что небольшая задержка гарантировала, что кнопка не была включена до тех пор, пока событие нажатия уже не перешло к кнопке, и, поскольку кнопка все еще была заблокирована, она не была нажата.

Второе решение,который я публикую здесь, лучше, потому что не требуется никаких таймеров или задержек.Я просто обертываю вызов, чтобы снова включить кнопку в SwingUtilities.invokeLater, которая помещает это событие в КОНЕЦ очереди событий.На этом этапе событие нажатия мыши уже находится в очереди, поэтому действие по включению кнопки гарантированно произойдет после этого, поскольку 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);
    }

}
0 голосов
/ 14 января 2011

Могу я предложить, чтобы вместо WindowListener вы использовали WindowStateListener, а затем протестировали WindowEvent, переданный для WINDOW_DEACTIVATED и WINDOW_LOST_FOCUS.Это должно охватывать возможность того, что диалог находится за родительским окном.

0 голосов
/ 16 ноября 2010

Развернувшись по совету awheel, я написал следующий пример, который использует функциональность Swing для работы со стеклянными панелями.Подход немного запутанный, но это не редкость, когда вы пробуете что-то умеренно продвинутое в Swing.

Идея состоит в том, чтобы при прозрачном щелчке отображать прозрачную оверлейную панель (стеклянную панель, покрывающую все содержимое окна)кнопку и избавьтесь от нее, когда пользователь либо щелкнет в любом месте окна или нажмет клавишу.

В верхней части этой стеклянной панели я отображаю еще одну JPanel («всплывающее окно») и пытаюсь расположить ее чуть вышекнопка, которая запускает ее видимость.

У этого подхода есть одно ограничение, которого нет у оригинального решения на основе диалоговых окон: все, что нарисовано поверх стеклянной панели, должно вписываться в область содержимого рамки (в конце концов, это неокно).Вот почему я в приведенном ниже коде выполняю некоторые корректировки, чтобы гарантировать, что координаты popup <находятся в границах области содержимого (в противном случае JLabel просто обрезается по краям фрейма). </p>

У него также есть ограничение, заключающееся в том, чтонажатия мыши, застрявшие на стеклянной панели, не делегируются каким-либо нижележащим компонентам.Поэтому, если вы нажмете кнопку, когда стеклянная панель видна, стеклянная панель исчезнет, ​​но также израсходует щелчок, и кнопка, на которую вы думали, что нажали, не будет реагировать.Можно обойти это, если пожелаете, но тогда это станет еще более запутанным, и я хотел, чтобы мой пример был относительно простым.: -)

import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import 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 {

        /** The currently displayed glass pane.
         * Should be null if nothing is displayed. */
        private JPanel theGlassPane;
        /** Root pane of connected window. Used to attach the glass pane. */
        private final JRootPane rootPane;
        /** Content pane of the connected window. Used for coordinate calculation. */
        private final Container contentPane;
        /* A "key hook" that allows us to intercept any key press when the glass pane is visible,
         * so we can hide the glass pane. */
        private final KeyEventDispatcher keyHook = new KeyEventDispatcher() {

            public boolean dispatchKeyEvent(KeyEvent e) {
                if (theGlassPane == null || e.getID() != KeyEvent.KEY_PRESSED) {
                    return false;
                }
                setGlassPaneVisible(false);
                return true;
            }
        };

        public PropertiesButton(Window parentWindow) {
            if (!(parentWindow instanceof JFrame || parentWindow instanceof JDialog)) {
                throw new IllegalArgumentException("only JFrame or JDialog instances are accepted");
            }
            if (parentWindow instanceof JDialog) {
                rootPane = ((JDialog) parentWindow).getRootPane();
                contentPane = ((JDialog) parentWindow).getContentPane();
            } else {
                rootPane = ((JFrame) parentWindow).getRootPane();
                contentPane = ((JFrame) parentWindow).getContentPane();
            }

            addActionListener(new ActionListener() {

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

        private JPanel createGlassPane() {
            // Create the glass pane as a transparent, layout-less panel
            // (to allow absolute positioning), covering the whole content pane.
            // Make it go away on any mouse press.
            JPanel gp = new JPanel();
            gp = new JPanel();
            gp.setOpaque(false);
            gp.setLayout(null);
            gp.setBounds(contentPane.getBounds());
            gp.addMouseListener(new MouseAdapter() {

                @Override
                public void mousePressed(MouseEvent e) {
                    setGlassPaneVisible(false);
                }
            });

            // Create the "popup" - a component displayed on the transparent
            // overlay.
            JPanel popup = new JPanel();
            popup.setBorder(new CompoundBorder(
                    new BevelBorder(BevelBorder.RAISED),
                    new EmptyBorder(5, 5, 5, 5)));
            popup.setBackground(Color.YELLOW);
            popup.add(new JLabel("Some info for \"" + getText() + "\"."));
            // Needed since the glass pane has no layout manager.
            popup.setSize(popup.getPreferredSize());

            // Position the popup just above the button that triggered
            // its visibility.
            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();
            if (y < 0) {
                y = 0;
            }
            popup.setLocation(x, y);

            gp.add(popup);

            return gp;
        }

        private void setGlassPaneVisible(boolean visible) {
            KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            if (visible) {
                theGlassPane = createGlassPane();
                rootPane.setGlassPane(theGlassPane);
                theGlassPane.setVisible(true);
                kfm.addKeyEventDispatcher(keyHook);
            } else {
                theGlassPane.setVisible(false);
                kfm.removeKeyEventDispatcher(keyHook);
                theGlassPane = null;
            }

        }
    }

    // A simple test program
    public GlassPaneTest() {
        setTitle("A glass pane example");
        setLayout(new FlowLayout(FlowLayout.CENTER));
        for (int i = 1; i <= 10; ++i) {
            PropertiesButton pb = new PropertiesButton(this);
            pb.setText("Properties button " + i);
            add(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(true);
            }
        });

    }
}
0 голосов
/ 16 ноября 2010

Простой, хотя и несколько хакерский, способ, который может решить эту проблему, состоит в том, чтобы в PropertiesButton был установлен логический флаг, указывающий, стоит ли нам беспокоиться о следующей операции кнопки. Мы устанавливаем этот флаг, если диалог скрыт из-за события windowDeactivation.

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% доволен этим трюком: могут быть некоторые тонкие случаи, которые я пропустил при неудачном подходе.

0 голосов
/ 16 ноября 2010

Вы можете попробовать использовать JPanel вместо 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);
    }

}

Использование облегченных компонентов вместо тяжеловесных, таких как JDialog, всегда предпочтительнее в Swing и имеет менее нежелательные эффекты, такие как отчет. Единственная проблема этого подхода заключается в том, что на расположение и размер панели может влиять менеджер макета, активный в родительском элементе.

...