KeyListener won't work? - java

I am trying to make a very simple 2-frame program called Duck Simulator.
This has a JFrame and 2 pictures. If you want to know what it does, it just is a JFrame with a starting picture of a duck sitting in a pond. It has a JLabel saying "Press D to drink water!" And when you press D, it is supposed to set the image to the duck drinking.
It shows the opening image of the duck sitting in the pond in the JFrame, but when I press D, it doesn't do anything.
Here is the code:
package net.ducksimulator.classes;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class a implements KeyListener {
static JFrame f;
public static void main (String[] args) {
f = new JFrame("Duck Simulator ALPHA");
try {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-sitting.png")))));
} catch (IOException e) {
System.out.println("Image doesn't exist.");
}
f.setResizable(false);
f.setVisible(true);
JLabel l = new JLabel("Press D to drink water!");
l.setBounds(250,20,100,10);
f.add(l);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
}
public void drink() throws IOException {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-drinking.png")))));
}
public void sit() throws IOException {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-sitting.png")))));
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
try {
drink();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
#Override
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
try {
System.out.println("Bagels");
sit();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
#Override
public void keyTyped(KeyEvent e) {
}
}

Simply implementing KeyListener isn't enough, you need to specify which component you want to receive key events.
This is where the problems start. KeyListener will only raise events for components that have focus AND are focusable.
A better solution would be to use the key bindings API which provides you with better control over the level of focus a component needs before it will trigger key events
You should avoid using setBounds for, at least, two reasons.
Firstly, the container you adding the component to is under the control of a layout manager (BorderLayout in this instance), which makes the use of setBounds pointless and secondly, you don't control the factors which will alter the required amount of space a component might need when presented on different platforms, such as font metrics and rendering pipelines. Let the layout managers do there job

Related

Java bringing window to front doesnt let me type in textfield

I want to be able to type in textfield when my frame brought foreground. Program is set to bring my Frame to the foreground after 5 seconds and set focus on textfield. I start the program then click another window. My frame shows up after 5 seconds with cursor blinking. But when i type something it doesnt actually taken as input by textfield. I also implemented FocusListener to confirm that focus set to textfield when frame brought to foreground. My operating system is Windows 10.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TestMain implements FocusListener {
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event-dispatching thread.
*/
public JFrame frame = null;
private void createAndShowGUI() {
// Create and set up the window.
frame = new JFrame();
frame.addFocusListener(this);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
JLabel label1 = new JLabel("Label");
panel.add(label1);
JTextField textField1 = new JTextField();
textField1.setPreferredSize(new Dimension(300, 40));
panel.add(textField1);
textField1.addFocusListener(this);
label1.addFocusListener(this);
frame.addWindowListener(new WindowAdapter() {
// Program Closing Alert
public void windowActivated(WindowEvent e) {
System.out.println("window activated");
textField1.requestFocusInWindow();
}
});
frame.getContentPane().add(panel, BorderLayout.CENTER);
// Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
TestMain mn = null;
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
mn = new TestMain();
mn.createAndShowGUI();
System.out.println("START");
try {
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("Focus Gained by " + e.getComponent().getClass().getName());
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("Focus Lost by " + e.getComponent().getClass().getName());
}
}
UPDATE : When i add
frame.setLocationByPlatform( true );
after
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
it works only for the first time. if i change main function as
try {
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
mn.frame.setAlwaysOnTop(false);
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
mn.frame.setAlwaysOnTop(false);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
it doesnt work for the second time
You do bring focus to the JTextField but you dont b ring focus to the frame itself. Bringing the frame to the front and bringing the focus to the frame is different. A solution similar can be found here: How to set focus the already running application?
Moving your frame doesn't focus your textfield. You should add toFront() after set always on top so if the Window is visible, brings the Window to the front and may make it the focused Window.
try {
Thread.sleep(5000);
mn.frame.setVisible ( true );
mn.frame.toFront ( );
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Changing Look And Feel by Swapping UIDefaults

I am writing a GUI Builder and want the user to be able to change the LookAndFeel of the GUI he builds. The LookAndFeel should only be changed for the Components inside the editor area. The rest of the Application should remain with the SystemLookAndFeel.
The great problem is, that the LookAndFeel is implemented as a Singleton and changing the LookAndFeel multiple times during the Application causes a lot of bugs.
I started experimenting with Buttons:
I tried setting the ButtonUI to MetalButtonUI, but they didn't render properly. So I debugged the default paintComponent method of JButton and saw that the ButtonUI still needed the UIDefaults, which were not complete since they were the WindowsUIDefaults.
My current solution is to set the MetalLookAndFeel, save the UIDefaults, then change the LookAndFeel to SystemLookAndFeel and save those UIDefaults aswell and everytime I draw a Button inside the editor I swap the UIDefaults.
Here is the Code:
public class MainClass{
public static Hashtable systemUI;
public static Hashtable metalUI;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
metalUI = new Hashtable();
metalUI.putAll(UIManager.getDefaults());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
systemUI = new Hashtable();
systemUI.putAll(UIManager.getDefaults());
} catch (Exception e) {
e.printStackTrace();
}
/* ...
* Generate JFrame and other stuff
* ...
*/
}
});
}
}
public class MyButton extends JButton {
public MyButton(String text) {
super(text);
ui = MetalButtonUI.createUI(this);
}
#Override public synchronized void paintComponent(Graphics g) {
UIManager.getDefaults().putAll(Application.metalUI);
super.paintComponent(g);
UIManager.getDefaults().putAll(Application.systemUI);
}
}
As you can see here the result is pretty good. On the left is the MetalLaF as it should look and on the right, how it gets rendered in my application. The gradient is painted correctly, but the Border and the Font aren't.
So I need to know why not all elements of the LaF are beeing applied to the Button and how to fix that.
-
Edit:
I found an ugly solution. The LookAndFeel has to be changed before Button creation, because the Graphics object will be created in the Constructor. After the super constructor was called you can change the LookAndFeel back.
Next you need to change the LookAndFeel before the Component is painted/repainted. The only point I got it working was in paintComponent before super is called. You can change it back after super is called.
Code:
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
public class MainClass {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(f.getExtendedState() | JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
f.setLayout(new FlowLayout());
f.add(new MyButton("MetalButton"));
f.add(new JButton("SystemButton"));
f.pack();
f.setVisible(true);
}
});
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
ui = MetalButtonUI.createUI(this);
}
#Override public synchronized void paintComponent(Graphics g) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
super.paintComponent(g);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
}
}
Edit 2:
Do not use this unless absolutely necessery!
This is extremely unstable. After a lot of testing I found it less buggy when I just swapped the UIDefaults instead of the whole LookAndFeel, but I do not recommend doing any of those.
Edit 3:
The best solution I found was using JavaFX as a GUI. I inserted a swing node into the Editor area and now can modify the Look and Feel of the swing components as often as I want without any noticeable side effects.
Rant:
If you can always choose JavaFX if you want to modify the style of your application. CSS makes it as easy as possible without any side effects ever!
Much Thanks
Jhonny
Disclarimer
Swing's Look And Feel isn't designed to be switched after it's first initalised, it's actually a kind of fluky side effect that it's possible. Some look and feels and some components might not like you doing this and may not behave as they might other wise under normal conditions.
Possible solution
For the love of sanity, DON'T change the UI defaults in the paintComponent method (don't change the state of the UI at all from within any paint method EVER, painting paints the current state only), that's just asking for no end of trouble.
Instead, when required use UIManager.setLookAndFeel(,,,) and SwingUtiltiies#updateComponentTreeUI and pass in the most top level container
For example...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LookAndFeelSwitcher {
public static void main(String[] args) {
new LookAndFeelSwitcher();
}
public LookAndFeelSwitcher() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);
add(new JLabel("I have a bad feeling about this"), gbc);
add(new JTextField("When this blows up in your face, don't blame me"), gbc);
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
DefaultComboBoxModel model = new DefaultComboBoxModel(lafs);
JComboBox cb = new JComboBox(model);
cb.setRenderer(new LookAndFeelInfoListCellRenderer());
add(cb, gbc);
String name = UIManager.getLookAndFeel().getName();
for (int index = 0; index < model.getSize(); index++) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) model.getElementAt(index);
if (info.getName().equals(name)) {
model.setSelectedItem(info);
break;
}
}
cb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) cb.getSelectedItem();
String className = info.getClassName();
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(TestPane.this));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
}
});
}
public class LookAndFeelInfoListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof UIManager.LookAndFeelInfo) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) value;
value = info.getName();
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
}
}

Update jPanel background color when a button is pressed

I'm just starting with Java and I wanted to make a little program that opens a jFrame with a text field in which you can write a number. Then you press a button and another jFrame with a jPanel, which will turn green if the number is even, and black if it's odd. I edited the code of the jPanel so that the color changes depending on the number, but the problem is that it will only work once. If I write "2" and press the button, the jFrame will appear with a green panel, but then if I write another odd number and press it again, the frame will stay green.
How could I solve this so that the background color changes whenever I press the button? I should also say that I made an "if-else", so that you could only open the second jFrame once because I didn't know how to make it close and then open again, so maybe that has to do with the problem. Thanks!
This is the code in the Panel. To make it easier, I tried to just turn it green if a zero was introduced, and now it doesn't even work:
jPanel1 = new javax.swing.JPanel();
if ("0".equals(Taller2.opcion)) {
jPanel1.setBackground(new java.awt.Color(0, 255, 0));
}
else {
jPanel1.setBackground(new java.awt.Color(0, 0, 0));
}
jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(255, 255, 255)));
// Code of sub-components - not shown here
// Layout setup code - not shown here
// Code adding the component to the parent container - not shown here
Here is the pretty basic main class:
public class Taller2 {
/**
* #param args the command line arguments
*/
public static String opcion;
public static boolean panelabierto;
public static void main(String[] args) {
Pregunta a = new Pregunta();
a.setVisible(true);
opcion = null;
panelabierto = false;
}
}
The second jFrame (the one with the jPanel inside) only has the basic code generated by Netbeans on the designer. If you need the code for the jFrame with the text field, I could add it too, although I believe the problem lies within the jPanel.
Don't create more instances of JPanel, simply create one and change it's state.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JFormattedTextField field = new JFormattedTextField(NumberFormat.getInstance());
field.setColumns(4);
setLayout(new GridBagLayout());
add(field);
field.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long value = (Long)field.getValue();
if ((value % 2) == 0) {
setBackground(Color.GREEN);
} else {
setBackground(Color.RED);
}
}
});
setBackground(Color.BLACK);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Are you sure your code is correct? My best guess (without you providing your code) is that your code for checking whether the number is odd or even does not work correctly.
The best way to determine whether a number is odd or even in Java is to use the modulus (%) operator:
if ((num%2)==0) {
// Number is even
} else {
// Number is odd
}
(Replace "num" with the number you want to test.)

Why can't I get KeyEvent.VK_TAB when I use Key Binding for a JPanel

I will print related info if users focus on current window and press a key. However, it works for some keys like 'a' but not for 'tab'. Here is a simple demo:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class KeyBindingTest {
public static void main(String[] args) {
KeyBindingTest test = new KeyBindingTest();
test.createUI();
}
public void createUI(){
JFrame frame = new JFrame("KeyBinding Test");
MainPanel mainPanel = new MainPanel();
frame.add(mainPanel,BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#SuppressWarnings("serial")
class MainPanel extends JPanel{
public MainPanel(){
setPreferredSize(new Dimension(200, 200));
//========================key binding============================
requestFocusInWindow();
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("a is typed");
}
});
String tabString = "tabStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
getActionMap().put(tabString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("tab is typed");
}
});
}
}
}
What can I do to reach it? Thanks in advance.
Quote from How to Use the Focus Subsystem (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features) (suggested by #alex2410(link to #camickr post) and #mKorbel):
In most Look and Feel models, components are navigated using the Tab and Shift-Tab keys. These keys are the default focus traversal keys and can be changed programmatically.
...
Tab shifts the focus in the forward direction. Shift-Tab moves the focus in the backward direction. Tabbing moves the focus through the buttons into the text area. Additional tabbing moves the cursor within the text area but not out of the text area because, inside a text area, Tab is not a focus traversal key. However, Control-Tab moves the focus out of the text area and into the first text field. Likewise, Control-Shift-Tab moves the focus out of the text area and into the previous component.
...
The Control key is used by convention to move the focus out of any component that treats Tab in a special way, such as JTable.
You have just received a brief introduction to the focus architecture. If you want more details, see the specification for the Focus Subsystem.
So if you want to make the Tab KeyBinding action work in the panel, you need to remove the Tab key focus navigation from the panel.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
//http://stackoverflow.com/q/24800417/714968
public class KeyBindingTest3 {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame("KeyBinding Test");
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new MainPanel());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class MainPanel extends JPanel {
public MainPanel() {
super();
//#see JTable constructor
Set<KeyStroke> forwardKeys = new HashSet<KeyStroke>(1);
forwardKeys.add(KeyStroke.getKeyStroke(
KeyEvent.VK_TAB, InputEvent.CTRL_MASK));
setFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
Set<KeyStroke> backwardKeys = new HashSet<KeyStroke>(1);
backwardKeys.add(KeyStroke.getKeyStroke(
KeyEvent.VK_TAB, InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK));
setFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
setPreferredSize(new Dimension(200, 200));
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override public void actionPerformed(ActionEvent e) {
System.out.println("a is typed");
}
});
String tabString = "TAB";
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
getActionMap().put(tabString, new AbstractAction() {
#Override public void actionPerformed(ActionEvent e) {
System.out.println("tab is typed");
}
});
}
}
interesting issue with TAB, looks like as bug, because isn't possible to get, capture the KeyChar from TAB without using Shift_TAB before, event from TAB is somehow consumed elsewhere, no idea whats happened
my view - there is an issue with Focus because key TAB is used by Native OS and as built_in KeyBindings in Swing,
opposite issue with TAB and Shift_TAB in question Java Swing: how to stop unwanted shift-tab keystroke action
maybe someone has explanation how to catch a TAB event
TAB is used as KeyBindings (built_in in API) for many JComponents or navigations inside Container contains more than one JComponent
funny output from AWTEventListener (win8_64b/Java7_xxx)
is typed //tab is pressed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed //shift is pressed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed // tab is pressed again
is typed
is typed
is typed
is typed //:-) nobody knows something from milky way
is typed
is typed
shift tab is typed //now is tab event unlocked for Container in Swing
shift tab is typed
shift tab is typed
ctrl tab is typed
ctrl tab is typed
ctrl tab is typed
tab is typed // now is possible, finally TAB is unlocked and firing an event
tab is typed
tab is typed
from code
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
//https://stackoverflow.com/q/24800417/714968
public class KeyBindingTest {
public static void main(String[] args) {
KeyBindingTest test = new KeyBindingTest();
test.createUI();
}
public void createUI() {
JFrame frame = new JFrame("KeyBinding Test");
MainPanel mainPanel = new MainPanel();
frame.add(mainPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) event;
System.out.println(ke.getKeyChar() + " is typed");
}
}
}, AWTEvent.KEY_EVENT_MASK);
}
#SuppressWarnings("serial")
class MainPanel extends JPanel {
public MainPanel() {
setPreferredSize(new Dimension(200, 200));
//========================key binding============================
//requestFocusInWindow();
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("a is typed");
}
});
String tabString = "TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(tabString), tabString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(tabString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("tab is typed");
}
});
String tabShiftString = "shift TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(tabShiftString), tabShiftString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(tabShiftString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("shift tab is typed");
}
});
String ctrlShiftString = "ctrl TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(ctrlShiftString), ctrlShiftString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(ctrlShiftString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("ctrl tab is typed");
}
});
}
}
}
I think it should work if you put
setFocusTraversalKeysEnabled(false);
in your MainPanel constructor. At least it works for e.g. addKeyListener(...);

How to set an accelerator for a JMenu sub menu?

I have a user request to add an accelerator to a sub menu (JMenu) which would allow the user to press the short cut and have the corresponding sub menu "fold out", showing its contained menu items.
I don't recall every having seen something like this (either in Java or any other language). Our application is written in Java using Swing. We have a number of JMenuItems with accelerators that work well, but when I attempted to add an accelerator to JMenu I get the following exception:
java.lang.Error: setAccelerator() is not defined for JMenu. Use setMnemonic() instead.
I've tried to use the MenuDemo! code to experiment with this a bit further.
This is what I tried:
//a submenu
menu.addSeparator();
submenu = new JMenu("A submenu");
submenu.setMnemonic(KeyEvent.VK_S);
submenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, InputEvent.CTRL_MASK));
The last line is the one added by me, which causes the exception.
I've tried extensive googling but all I can find is articles on how to add accelerators to JMenuItem.
It seems the JMenu does not support this natively. Is there any workaround to achieve this behaviour?
Another option is to override the accelerator get/set and reproduce the JMenuItem behaviour. Then the UI will do the rest of the job.
The important thing is to fire the property change and have a consistent get/set for the accelerator. The advantage of this solution is that it also provides a visual indication of the shortcut/accelerator.
Here is a small demo code:
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestMenu {
protected void initUI() {
JFrame frame = new JFrame(TestMenu.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMenuBar bar = new JMenuBar();
JMenu topMenu = new JMenu("Top Menu");
JMenu subMenu = new JMenu("Sub menu") {
private KeyStroke accelerator;
#Override
public KeyStroke getAccelerator() {
return accelerator;
}
#Override
public void setAccelerator(KeyStroke keyStroke) {
KeyStroke oldAccelerator = accelerator;
this.accelerator = keyStroke;
repaint();
revalidate();
firePropertyChange("accelerator", oldAccelerator, accelerator);
}
};
subMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_MASK));
JMenuItem item1 = new JMenuItem("Item 1");
JMenuItem item2 = new JMenuItem("Item 2");
subMenu.add(item1);
subMenu.addSeparator();
subMenu.add(item2);
topMenu.add(subMenu);
bar.add(topMenu);
frame.setJMenuBar(bar);
frame.setSize(400, 300);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new TestMenu().initUI();
}
});
}
}
I don't think that is possible just like that.
But what you could do is adding an AbstractAction, which simulates a click.
submenu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control U"), "expand");
submenu.getActionMap().put("expand", new AbstractAction("expand") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent evt) {
submenu.doClick();
}
}
);
I hope this also works for you.

Categories

Resources