I have some simple code where I'm trying to get keyboard events into a Java applet. The code runs just fine when being run with appletviewer, but when I'm loading it from a browser (tried both Chrome and Firefox), the JApplet won't get focus on click.
Trying exactly the same code with Applet instead of JApplet works without a problem.
Here's my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test extends JApplet {
String s = "";
public void init() {
setFocusable(true);
setEnabled(true);
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
s = "KEY PRESSED: " + e.getKeyCode();
repaint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
boolean ret = requestFocusInWindow();
s = "requestFocusInWindow: " + ret;
repaint();
}
});
requestFocusInWindow();
}
public void start() {
requestFocusInWindow();
}
public void paint(Graphics g) {
super.paint(g);
requestFocusInWindow();
g.setColor(Color.BLACK);
s = "Focus owner: " + isFocusOwner() + ", " + s;
g.drawString(s, 24, 24);
}
}
Applets should be created on Event Dispatch Thread by wrapping code in overridden init() method in SwingUtilities.invokeAndWait() block
Dont use KeyListener for JApplet/Swing components use KeyBindings
call requestFocusInWindow() on JApplet after creating and adding all content to container (this is not necessary with keybindings though)
Also dont do drawing in paint() rather add JPanel to container and override paintComponent(..)
Here is a small example, its a simple JLabel with a dummy label and textfield added to the container with a KeyBinding for A only; so when A is pressed it will be added to JLabel text:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import javax.swing.AbstractAction;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class Test extends JApplet {
#Override
public void init() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
final JLabel label = new JLabel("Text:");
final JLabel label2 = new JLabel("Dummy label");
final JTextField jtf = new JTextField("Dummy Field");
label2.setFocusable(true);
label.setFocusable(true);
//allwos user to add letter A to JLabel
label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "A");
label.getActionMap().put("A", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
String tmp = label.getText();
label.setText(tmp + "A");
}
});
setLayout(new GridLayout(3, 1));
add(label);
add(label2);
add(jtf);
}
});
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
Related
I have two classes that implement KeyListener and FocusListener interfaces. Neither works, but if I don't add the JButton by commenting or removing this: add(whiteJButton), then they do work. Could someone explain to me why this happens? Thanks in advance.
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrames {
public static void main(String[] args) {
JFrame myJFrame = new JFrame("MyJFrame");
myJFrame.setBounds(400, 400, 500, 500);
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyJPanel myJPanel = new MyJPanel();
myJFrame.add(myJPanel);
// Doesn't work
myJFrame.addKeyListener(new MyKeyListener());
// Doesn't work
myJFrame.addFocusListener(new MyFocusListener());
myJFrame.setVisible(true);
}
static class MyJPanel extends JPanel {
public MyJPanel() {
JButton whiteJButton = new JButton("WHITE");
add(whiteJButton);
whiteJButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setBackground(Color.WHITE);
}
});
}
}
}
class MyKeyListener implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
System.out.println("keyTyped: " + e.getKeyChar());
}
#Override
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed: " + e.getKeyChar());
}
#Override
public void keyReleased(KeyEvent e) {
System.out.println("keyReleased: " + e.getKeyChar());
}
}
class MyFocusListener implements FocusListener {
#Override
public void focusGained(FocusEvent e) {
System.out.println("focusGained");
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("focusLost");
}
}
The problem is that the button steals focus from the JFrame, preventing the focus listener from working (focus is already gone). Some solutions if you absolutely need to use a KeyListener are kludges, including making the JButton not focusable: whiteJButton.setFocusable(false);, but if you do this, you need to do this for all components added. And yes you can request focus as the other answer suggests, but it should be requestFocusInWindw(), not requestFocus() (many similar questions explain why this is so). And if you do this and the components are still focusable, then the whole thing breaks down if a component gains focus -- not good.
Better (as per comments) is to use Key Bindings which don't require focus to work if you use the correct InputMap. Note that key bindings is how Swing itself traps keystrokes for components, so using this would follow with the Swing general structure and contracts. The problem with Key Bindings is that you have to bind each key that you wish to trap, but you can use for loops to assist with this.
Another solution is to use a KeyEventDispatcher to the keyboard focus manager:
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
// code goes here
return false;
}
});
Example using Key Bindings:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class KeyboardFun extends JPanel {
private InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
private ActionMap actionMap = getActionMap();
public KeyboardFun() {
setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
setLayout(new GridLayout(0, 8, 3, 3));
for (char c = 'A'; c <= 'Z'; c++) {
final String text = String.valueOf(c);
JButton button = new JButton(text);
button.addActionListener(e -> {System.out.println("Key pressed: " + text);});
add(button);
setBinding(c, button);
}
}
private void setBinding(char c, final JButton button) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(Character.toLowerCase(c));
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
button.doClick();
}
});
}
private static void createAndShowGui() {
KeyboardFun mainPanel = new KeyboardFun();
JFrame frame = new JFrame("KeyboardFun");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
You must focus the object that you add listener, try add listener to "myJFrame". You can't focus JPanel.
Your JPanel is probably isn't focusable at start and your JButton gets Focus.
So you can also add this codes to "myJPanel" :
setFocusable(true);
requestFocusInWindow();
I'm working on an application that allows me to show and hide split planes.
I've read some articles on how to get this but its not what I'm looking for.
here's the code Ive written:
Im currently using netbeans.
private void jSplitPane1MouseEntered(java.awt.event.MouseEvent evt) {
if(MouseInfo.getPointerInfo().getLocation() == jSplitPane1.getLeftComponent().getLocation()){
jSplitPane1.setDividerLocation(100);
System.out.println("Mouse Entered");
}else{
jSplitPane1.setDividerLocation(20);
System.out.println("Mouse Exited");
}
}
I have referred to these posts:
How to make JSplitPane auto expand on mouse hover?
Get Mouse Position
What I want to happen is when I mouse over the left side of the jSplitPane, I would get the divider to extend to 100 as per my first if statement, and when it exists the left side, it contracts back to divider location 20.
This is really, really tricky.
You could use a MouseListener on the "left" component and monitor the mouseEntered and mouseExited events, but these will also get triggered when when you move into and out of a child component which has a MouseListener of it's own (like a JButton).
Okay, you could use a MouseMotionListener on the JSplitPane and monitor for the mouseMoved event and check where the mouse cursor is, but this goes to hell the moment the components (left/right) get their own MouseListener, as the MouseEvents are no longer delivered to the JSplitPane
So, one of the last options you have is to attach a global AWTListener to the event queue and monitor for events which occur on the JSplitPane itself, for example...
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
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 BorderLayout());
JSplitPane pane = new JSplitPane();
pane.setLeftComponent(makePane(Color.RED));
pane.setRightComponent(makePane(Color.BLUE));
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof MouseEvent) {
MouseEvent me = (MouseEvent) event;
if (pane.getBounds().contains(me.getPoint())) {
System.out.println("Global Motion in the pane...");
me = SwingUtilities.convertMouseEvent(me.getComponent(), me, pane);
Component left = pane.getLeftComponent();
if (left.getBounds().contains(me.getPoint())) {
pane.setDividerLocation(100);
} else {
pane.setDividerLocation(20);
}
}
}
}
}, MouseEvent.MOUSE_MOTION_EVENT_MASK);
// You don't need this, this is to demonstrate
// that mouse events aren't hitting your component
// via the listener
pane.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
System.out.println("Motion in the pane...");
Component left = pane.getLeftComponent();
if (left.getBounds().contains(e.getPoint())) {
pane.setDividerLocation(100);
} else {
pane.setDividerLocation(20);
}
}
});
pane.setDividerLocation(20);
add(pane);
}
protected JPanel makePane(Color background) {
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
};
pane.setLayout(new GridBagLayout());
pane.add(new JButton("..."));
pane.setBackground(background);
pane.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("...");
}
});
return pane;
}
}
}
I found a cool way in another question to create a JButton whose actions are written and viewed in an easy way:
public JButton makeToolbarButton(String title, String actionCommand) {
JButton button = new JButton(title);
button.setActionCommand(actionCommand);
button.addActionListener(this);
return button;
}
The class this method is in implements ActionListener, and the buttons commands are assigned by:
public void actionPerformed(ActionEvent e) {
int action = Integer.parseInt(e.getActionCommand());
switch(action) {
case 1:
System.out.println("This button pressed.");
break;
}
}
And the buttons are made by:
JButton button1 = makeToolbarButton("Button 1", "1");
So my question is: can I add KeyStrokes to a button by this method? I tried something like this (inside of the makeToolbarButton method):
button.getInputMap().put(KeyStroke.getKeyStroke("B"), "button_pressed");
button.getActionMap().put("button_pressed", button.getAction());
But I figure this doesn't work because the action command isn't actually assigning an action to a specific button. Is there a way to add something to the makeToolbarButton() method and a parameter for the KeyStroke to accomplish this?
I think you're missing the point of the Action API. A Action is intended to provide a single, self contained, unit of work. This means that the actionCommand really isn't required, as when the actionListener event is triggered, you know exactly the context in which it's been executed
I'd also avoid using KeyStroke.getKeyStroke(String), as the text is a verbose description of what you want to do (ie pressed B or something, but needless to say, it's a pain to get right)
So, the following demonstrates how you might use Actions and assign them to a button AND a key binding
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ActionTest {
public static void main(String[] args) {
new ActionTest();
}
public ActionTest() {
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() {
add(createButton(new ActionOne(), KeyStroke.getKeyStroke(KeyEvent.VK_1, 0)));
add(createButton(new ActionTwo(), KeyStroke.getKeyStroke(KeyEvent.VK_2, 0)));
}
public JButton createButton(Action action, KeyStroke keyStroke) {
JButton btn = new JButton(action);
btn.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, "button_pressed");
btn.getActionMap().put("button_pressed", action);
return btn;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.dispose();
}
}
public class ActionOne extends AbstractAction {
public ActionOne() {
putValue(NAME, "1");
putValue(Action.ACTION_COMMAND_KEY, "Action.one");
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
public class ActionTwo extends AbstractAction {
public ActionTwo() {
putValue(NAME, "2");
putValue(Action.ACTION_COMMAND_KEY, "Action.two");
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
}
See How to Use Actions for more details
I've got a viewport, and I've attached a change listener to it. Whenever I scroll through my viewport, my change listener gets called about four times. I'm not sure how to avoid this; I only want the call to happen once?
There's no way around this, JViewport will fire several stateChanged events because it's providing notification about changes to a number of properties...
From the JavaDocs...
Adds a ChangeListener to the list that is notified each
time the view's size, position, or the viewport's extent size has
changed.
At this point, it's kind of hard to know what to suggest as we don't know what it is you are trying to achieve, however, if you have to use a ChangeListener, you could set up a coalescing mechanism. That is, rather then responding to each event, you basically wait until a long enough delay has occurred between events before responding to it...
For example...
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
Basically, this is a ChangeListener implementation that uses a non-repeating javax.swing.Timer with a short delay. Each time stateChanged is called, the timer is restart. Finally, when the timer is allowed to "tick", it calls stableStateChanged indicating that enough time has passed since the last event was raised.
This assumes that you don't so much care about what caused the event, only that the event occured...
A runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestViewport {
public static void main(String[] args) {
new TestViewport();
}
public TestViewport() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
};
JScrollPane sp = new JScrollPane(pane);
sp.getViewport().addChangeListener(new DelayedChangeHandler());
sp.getViewport().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
}
You can try to use AdjustmentListener for gettign scroll event once, try next:
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.io.UnsupportedEncodingException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Example {
public static void main(String[] args) throws UnsupportedEncodingException {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JScrollPane pane = new JScrollPane(new JTextArea());
pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if(e.getValueIsAdjusting()){
return;
}
System.out.println("vertical scrolled");
System.out.println("bar value = " + e.getValue());
}
});
frame.setContentPane(pane);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Here is another example.
When it is clicked on JLabel, I want to understand if the click was on "Icon part", or "Text part" of the JLabel, so that different action can be taken. Is there a clever way to do that? Or just I have to solve it relatively with the coordinates of the icon and text?
+1 to #aymeric comment.
What about having two different JLabels
However I do understand why you might be hesitating
negative: requires maintenance of 2 labels.
My clever (:P) solution to this is create your own abstract component - which accepts icon and text as parameters for constructor - by extending JPanel and than adding 2 JLabels to the JPanel, each label has its on MouseAdapter which calls abstract method xxxClicked() (thus any implementing class must override these methods).
Here is an example I made:
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
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) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ImageIcon ii = null;
try {
//I dont remmend getScaledInstance just used it for speed of code writing
ii = new ImageIcon(ImageIO.read(new URL("http://www.candonetworking.com/java.gif")).getScaledInstance(32, 32, Image.SCALE_SMOOTH));
} catch (Exception ex) {
ex.printStackTrace();
}
MyLabel ml = new MyLabel(ii, "Something") {
#Override
void iconClicked() {
System.out.println("Icon clicked");
}
#Override
void textClicked() {
System.out.println("Text clicked");
}
};
frame.add(ml);
frame.pack();
frame.setVisible(true);
}
});
}
}
abstract class MyLabel extends JPanel {
JLabel iconLabel;
JLabel textLabel;
MouseAdapter iconMA;
MouseAdapter textMA;
public MyLabel(ImageIcon icon, String text) {
iconLabel = new JLabel(icon);
textLabel = new JLabel(text);
iconMA = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
iconClicked();
}
};
textMA = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
textClicked();
}
};
iconLabel.addMouseListener(iconMA);
textLabel.addMouseListener(textMA);
add(iconLabel);
add(textLabel);
}
abstract void iconClicked();
abstract void textClicked();
public JLabel getIconLabel() {
return iconLabel;
}
public JLabel getTextLabel() {
return textLabel;
}
}