I have Java Swing application ToolTipMouseTest
The critical line is label.setToolTipText("label" + i);. Once it is commented out very click on a label produces 2 mousePressed in console. With this line enabled click on labels would produce nothing.
Is this expected behaviour or a bug? My goal is to show tooltips without disabling MouseListener from working.
Almost SSCCE, but without imports:
public class ToolTipMouseTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ToolTipMouseTest();
}
});
}
public ToolTipMouseTest() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JLayeredPane lpane = new JLayeredPane() {
#Override
public Dimension getPreferredSize() {
return new Dimension(600,400);
}
};
MouseAdapter1 mouseAdapter1 = new MouseAdapter1();
lpane.addMouseListener(mouseAdapter1);
frame.add(lpane);
JPanel panel1 = new JPanel();
panel1.setSize(new Dimension(600, 400));
panel1.setOpaque(false);
lpane.add(panel1, JLayeredPane.PALETTE_LAYER);
JPanel panel2 = new JPanel();
for (int i = 0; i < 5; i++) {
JLabel label = new JLabel("Label " + i);
panel2.add(label);
label.setToolTipText("label" + i); //HERE!!
}
JScrollPane spane = new JScrollPane(panel2) {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
};
MouseAdapter2 mouseAdapter2 = new MouseAdapter2();
spane.addMouseListener(mouseAdapter2);
panel1.add(spane);
frame.pack();
frame.setVisible(true);
}
private class MouseAdapter1 extends MouseAdapter {
#Override
public void mousePressed (MouseEvent me) {
System.out.println("1 mousePressed");
}
}
private class MouseAdapter2 extends MouseAdapter {
#Override
public void mousePressed (MouseEvent me) {
System.out.println("2 mousePressed");
}
}
}
It is working as intended. Let me explain why.
When a component has no listener, the mouse events will be propagated.
When any component has at least one MouseListener set on it - it will consume any mouse enter/exit/click/press/release events from going down in the components hierarchy.
It's the same for any listener such as MouseMotionListener with
mouse dragged/moved.
When you are adding a tooltip to a component (JLabel in your case), the component automatically receive a new MouseListener and a MouseMotionListener from ToolTipManager. The registerComponent method from ToolTipManager class do this (it's invoked by setToolTipText) :
public void registerComponent(JComponent component) {
component.removeMouseListener(this);
component.addMouseListener(this);
component.removeMouseMotionListener(moveBeforeEnterListener);
component.addMouseMotionListener(moveBeforeEnterListener);
component.removeKeyListener(accessibilityKeyListener);
component.addKeyListener(accessibilityKeyListener);
}
In your case - JLabels are consuming mouse events and mouse motion events, and as such prevents from propagating the events to the JLayeredPane because ToolTipManager listener added itself when the tooltip is set (setToolTipText) on the component.
In order to work around this, register a listener that will pass events down. You can add that listener to every component with a tooltip that should pass mouse events down (e.g to a JLayeredPane, a JScrollPane, etc).
Here is a small example of how that could be done:
var destinationComponent = // the JLayeredPane, JScrollPane, etc with mouse listeners
componentWithToolTip.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent event) {
destinationComponent.dispatchEvent(
SwingUtilities.convertMouseEvent(
event.getComponent(), // the component with the tooltip
event,
destinationComponent
)
);
}
// implements other mouse* handlers as required.
});
In that setup componentWithToolTip will have 2 listeners, the one from the ToolTipManager and the propagating one. When componentWithToolTip all its listeners will be triggered, and the propagating listener will dispatch to the declared destination component destinationComponent. So that destinationComponent listeners receive the mouse events as well.
Related
There are a transparent JPanel on top of the screen, and a background JPanel and a JButton added to it(All are visible and have mouse listeners)
After handling mouse events in the transparent panel's listeners, they should be dispatched to the next deeper component(i.e. background panel or its button)
Clicking on background panel is OK but clicking on the button cause an unwanted ClassCastException.
example:
//making frame
frame=new JFrame();
frame.setVisible(true);
//making layered pane
layeredPane = new JLayeredPane();
frame.add(layeredPane);
//transparent panel on top
frontPanel = new JPanel();
frontPanel.setOpaque(false);
layeredPane.add(frontPanel,Integer.valueOf(1));
//background panel containing a button
backPanel = new JPanel();
JButton button = new JButton();
backPanel.add(button);
layeredPane.add(backPanel,Integer.valueOf(0));
//listeners
button.addMouseListener(new MouseListener()...a blank listener...);
backPanel.addMouseListener(new MouseListener()...a blank listener...);
frontPanel.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
SwingUtilities.getDeepestComponentAt(backPanel, e.getX(), e.getY()).dispatchEvent(e);
}
#Override
public void mousePressed(MouseEvent e) {
SwingUtilities.getDeepestComponentAt(backPanel, e.getX(), e.getY()).dispatchEvent(e);
}
#Override
public void mouseReleased(MouseEvent e) {
SwingUtilities.getDeepestComponentAt(backPanel, e.getX(), e.getY()).dispatchEvent(e);
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
});
//setting dimensions
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frontPanel.setSize(screenSize);
backPanel.setSize(screenSize);
frame.setSize(screenSize);
//now clicking on the button cause a ClassCastException: JPanel cannot be cast to javax.swing.AbstractButton. why?
You can't just dispatch the old event. The old event contains information about the original event (ie. the component source).
You need to create a new MouseEvent before dispatching it.
For example see the GlassPaneDemo from the Swing tutorial on How to Use Root Panes.
I was wondering how to add a focus gained event listener.
At the moment I have a Mouse Event which is being added to my
JTextareas
//=======================================================
// mouse drag event
//=======================================================
public static class genDrag extends MouseMotionAdapter {
JTextArea textarea;
// receive textarea as argument
public genDrag(JTextArea argTextarea) {
textarea = argTextarea;
}
// add drag functionality to argument
public void mouseDragged(MouseEvent E) {
Point p = SwingUtilities.convertPoint(textarea, E.getPoint(), gc_gui.cv_content);
textarea.setBounds((p.x - 40), (p.y - 15), 100, 30);
}
}
which I can then call using
//=======================================================
// apply mouse event
//=======================================================
JTextArea textarea = new JTextArea();
textarea.setBounds(50, 50, 100, 30);
textarea.addMouseMotionListener(new genDrag(textarea));
this works fine but I have been unable to reproduce the same
functionality for a focusGained event
//=======================================================
// mouse focus event
//=======================================================
public static class genFocus extends EventListener {
JTextArea textarea;
public genFocus() {
textarea = argTextarea;
}
public void focusGained(FocusEvent E) {
System.out.println("Focus Triggered");
}
}
The above doesn't seem happy at all
UPDATING CODE
static gui classGui;
public static void main(String[] args) {
classGui = new gui();
classGui.textarea.addMouseMotionListener(
new genDrag(classGui.textarea)
);
classGui.textarea.addFocusListener(
new genFocus(this)
);
classGui.frame.setVisible(true);
public static class gui {
JFrame frame;
JPanel panel;
JTextArea textarea;
public gui() {
frame = new JFrame();
// configure JFrame here
panel = new JPanel();
// configure JPanel here
textarea = new JTextArea();
textarea.setBounds(50, 50, 100, 30);
frame.add(textarea);
}
}
public static class genDrag extends MouseMotionAdapter {
JTextArea textarea;
public genDrag(JTextArea argTextarea) {
textarea = argTextarea;
}
public void mouseDragged(MouseEvent E) {
Point p = SwingUtilities.convertPoint(textarea, E.getPoint(), gc_gui.cv_content);
textarea.setBounds((p.x - 40), (p.y - 15), 100, 30);
}
}
public static class genFocus implements FocusListener {
JTextArea textarea;
public genFocus(JTextArea argTextarea) {
textarea = argTextarea;
}
public void focusGained(FocusEvent E) {
System.out.println("Focus gained");
}
public void focusLost(FocusEvent E) {
System.out.println("Focus lost");
}
}
}
To handle focus events, your handler needs to implement the FocusListener interface instead of EventListener.
Note that you need to add this handler via the addFocusListener. I don't think you did this, because if you had done this, you would have gotten a compiler error indicating what was wrong.
Use of the #Override annotation helps finding such errors. Put it above every method you think should override a parent method. If such a method does not actually override another method, the compiler will throw an error. This way you get informed of the mistake instead of your program failing silently.
you should add a event-listener to the control JTextArea then only it will be able to handle any event request.
JTextField textarea= new JTextField("Value");
textarea.addFocusListener(new genFocus(textarea)); //this peice of code will add an listener to you textarea Object of JTextField.
Your Mouse Listener will work because you have added a mouse event listener to your JTextArea.
textarea.addMouseMotionListener(new genDrag(textarea));//code to add MouseMotionListener.
but there is no FocusEvent is registerd with your JTextArea.
Thanks.
I think this is exactly what you need...
Just a hint: your class genFocus (prefer to follow code conventions: GenFocus) should implement FocusListener.
I'm new to java and working on a GUI assignment and I'm running into some issues. The premise is a simple GUI window that has a few mouse events and a few keyboard events.
I put together a window with some mouse events and once that was working started to add a couple JTextFields to the window but they're not showing up on the window and I'm not exactly sure why.
Here is the problem now. I created a new panel (panel2) to add the JTextFields to the window and I now see the JTextFields but it overtakes the entire window and the Mouse Events do not work with it. If I add the JTF to the panel above the Mouse Events then the JTF do not show up and the Mouse Events work.......
code
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class EventDemo extends JFrame {
private JPanel mousePanel;
private JPanel panel1;
private JPanel panel2;
private JPanel panel3;
private JLabel statusBar;
private JLabel directions1;
private JLabel directions2;
private JLabel directions3;
private JTextField textField1;
private JTextField textField2;
public EventDemo() {
super("EVENT DEMO PROGRAM");
panel1 = new JPanel();
panel2 = new JPanel();
//Add directions for the events to top of the window.
directions1 = new JLabel("Enter & Leave window." +
" Press & hold, release, drag, move cursor to display a message in statusbar." +
" Clicking in one spot will display coordinates.");
panel1.add(directions1);
add(panel1, BorderLayout.PAGE_START);
//Add mouse and statusBar to panel.
mousePanel = new JPanel();
mousePanel.setBackground(Color.WHITE);
add(mousePanel, BorderLayout.CENTER);
statusBar = new JLabel("Default");
add(statusBar, BorderLayout.SOUTH);
//Create handler object for Mouse events
TheHandler handler = new TheHandler();
mousePanel.addMouseListener(handler);
mousePanel.addMouseMotionListener(handler);
textField1 = new JTextField(10);
panel2.add(textField1, BorderLayout.SOUTH);
textField2 = new JTextField("Enter Text Here");
panel2.add(textField2, BorderLayout.SOUTH);
add(panel2);
TheHandler handlerJTF = new TheHandler();
textField1.addActionListener(handlerJTF);
textField2.addActionListener(handlerJTF);
}
private class TheHandler implements MouseListener, MouseMotionListener {
public void mouseClicked(MouseEvent event) {
statusBar.setText(String.format("YOU CLICKED THE MOUSE AT %d, %d", event.getX(), event.getY()));
}
public void mousePressed(MouseEvent event) {
statusBar.setText("YOU HAVE PRESSED THE MOUSE BUTTON.");
}
public void mouseReleased(MouseEvent event) {
statusBar.setText("YOU HAVE RELEASED THE MOUSE BUTTON.");
}
public void mouseEntered(MouseEvent event) {
statusBar.setText("YOU HAVE ENTERED THE WINDOW THE BACKGROUND CHANGES TO RED.");
mousePanel.setBackground(Color.RED);
}
public void mouseExited(MouseEvent event) {
statusBar.setText("EXITING THE WINDOW, BACKGROUND CHANGES BACK TO WHITE.");
mousePanel.setBackground(Color.WHITE);
}
//Mouse Motion events.
public void mouseDragged(MouseEvent event) {
statusBar.setText("YOU ARE DRAGGING THE MOUSE.");
}
public void mouseMoved(MouseEvent event) {
statusBar.setText("YOU ARE MOVING THE MOUSE AROUND.");
}
}
public void actionPerformed(ActionEvent event) {
textField1.getText();
textField2.getText();
}
public static void main(String[] args) {
EventDemo go = new EventDemo();
go.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
go.setSize(960, 300);
go.setVisible(true);
new EventDemo();
}
}
TheHandler does not implement ActionListener which is the required type of JTextField#addActionListener. See How to Write an Action Listeners and How to Use Text Fields for more details
Your actionPerformed method isn't actually defined in TheHandler class, but is a method of the EventDemo class
You should use the #Override annotation when you think you're overriding methods (or implementing methods from an interface) as this will cause a compiler error when you're wrong (for some reason)
Your fields aren't appearing for two reasons, first, textField is added to panel2 which is never added to anything and textField2 is been added to the frame (at the default position of BorderLayout.CENTER), but is been overriden/circumvented by add(mousePanel, BorderLayout.CENTER);, so it's actually never laid out by the frames BorderLayout. Have a look at How to Use Borders for more details
Not sure if this has been discussed before. But I am having a odd issue with transparent JTextFields added on a transparent JPanel. For some reason (I could not dig enough to find why) there are additional paintings that get carried out. Perhaps there are dirty regions that needs to be dealt with? not sure.
Let me present this simple example:
public class TextFieldGame extends JPanel {
public static void main(String [] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame someFrame = new JFrame("Is this odd?");
someFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
someFrame.setSize(200,600);
someFrame.add(new TextFieldGame());
someFrame.setVisible(true);
}
});
}
public TextFieldGame() {
setupContentPane();
}
private void setupContentPane() {
setLayout(new BorderLayout());
final CanvasPanel canvasPanel = new CanvasPanel();
add(canvasPanel, BorderLayout.CENTER);
add(new ControlPanel(canvasPanel), BorderLayout.SOUTH);
}
public static class ControlPanel extends JPanel {
private final CanvasPanel canvasPanel;
ControlPanel(CanvasPanel canvasPanel) {
this.canvasPanel = canvasPanel;
setupContentPane();
}
private void setupContentPane() {
setLayout(new FlowLayout(FlowLayout.RIGHT));
final JButton load = new JButton("load");
add(load);
load.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
canvasPanel.addChildComponent(getComponent());
canvasPanel.revalidate();
canvasPanel.repaint();
}
});
}
private JComponent getComponent() {
final JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
container.setOpaque(false);
for (int i = 0; i < 10; i++) {
final JTextField textField = new JTextField("why you no work?") {
#Override
public Dimension getMaximumSize() {
return new Dimension(Short.MAX_VALUE, getPreferredSize().height);
}
};
textField.setOpaque(false);
container.add(textField);
}
return container;
}
}
public static class CanvasPanel extends JPanel {
private int paintCount = 0;
CanvasPanel() {
setupContentPane();
}
public void setupContentPane() {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBackground(Color.white);
}
public void addChildComponent(JComponent component) {
component.setAlignmentY(TOP_ALIGNMENT);
add(component);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("paint count: " + ++paintCount);
}
}
}
I have added the System out statement to show the count of paint task.
When first loaded, there will be 2/3 paints - fair enough. Then if you are to press the "Load" button, 10 transparent JTextFields will be added to a transparent JPanel, and this transparent JPanel will be added to the CanvasPanel. (Canvas panel is in turn a subchild of the JFrame). You will notice upon doing this, 11 additional paint jobs will be done.
But in theory (well in my understanding) after "Load" is pressed, only one paint job should be carried out. That is because, I have added only one child to CanvasPanel (the child itself might have 10 textfields, but they should all be painted in one shot).
Just to test my understanding, if you are to use 10 JLabels instead of the 10 JTextFields, only one paint job is carried out after "Load" is pressed. Which is what it should be.
Also, if you are to keep JTextField opaque, only one paint job is carried out. (Just tested that if instead of JTextField, a JTextArea is used, one paint is done)
What is going on? Note that JLabel is transparent by default, so I am not sure why transparency of JTextField component causing these additional paints.
Please help/
I have a main JPanel class (not exact code):
class Panel extends JPanel {
public void initGUI() {
setLayout(...);
JTabbedPane tabbedPane = new JTabbedPane();
JPanel boxPanel = new JPanel(...);
tabbedPane.addTab("test", boxPanel);
JLabel label = new JLabel("Label")
boxPanel.add(label);
add(tabbedPane);
}
}
I want to be able to click anywhere on the Panel or its inner components and return the Panel.
public class PanelMouseAdapter extends MouseAdapter {
public void mouseReleased(MouseEvent e) {
Panel panel = (Panel)e.getSource();
//do other stuff
}
}
And for each Panel I'm adding this mouse listener.
But it only works around the edges of the Panel, any inner components are ignored. I need it to be able to click anywhere in that Panel.
I need to maintain that anywhere I click it will return the Panel object (as in the mouse listener).
Thanks for any feedback.
Not sure I understand the question. Your demo code only shows a single panel, so why do you care what parent panel is clicked on? The better your explanation of the requirement the better the solution we can provide.
Anyway, check out Global Event Listeners. This will allow you to listen for a mousePressed event (which is a better then listening for a mouseClicked).
Next you need to create a custom panel (MyCustomPanel) that you use for the top level panel.
Now, whenever a mousePressed is generated you can get the source of the event and then use:
MyCustomPanel panel = SwingUtilties.getAncestorOfClass(MyCustomPanel.class, (Component)event.getSource());
You can use Container#getComponents() for this case. For example consider the code snippet given below (look for addListeners(Container) method):
import javax.swing.*;
import java.awt.event.*;
import java.util.*;
import java.awt.*;
class SPanel extends JPanel
{
public void init()
{
this.add(new JButton("Button"));
this.add(new JLabel("Click"));
JPanel pan = new JPanel();
pan.add(new JButton("PanButton"));
pan.add(new JTextField(29));
add(pan);
addListeners(this);
}
public void addListeners(Container comp)
{
Component[] components = comp.getComponents();
for (Component component : components)
{
if (component instanceof Container)
{
Component[] child = ((Container)component).getComponents();
if (child != null && child.length > 0)
{
addListeners((Container)component);
}
}
component.addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent evt)
{
System.out.println(evt.getSource().getClass());
}
});
}
}
public static void main(String[] args)
{
SwingUtilities.invokeLater( new Runnable()
{
#Override
public void run()
{
SPanel sp = new SPanel();
sp.init();
JFrame frame = new JFrame("Frame");
frame.getContentPane().add(sp);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
Solved issue.
Initially I was going to open a popup menu once I had the mouse listener in place.
But now I added JComponent.setComponentPopupMenu as Polum suggested, not to the Panel but to the tabbedPane.
Then I added a listener to the popup menu, got the source object via event in popupMenuWillBecomeVisible method, then the component via source.getInvoker(), then get the parent of the invoker component and check if its an instance of PairBox.