I have a main window:
public class MainPanel extends JFrame implements MouseListener {
public MainPanel() {
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
addMouseListener(this);
ChildPanel child = new ChildPanel();
add(child);
JPanel spacer = new JPanel();
spacer.setPreferredSize(new Dimension(50, 50));
add(spacer);
pack();
setLocationRelativeTo(null);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse click event on MainPanel");
}
}
And a child JPanel:
public class ChildPanel extends JPanel implements MouseListener {
public ChildPanel() {
setBackground(Color.RED);
setPreferredSize(new Dimension(200, 200));
//addMouseListener(this);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse click event on ChildPanel");
}
}
With the call to addMouseListener commented out in the child panel, the parent receives click events when I click anywhere in the window, including on the child. If I uncomment that call and click on the child panel, only the child receives the click event and it doesn't propagate to the parent.
How do I stop the event from being consumed by the child?
In Swing, you generally want the clicked component to respond; but you can forward the mouse event to the parent, as shown below. Here's a related example.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see https://stackoverflow.com/questions/3605086 */
public class ParentPanel extends JPanel {
public ParentPanel() {
this.setPreferredSize(new Dimension(640, 480));
this.setBackground(Color.cyan);
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse clicked in parent panel.");
}
});
JPanel child = new JPanel();
child.setPreferredSize(new Dimension(320, 240));
child.setBackground(Color.blue);
child.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse clicked in child panel.");
ParentPanel.this.processMouseEvent(e);
}
});
this.add(child);
}
private void display() {
JFrame f = new JFrame("MouseEventTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ParentPanel().display();
}
});
}
}
I don't think you can. I believe it's a Swing design principle that only one component receives an event.
You can get the behavior you want, however, but pass the JFrame to the ChildPanel and calling its mouseClicked(MouseEvent) or whatever method you want. Or just get the parent component.
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse click event on ChildPanel");
this.frame.mouseClicked(e);
getParent().mouseClicked(e);
}
Related
Is there a way to retain the set mouse cursor even the JDialog is not in focus?
I have the following SSCCE:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test extends JDialog {
public Test() {
super((Frame) null, true);
setLayout(new BorderLayout());
setModalityType(Dialog.ModalityType.MODELESS);
setType(Window.Type.UTILITY);
setUndecorated(true);
setAlwaysOnTop(true);
setBackground(Color.BLACK);
setSize(300, 300);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
getContentPane().setBackground(Color.BLACK);
getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
setLocationRelativeTo(null);
var label = new JLabel("Focused");
label.setForeground(Color.WHITE);
addWindowFocusListener(new WindowAdapter() {
#Override
public void windowGainedFocus(WindowEvent e) {
label.setText("Focused");
}
#Override
public void windowLostFocus(WindowEvent e) {
label.setText("Not Focused");
}
});
add(label, BorderLayout.CENTER);
}
public static void main(String[] args) {
// Create the GUI on the event-dispatching thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Test sw = new Test();
// Display the window.
sw.setVisible(true);
}
});
}
}
Here is what is happening:
My goal is for the resize cursor to be retained on mouse hover even if the JDialog is not focused.
Tried on AdoptOpenJDK 11 & 15 and Liberica JDK 11 on MacOS Catalina 10.15.4
Add a mouse listener for your dialog. By using the MouseAdapter implementation, you can override only the methods that you want.
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
super.mouseEntered(e);
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
}
#Override
public void mouseExited(MouseEvent e) {
super.mouseExited(e);
setCursor(Cursor.getDefaultCursor());
}
});
I have a menu bar with a search-bar that is implemented with a JTextField like this:
public class Ui_Frame {
private static JFrame f;
private static DrawPanel drawPanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(Ui_Frame::createAndShowGUI);
}
private static void createAndShowGUI() {
f = new JFrame("Frame");
drawPanel = new DrawPanel(); //A class that extends JPanel where I draw
JMenuBar menubar = new JMenuBar();
JMenu j_menu_data = new JMenu("Data");
JTextField j_menu_searchfield = new JTextField();
j_menu_searchfield.setSize(new Dimension(100,20));
menubar.add(j_menu_data);
menubar.add(j_menu_searchfield);
f.setJMenuBar(menubar);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(drawPanel);
f.pack();
f.setVisible(true);
}
}
I have KeyListeners for the DrawPanel class, these work just fine. The problem is that when I add the search bar JTextField to the menu bar as above, everything I write is being written to the text field and my key listeners do not trigger. I cannot "get out" of the text field, so If I click inside the area where I am drawing all the keys I press are still put into the text field.
I have tried getFocus() for the DrawPanel but to no avail.
How to solve this?
EDIT:
DrawPanel class so you have all the classes you need to run it:
public class DrawPanel extends JPanel {
public DrawPanel() {
addKeyListener(new CustomKeyListener());
this.setBackground(new Color(220,220,220));
setBorder(BorderFactory.createLineBorder(Color.black));
setFocusable(true);
setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
}
class CustomKeyListener implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_SPACE) {
System.out.println("Pressed SPACE");
}
}
#Override
public void keyPressed(KeyEvent e) { }
#Override
public void keyReleased(KeyEvent e) { }
}
}
When you launch your Swing application, the JTextField initially has the keyboard focus. You know this because you see the cursor flashing inside the JTextField.
Clicking on the DrawPanel with the mouse does not transfer the keyboard focus to the DrawPanel. You know this because after clicking the mouse inside the DrawPanel, the cursor is still flashing inside the JTextField.
You can transfer the keyboard focus from the JTextField to the DrawPanel by hitting the TAB key on the keyboard since that is the default focus traversal key. You know that the JTextField no longer has keyboard focus because there is no flashing cursor in it.
If you really want the DrawPanel to gain keyboard focus by clicking the mouse in it, you can add a MouseListener to DrawPanel, as demonstrated in the below code which is essentially your code (for class DrawPanel) with a MouseListener added.
Note that I did not change class Ui_Frame.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
public class DrawPanel extends JPanel implements MouseListener {
public DrawPanel() {
addKeyListener(new CustomKeyListener());
addMouseListener(this);
this.setBackground(new Color(220,220,220));
setBorder(BorderFactory.createLineBorder(Color.black));
setFocusable(true);
setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
}
class CustomKeyListener implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_SPACE) {
System.out.println("Pressed SPACE");
}
}
#Override
public void keyPressed(KeyEvent e) { }
#Override
public void keyReleased(KeyEvent e) { }
}
#Override
public void mouseClicked(MouseEvent mouseEvent) {
System.out.println(this.requestFocusInWindow());
}
#Override
public void mousePressed(MouseEvent mouseEvent) {
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
}
#Override
public void mouseEntered(MouseEvent mouseEvent) {
}
#Override
public void mouseExited(MouseEvent mouseEvent) {
}
}
Note that method requestFocusInWindow() returns true if it succeeds.
I am trying to figure out how can we exit the application with button click.
The problem i faced which makes me unable to exit the application is because i am using "extend JFRame" from the main class.
For an example,
app.class
public class app{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame frame = new MainFrame("Exercise one");
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
.. .. ..
frame.setVisible(true);
})
}
}
MainFrame.java
public class MainFrame extends JFrame(){
public MainFrame(String title){
super(title)
//set layout manager
setLayout(new BorderLayout());
//swing components
JButton exit = new JButton("Exit");
//add container
Container container = getContentPane();
container.add(exit);
//create actionlist logic
exit.addActionListener(new ActionListener()){
#Override
public void actionPerformed(ActionEvent arg0){
// on click , this logic will end the application
}
}
}
}
I fully understand how i can cancel the application from app class.But in scenarios where i want to cancel the application from MainFrame.Can it be done ?
Thank you in advance.
The defaultCloseOperation is only processed by the frame when it encounters a WINDOW_CLOSING event, neither setVisible or dispose trigger this event, which means the the defaultCloseOperation won't be processed
The only way to ensure that this operation is triggered is to manually dispatch a WINDOW_CLOSING event
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
One of the main reasons for wanting to follow this path is that it ensures the application is following the configured defaultCloseOperation and making up it's own mind (like calling System.exit manually)
The following demonstrates hiding, disposing and dispatching approaches. Only the dispatch approach will close the window and terminate the JVM
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.AbstractAction;
import static javax.swing.Action.NAME;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
frame.add(new JButton(new HideAction(frame)), gbc);
frame.add(new JButton(new DisposeAction(frame)), gbc);
frame.add(new JButton(new DispatchAction(frame)), gbc);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.out.println("Closing");
}
#Override
public void windowClosed(WindowEvent e) {
System.out.println("Closed");
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class HideAction extends AbstractAction {
private JFrame frame;
public HideAction(JFrame frame) {
this.frame = frame;
putValue(NAME, "Hide");
}
#Override
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
}
}
public class DisposeAction extends AbstractAction {
private JFrame frame;
public DisposeAction(JFrame frame) {
this.frame = frame;
putValue(NAME, "Dispose");
}
#Override
public void actionPerformed(ActionEvent e) {
frame.dispose();
}
}
public class DispatchAction extends AbstractAction {
private JFrame frame;
public DispatchAction(JFrame frame) {
this.frame = frame;
putValue(NAME, "Dispatch");
}
#Override
public void actionPerformed(ActionEvent e) {
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
}
}
}
If I run this in my IDE, unless I use the dispatch option, the JVM is left running and I have to terminate the session to close it fully.
I also noted that calling dispose only triggers the WINDOW_CLOSED event, while the dispatch method will trigger the WINDOW_CLOSING event
Problem solved.
Credits to #XtremeBaumer.
Basically, when using "classname" extend JFRame.
We can input dispose() which will kill the application completely. This will causes the JFrame window to be destroyed and cleaned up by the operating system. :)
Indeed, dispose() is the right solution. I also suggest to add a pack() call so the UI shows up properly:
public class MainFrame extends JFrame {
public MainFrame(String title) {
super(title);
...
// create actionlist logic
exit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("--> closing app programmatically");
MainFrame.this.dispose();
}
});
this.pack();
}
}
You can test the events with a WindowListener on the MainFrame. They are invoked as if the user pressed the close button:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new MainFrame("Exercise one");
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
// .. .. ..
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.out.println("--> closing...");
}
public void windowClosed(WindowEvent e) {
System.out.println("--> closed...");
}
});
}
});
}
Is it possible to listen for mouse released event on the component on which it was not pressed?
I know that when mouse is released MouseListener.mouseReleased()is invoked on the listeners for that component when mouse press originated even if the cursor is above other component.
How to inform a component or its listeners that the mouse was over it and it was released?
If you add your MouseListener to the container that holds your components of interest, you can find out which component the mouse is over on press or drag. For instance in the code below, I've added my MouseAdapter (a combination MouseListener, MouseMotionListener, and MouseWheelListener) to the containing JPanel, and after getting the location of the mouse event on the container, I call getComponentAt(Point p) on my container to get the child component that the mouse was over:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestMouseRelease extends JPanel {
private String[] panelNames = { "Panel A", "Panel B" };
public TestMouseRelease() {
setLayout(new GridLayout(1, 0));
MouseAdapter mAdapter = new MyMouseAdapter();
addMouseListener(mAdapter);
addMouseMotionListener(mAdapter);
for (String panelName : panelNames) {
JPanel panel = new JPanel();
panel.setName(panelName);
// panel.addMouseListener(mAdapter);
// panel.addMouseMotionListener(mAdapter);
panel.setBorder(BorderFactory.createTitledBorder(panelName));
panel.add(Box.createRigidArea(new Dimension(300, 300)));
add(panel);
}
}
private class MyMouseAdapter extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
displayInfo(e, "mousePressed");
}
#Override
public void mouseReleased(MouseEvent e) {
displayInfo(e, "mouseReleased");
}
#Override
public void mouseDragged(MouseEvent e) {
displayInfo(e, "mouseDragged");
}
private void displayInfo(MouseEvent e, String info) {
JComponent comp = (JComponent) e.getSource();
Component childComp = comp.getComponentAt(e.getPoint());
if (childComp != null) {
String name = childComp.getName();
System.out.println(name + ": " + info);
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TestMouseRelease");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestMouseRelease());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Yeah, I handled something similar in my project. I use getBounds() on all component in the container and check if they contain the x,y coordinates of your mouse. Below is my code:
Component[] components = jPanel1.getComponents();
for (Component c : components) {
if (c.getBounds().contains(ev.getXOnScreen(), ev.getYOnScreen())) {
System.out.println(c.getClass());
y = ch.getPosY();
x = ch.getPosX();
}
}
I've been trying to implement mouse motion event dispatches but I continue to get a stack overflow error. It works for the mouseMoved(MouseEvent e) method but not for mouseDragged(MouseEvent e). Does anybody have a clue as to why? Are there any solutions?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test extends JFrame {
public Test() {
setLayout(null);
setSize(500,500);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
System.err.println("moved outside");
}
public void mouseDragged(MouseEvent e) {
System.err.println("dragged outside");
}
});
JPanel inside = new JPanel();
inside.setLocation(0, 0);
inside.setSize(100, 100);
inside.setBackground(Color.RED);
inside.addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
System.out.println("dragged inside");
//The error occurs here when I begin dragging
//here and continue dragging to any other location.
Test.this.dispatchEvent(e);
}
public void mouseMoved(MouseEvent e) {
System.out.println("moved inside");
Test.this.dispatchEvent(e);
}
});
add(inside);
}
public static void main(String[] args) {
Test client = new Test();
}
}
My actual project uses many inside components and my goal is to have each component implement their own mouse press/click/release actions and have the frame handle mouse motions and dragging that influences all of the components.
Here is a similar code that does work for both mouse motion methods.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test2 {
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final JComponent outside = new JPanel();
JComponent inside = new JPanel();
inside.setBackground(Color.red);
inside.setPreferredSize(new Dimension(200, 200));
inside.addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
System.out.println("dragged inside");
outside.dispatchEvent(e);
}
public void mouseMoved(MouseEvent e) {
System.out.println("moved inside");
outside.dispatchEvent(e);
}
});
outside.add(inside);
outside.setPreferredSize(new Dimension(300, 300));
outside.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
System.err.println("moved outside");
}
public void mouseDragged(MouseEvent e) {
System.err.println("dragged outside");
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(outside);
frame.pack();
frame.setVisible(true);
}
});
}
}
Help is appreciated.
Your MouseMotionListener in the inside is generating a new event. That event will be caught again by the very same MouseMotionListener, creating an endless loop. Since you are creating an event when the previous one is still unfinished, they will stack up until a StackOverflowError explodes your application.
Your second code does not have this problem because the inside delegates to the outside and it finishes there.