Search code examples
javaswingkey-bindings

Close current window using ctrl+w


I have a Java program that uses a bunch of JFrame objects. To make it easier to clean up desktop, I want to implement that the current focused window can be closed with Ctrl + w.

I tried to use a keybinding (in the superclass of any view) whose Action's actionPerformed method contains this:

frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));

This works quite well – as long as I use only one window. It only works, when the last-opened frame is focused, and it only closes that one.


My question is:

  1. Why does the keybinding behaves like this? (I guess it's by design.)
  2. How to create a keybinding per frame without adding any single component to a KeyListener.

Solution

  • Why does the keybinding behaves like this? (I guess it's by design.)

    I'd guess you're doing something wrong, but without any kind of example code, it's impossible to know what

    How to create a keybinding per frame without adding any single component to a KeyListener

    There's a number of ways you might use to achieve this...

    Global based solution...

    One approach is to take a "global" approach, using a system which doesn't rely on you extending from a root solution, but which can be applied to just about any existing or future project.

    AWTEventListener

    One solution might be to attach a AWTEventListener to the Toolkit. This is quite low level and provides you access into the ALL the key events which the system is processing

    import java.awt.AWTEvent;
    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.Toolkit;
    import java.awt.Window;
    import java.awt.event.AWTEventListener;
    import java.awt.event.KeyEvent;
    import javax.swing.FocusManager;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        private int count = 0;
        private int xPos = 10;
        private int yPos = 10;
    
        public Test() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    installKeyboardMonitor();
                    for (int index = 0; index < 10; index++) {
                        makeWindow();
                    }
                }
            });
        }
    
        public static void installKeyboardMonitor() {
            Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                @Override
                public void eventDispatched(AWTEvent event) {
                    KeyEvent ke = (KeyEvent) event;
                    if (ke.getID() == KeyEvent.KEY_PRESSED) {
                        System.out.println("Pressed");
                        if (ke.getKeyCode() == KeyEvent.VK_W) {
                            System.out.println("W Key");
                            if (ke.isControlDown()) {
                                System.out.println("Control down");
                                Window window = FocusManager.getCurrentManager().getActiveWindow();
                                if (window != null) {
                                    window.dispose();
                                }
                            }
                        }
                    }
                }
            }, AWTEvent.KEY_EVENT_MASK);
        }
    
        public void makeWindow() {
            count++;
            JFrame frame = new JFrame("Test " + count);
            frame.setContentPane(new JPanel(new BorderLayout()) {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(200, 200);
                }
            });
            frame.add(new JLabel("Window " + count));
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.pack();
            frame.setLocation(xPos, yPos);
            frame.setVisible(true);
            xPos += 100;
            yPos += 100;
        }
    
    }
    

    KeyEventDispatcher

    This is slightly less low level then the AWTEventListener but it focus only on KeyEvents which makes it a little easier to manage, but is essentially the same idea

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.KeyEventDispatcher;
    import java.awt.KeyboardFocusManager;
    import java.awt.Window;
    import java.awt.event.KeyEvent;
    import javax.swing.FocusManager;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        private int count = 0;
        private int xPos = 10;
        private int yPos = 10;
    
        public Test() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    installKeyboardMonitor();
                    for (int index = 0; index < 10; index++) {
                        makeWindow();
                    }
                }
            });
        }
    
        public static void installKeyboardMonitor() {
            KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
                @Override
                public boolean dispatchKeyEvent(KeyEvent ke) {
                    if (ke.getID() == KeyEvent.KEY_PRESSED) {
                        System.out.println("Pressed");
                        if (ke.getKeyCode() == KeyEvent.VK_W) {
                            System.out.println("W Key");
                            if (ke.isControlDown()) {
                                System.out.println("Control down");
                                Window window = FocusManager.getCurrentManager().getActiveWindow();
                                if (window != null) {
                                    window.dispose();
                                    return true;
                                }
                            }
                        }
                    }
                    return false;
                }
            });
        }
    
        public void makeWindow() {
            count++;
            JFrame frame = new JFrame("Test " + count);
            frame.setContentPane(new JPanel(new BorderLayout()) {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(200, 200);
                }
            });
            frame.add(new JLabel("Window " + count));
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.pack();
            frame.setLocation(xPos, yPos);
            frame.setVisible(true);
            xPos += 100;
            yPos += 100;
        }
    
    }
    

    Configurable solution

    Another solution is to provide a "configuration" based solution. This is similar to the concept of having a base component, but frees you from been locked into a single extension point.

    This approach is a little more troublesome, as you actually need to remember to apply to every window and dialog your application might create.

    It simply uses the Key Bindings API to register a binding against the windows JRootPane, but you could use just about any component which you know isn't going to be removed from the window.

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.Window;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        private int count = 0;
        private int xPos = 10;
        private int yPos = 10;
    
        public Test() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    for (int index = 0; index < 10; index++) {
                        makeWindow();
                    }
                }
            });
        }
    
        public static void installKeyBindings(JComponent component) {
            InputMap inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = component.getActionMap();
    
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_DOWN_MASK), "Window.close");
            actionMap.put("Window.close", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Window window = SwingUtilities.windowForComponent(component);
                    if (window != null) {
                        window.dispose();
                    }
                }
            });
        }
    
        public void makeWindow() {
            count++;
            JFrame frame = new JFrame("Test " + count);
            installKeyBindings(frame.getRootPane());
            frame.setContentPane(new JPanel(new BorderLayout()) {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(200, 200);
                }
            });
            frame.add(new JLabel("Window " + count));
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.pack();
            frame.setLocation(xPos, yPos);
            frame.setVisible(true);
            xPos += 100;
            yPos += 100;
        }
    
    }
    

    After thoughts

    This is just three possible solutions. It wouldn't take to much effort to provide a more configurable based solution around each one (so you could supply the key stroke), but I'll leave that up to you