How can I switch focus to a canvas? - java

I have this class and I want to switch focus to the Game class right after it was invoked. I might've not understand the purpose of focus but when I press start I have to click on the game canvas itself so I can use the keyboard . In other words: How can I make it so I don't have to click on it to use the keyboard?
package com.runner.panels;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
import com.runner.main.Game;
import com.runner.main.Main;
public class PlayPanel extends JPanel{
private static final long serialVersionUID = 1L;
public PlayPanel(){
//setting the layout of the playpanel to null
setLayout(null);
//setting up the info panel : high score, meters ran, pause button etc...
JPanel info = new JPanel();
info.setBounds(0,0,1200,50);
add(info);
//back button
JButton back = new JButton("Back");
info.add(back);
Game game = new Game();
game.setBounds(0,50,1200,521);
game.setBackground(Color.black);
add(game);
back.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout) Main.mainp.getLayout();
cl.show(Main.mainp, "Menu");
}
});
}
}

Off topic: (kinda)
The fact that you are doing Main.mainp.getLayout();, calling the panel statically tells me you have poor design and should be looking into other options like an Model-view-controller pattern, an Observer pattern, or at the very least passing a reference of of the Main to the panel, instead of using static objects/calls.
Back on topic
Sounds like a common KeyListener problem. Generally to gain focus you call requestFocusInWindow(). But you still have to worry about other components stealing the focus way after the fact.
I would instead recommend using Key Bindings instead of KeyListener. You have more control over the focus. For instance, by using
InputMap im = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke("SPACE"), "hitSpace");
panel.getActionMap().put("hitSpace", new AbstractAction(){
public void actionPerformed(ActionEvent e) {
// do something.
}
});
The panel will have immediate access to the action once you show it from the CardLayout. If you happen to use any other components that would steal the focus away from the panel, the action is still accessible because of the WHEN_IN_FOCUSED_WINDOW input map
Here's a simple example. Type A if it is on panel A, you will see it print. If you type B, it won't print because panel A is in the window. Also if you try and press the button in the panel to steal the focus, you can still type and it will still print. Same goes for panel B
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class Main {
CardLayout layout = new CardLayout();
JPanel panel = new JPanel(layout);
JPanel p1 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
};
JPanel p2 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
};
JButton b1 = new JButton("panelA");
JButton b2 = new JButton("panelB");
public Main() {
addKeyBind(p1, "pressA", "A");
addKeyBind(p2, "pressB", "B");
p1.add(new JButton("Button for Panel A"));
p2.add(new JButton("Button for Panel B"));
panel.add(p1, "panelA");
panel.add(p2, "panelB");
b1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
show("panelA");
}
});
b2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
show("panelB");
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(b1);
buttonPanel.add(b2);
JFrame frame = new JFrame();
frame.add(panel);
frame.add(buttonPanel, BorderLayout.PAGE_END);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void show(String panelName) {
layout.show(panel, panelName);
}
private void addKeyBind(JComponent comp, String name, final String stroke) {
InputMap im = comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(stroke), name);
comp.getActionMap().put(name, new AbstractAction(){
public void actionPerformed(ActionEvent e) {
System.out.println(stroke + " pressed");
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new Main();
}
});
}
}
Take some time to go over the link I gave you for Key Bindings to learn more.
Back off-topic
Take a look at #AndrewThompson's comment about the null layouts. Learn how to use the LayoutManagers

I believe your problem would be fixed if you add
setFocusable(true);
to your PlayPanel constructor (works for me).
Also, if you want a specific Panel in your GUI to have focus when you start your application, follow the link in the comment of "user3218114", as this will explain how to implement this functionality with Listeners.
Good luck!

You don't need to do anything. The container should be focusable for that.
Here is a code to demonstrate.
package one;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JFrame;
public class PlayPanel extends Canvas {
public static void main(String... args) {
PlayPanel p = new PlayPanel();
p.addFocusListener(new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
p.msg = "Focus gained";
p.repaint();
}
#Override
public void focusLost(FocusEvent e) {
p.msg = "Focus Lost";
p.repaint();
}
});
p.setBackground(Color.GRAY);
JFrame f = new JFrame();
f.add(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 200);
f.setLocation(300, 300);
f.setVisible(true);
}
String msg = "NO FOCUS";
public void paint(Graphics g) {
g.drawString(msg, 50, 50);
}
}

Related

Replace button with a filled circle?

How to replace button shown on code with a filled circled.The circle (e.g. red) must move away from cursor just as same it was with button.
Is it another possible way to implement this type of task?
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class NewSwingTemplate {
private Random d = new Random();
public NewSwingTemplate() {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.add(createButton());
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JButton createButton() {
final JButton button = new JButton("Button");
button.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
int a = d.nextInt(200);
int b = d.nextInt(200);
button.setLocation(a, b);
}
});
return button;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new NewSwingTemplate();
}
});
}
}
Create your own button.
That is, you derive from JComponent and overwrite the paint method. It needs to render your circle - the way you want it. Add some keyboard/mouse handling and maybe an ActionListener to the circle can react similar to a JButton.
Finally you use it just like a JButton - I guess you have the code that makes your button move somewhere already.

How to leave MouseListener on ChildComponent but correctly keep track of mouse enter and exit on parent?

I have a JPanel with a CardLayout and two cards. I want the layout to flip the cards each time the mouse enters or exits the panel.
This works fine unless one of the cards is a component that listens for mouse events. Consider the following example:
JPanel cardLayoutPanel = new JPanel(layout);
JButton button = new JButton("listening!");
JLabel label = new JLabel("not listening.");
cardLayoutPanel.add(button);
cardLayoutPanel.add(label);
layout.last(cardLayoutPanel);
cardLayoutPanel.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("entered!");
layout.next(cardLayoutPanel);
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("exited!");
layout.next(cardLayoutPanel);
}
});
The problem is that, if the MouseEvent is catched by a child component, it is not processed by the parent as a read in many SO-questions related to this topic.
I tried different things like redispatching the event or just ignoring the exit event if the event if the event coordinates are still in the panel.
The first solution does not work at all, the second neither since then the mouse entered event does not occur anymore.
How can i solve this?
The only solution that I see right now would be to completely remove the listener from the child component and perform the collision and event handling on my own in the parents mouse mouse listener, but this would be a mess and not the intended way to do this, i guess.
Any help or ideas appreciated.
EDIT: here is a complete short compilable example:
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class CardLayoutTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel content = new JPanel();
content.setBorder(new EmptyBorder(new Insets(50, 50, 50, 50)));
CardLayout layout = new CardLayout();
JPanel cardLayoutPanel = new JPanel(layout);
JButton button = new JButton("listening!");
JLabel label = new JLabel("not listening.");
cardLayoutPanel.add(button);
cardLayoutPanel.add(label);
layout.last(cardLayoutPanel);
cardLayoutPanel.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("entered!");
layout.next(cardLayoutPanel);
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("exited!");
layout.next(cardLayoutPanel);
}
});
content.add(cardLayoutPanel);
frame.add(content);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
I modified your example slightly and got it to work. The key (at least in this example), was to have the button share the same mouseListener with the cardLayoutPanel. I do not know if this is a universal solution but it does work here. I also made the following changes:
added a private innner class for the mouseListener.
Increased the size of the cardLayoutPanel.
Added color borders to the Label and Button.
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class CardLayoutTest {
JButton button = new JButton("listening!");
JPanel cardLayoutPanel = new JPanel();
CardLayout layout = new CardLayout();
public static void main(String[] args) {
new CardLayoutTest().start();
}
public void start() {
JFrame frame = new JFrame();
JPanel content = new JPanel();
content.setBorder(new EmptyBorder(
new Insets(50, 50, 50, 50)));
cardLayoutPanel.setLayout(layout);
cardLayoutPanel
.setPreferredSize(new Dimension(200, 200));
button.addActionListener(
ae -> System.out.println("IT WORKS!"));
button.removeMouseMotionListener(
button.getMouseMotionListeners()[0]);
button.removeMouseListener(
button.getMouseListeners()[0]);
MyMouseListener ml = new MyMouseListener();
button.addMouseListener(ml);
button.setBorder(new LineBorder(Color.red, 2));
JLabel label = new JLabel("not listening.");
label.setBorder(new LineBorder(Color.blue, 2));
cardLayoutPanel.add(button);
cardLayoutPanel.add(label);
layout.last(cardLayoutPanel);
cardLayoutPanel.addMouseListener(ml);
content.add(cardLayoutPanel);
frame.add(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("entered!");
layout.next(cardLayoutPanel);
}
public void mouseClicked(MouseEvent e) {
if (e.getSource() instanceof JButton) {
JButton b = (JButton)(e.getSource());
b.doClick();
}
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("exited!");
layout.next(cardLayoutPanel);
}
}
}
It is not quiet a good solution but i managed to achieve what i want by calling the flip event when entering the parent component. This works since i have enough space between my panels with this property.
I did not, however, managed to achieve this effect with glass panes as suggested in a comment. Maybe I was doing it wrong, but the events did not arrive at the components properly, even some instanceof checks failed out of nowhere.
If someone knows how to do this properly I would be glad to see.

How do I get focus for a keypress in a CardLayout?

I had a CardLayout example working correctly with a button, then tried to convert it to work with keypress. I think the problem is that I don't have focus, but I can't set the focus to frame or panel successfully. Thanks!
I tried requestFocusInWindow from the frame and from the first panel shown, and that didn't help. I asked frame.getFocusOwner() and it returned null.
I thought that CardLayout would give the focus to the top element automatically, but while that worked when I had a button, it is not working now.
public class MyCardLayoutExample3 {
public static void main(String[] args){
MyCardLayoutExample3 game = new MyCardLayoutExample3();
game.display();
}
void display() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(300, 200);
CardLayout cardLayout = new CardLayout();
frame.getContentPane().setLayout(cardLayout);
MyGamePanel3 mgp3 = new MyGamePanel3("minigame A", Color.red);
frame.getContentPane().add(mgp3);
frame.getContentPane().add(new MyGamePanel3("minigame B", Color.green));
frame.getContentPane().add(new MyGamePanel3("minigame C", Color.blue));
frame.setVisible(true);
System.out.println("owner: " + frame.getFocusOwner()); //this prints null
}
}
class MyGamePanel3 extends JPanel implements KeyListener{
MyGamePanel3(String text, Color bg){
JLabel textLabel = new JLabel(text);
this.setBackground(bg);
this.add(textLabel);
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed worked");
}
#Override
public void keyReleased(KeyEvent e) {}
}
Changing to key bindings made the example work easily, thanks Abra. I never got the keyListener to work, despite trying the links above and many other links.
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
class MyGamePanel extends JPanel{
MyGamePanel(ActionListener alNext, String text, Color bg){
JButton buttonNext = new JButton("next");
buttonNext.addActionListener(alNext);
JLabel textLabel = new JLabel(text);
this.setBackground(bg);
this.add(textLabel);
this.add(buttonNext);
}
}
public class MyCardLayoutKeyBindingExample {
public static void main(String[] args){
MyCardLayoutKeyBindingExample game = new MyCardLayoutKeyBindingExample();
game.display();
}
void display() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(300, 200);
CardLayout cardLayout = new CardLayout();
//frame.getContentPane().setLayout(cardLayout);
JPanel mainPanel = new JPanel(cardLayout);
frame.add(mainPanel);
ActionListener al1 = e -> cardLayout.next(mainPanel);
mainPanel.add(new MyGamePanel(al1, "minigame A", Color.red));
mainPanel.add(new MyGamePanel(al1, "minigame B", Color.green));
mainPanel.add(new MyGamePanel(al1, "minigame C", Color.blue));
mainPanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
Action kp = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
System.out.println("key pressed");
}
};
mainPanel.getActionMap().put("space", kp);
frame.setVisible(true);
}
}

added glasspane makes calls to getMousePosition() for any component return null

I have written a custom glasspane component and set that to my JFrame and JDialog classes.
I don't intercept mouse events in the custom glasspane, as I don't need to and also because there are a number of components on the GUI e.g. trees, popups etc which makes it complicated trying to intercept and forward mouse events.
Everything works fine as is - except that whenever I call getMousePosition() on any of the other components (e.g. JPanel) in the frame/dialog it returns null.
On my JFrame and JDialog classes, I have set the glasspane to disabled (but visible) and it works for the most part. What do I need to do so the getMousePosition() method returns the correct value without having to add mouse event listeners to the glasspane component.
Sample code that demonstrates the problem.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GlassPaneExample
{
private JPanel panel;
private JButton btn;
private JLabel response;
private int counter = 0;
GlassPaneExample()
{
buildUI();
}
private void buildUI()
{
JFrame frame = new JFrame("GlassPane Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getPanel());
frame.setGlassPane(new MyGlassPane());
frame.getGlassPane().setVisible(true);
frame.getGlassPane().setEnabled(false);
frame.setPreferredSize(new Dimension(200, 220));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
JPanel getPanel()
{
panel = new JPanel(new BorderLayout());
panel.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e)
{
}
#Override
public void mouseMoved(MouseEvent e)
{
System.out.println("mousePosition : " + panel.getMousePosition());
System.out.println("mousePosition(true) : " + panel.getMousePosition(true));
}
});
btn = new JButton("Click here...");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
response.setText("Button click number " + getButtonClickCount());
}
});
response = new JLabel();
response.setToolTipText("Response label");
panel.add(btn, BorderLayout.NORTH);
panel.add(response, BorderLayout.SOUTH);
return panel;
}
private int getButtonClickCount()
{
return counter++;
}
public static void main(String[] arg)
{
new GlassPaneExample();
}
class MyGlassPane extends JComponent
{
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(10,40,120,120);
}
}
}
If you comment out the 3 lines to do with adding the glasspane to the Frame, then the mouse position point is printed out correctly.
This is a simple example to illustrate the problem, and I've said above, I dont want to add mouse listeners to the custom glasspane component as it introduces other complications.

stopCellEditing on JDialog

My question is similar to this one: JTable Cell Update doesn't work.
However, I am using JDialog instead of a JTable specified in above link. I have a custom class which extends JDialog. I use JEditorPane as a text-component in that dialog and create simple OK, Cancel buttons. Now the problem is, when I enter something in the JEdiorPane and presses OK button, the value is not applied to the text-component until I move the focus out of a JDialog or hit tab/ENTER.
I want the container to be notified that I am done with editing as soon as I press the OK button. In short I want to explicitly have a feature similar to stopCellEditing(). How can I do that?
See this example which seems to work correctly and does the same thing as you described:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class TestEditorPaneDialog {
public void init() {
final JFrame frame = new JFrame();
JButton clickMe = new JButton("Click me");
clickMe.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
showDialog(frame);
}
});
frame.add(clickMe);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
showDialog(frame);
}
private void showDialog(final JFrame frame) {
final JDialog dialog = new JDialog(frame, true);
final JEditorPane pane = new JEditorPane();
pane.setText("Type something here");
JPanel south = new JPanel();
JPanel buttons = new JPanel(new GridLayout(1, 0, 10, 10));
JButton ok = new JButton("OK");
ok.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dialog.dispose();
JOptionPane.showMessageDialog(frame, "You have typed in: " + pane.getText());
}
});
JButton cancel = new JButton("Cancel");
cancel.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dialog.dispose();
}
});
buttons.add(ok);
buttons.add(cancel);
south.add(buttons);
dialog.add(new JScrollPane(pane));
dialog.add(south, BorderLayout.SOUTH);
dialog.setSize(250, 150);
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestEditorPaneDialog().init();
}
});
}
}

Categories