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:
KeyListener
.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...
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 KeyEvent
s 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;
}
}
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;
}
}
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