I have an issue where Swing (in Java 1.6, Windows) doesn't seem to trigger mouseEntered and mouseExited events the way I want it to. I have an application where I wish to have a number of JPanels stacked vertically in a JScrollPane, and that they should be highlighted with a different colour when the mouse is over them. Simple enough problem, but whenever I scroll using the mouse wheel, it doesn't quite behave.
I have made a sample application to illustrate my problem (code found below). The images below are from that one, not the "real" application.
When I hold the mouse cursor over the edge of a panel, it's highlighted correctly. Now, when I use the mouse wheel to scroll down, I expect the cursor to be over box B, and the proper mouseEntered/mouseExited events to be triggered so that A becomes white and B becomes red.
(source: perp.se)
(source: perp.se)
However, that doesn't seem to happen.
Now, B becomes highlighted if I trigger another mouse event, be it "move 1 pixel", "click a button" or "scroll another step". Knowing this, I could perhaps solve it in a hackish way, but I'd rather not if there's a proper solution.
So basically what I'm wondering is if this is to be regarded as a bug in Swing, or am I just doing things wrong?
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class ScrollTest extends JFrame {
public static class LetterPanel extends JPanel {
private static final Font BIG_FONT = new Font(Font.MONOSPACED, Font.BOLD, 24);
public LetterPanel(String text) {
setBackground(Color.WHITE);
setBorder(BorderFactory.createLineBorder(Color.BLACK));
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
setBackground(Color.RED);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(Color.WHITE);
}
});
setLayout(new GridLayout(1, 1));
setPreferredSize(new Dimension(-1, 50));
JLabel label = new JLabel(text, SwingConstants.CENTER);
label.setFont(BIG_FONT);
add(label);
}
}
public ScrollTest() {
setLayout(new GridLayout(1, 1));
setSize(400, 400);
JPanel base = new JPanel();
JScrollPane jsp = new JScrollPane(base);
jsp.getVerticalScrollBar().setUnitIncrement(16);
add(jsp);
base.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridheight = 1;
gbc.gridwidth = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(0, 0, 10, 0);
gbc.weightx = 1.0;
for (char c = 'A'; c <= 'Z'; c++) {
base.add(new LetterPanel(String.valueOf(c)), gbc);
gbc.gridy++;
}
}
public static void main(String[] args) {
final JFrame f = new ScrollTest();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.setVisible(true);
}
});
}
}
This seems like a similiar problem to the one described in Tooltips and Scrollpanes. That is, no mouse events are generated because the mouse itself doesn't move, the viewport moves. I'm not sure the exact solution other using the AdjustmentListener to track the component at the mouse location. Every time is changes you can fire a mouseExited event to the previous panel and a mouseEntered event to the new panel.
I can get your code to reproduce this reliably but only when I don't quite finish the scrolling. On my mouse at least there is sort of a "catch" when the mouse wheel finished scrolling. If I scroll very slowly I can have it move but it doesn't change the highlight until the mouse wheel has reached the "catch".
When I do that the mouse enter message is received on the previous panel (same behaviour you are seeing).
Looking at it I scroll the mouse and it does not actually receive the exited/entered events unless I scroll enough to have the mouse wheel "catch". It is possible that Windows does not send the message to Java until the "catch" happens... from my testing that is what it looks like.
You might want to look into the MouseWheelListener interface and the MouseInfo class. I guess you might be able to detect the wheel movement and then figure out where you are with MouseInfo.getPointerInfo().getLocation() and then figure out what component you are over and change the highlighting.
Related
I have created a small app that uses JButtons to increment numbers. The buttons aren't supposed to be clickable but rather be activated by keyboard (ie the numbers in the textField increase with the keyboard pushed and not by using a mouse to click the button). My issue is that when the app is first launched, the keyboard doesn't do anything until I first click one of the buttons - even though clicking the button doesn't progress anything. I have tried to make one of the buttons requestFocusInWindow() thinking that if it was already focused on, the the keys would work, but that hasn't seemed to work whether I put it in my main method or controller class. I've tried to figure out if I need to do a KeyboardFocusManager or a addFocusListener() (but I don't want something always happening if a button gains/loses focus). I've tried so many things my head is spinning, trying to add either to my main method with frame or my controller class. Below is what my current code is:
Class with Main
import javax.swing.JFrame;
public class Count {
public static void main(String[] args) {
CountController frame = new CountController();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(560, 150);
frame.setVisible(true);
//I've tried to add the button and requestFocusInWindow here
//as well as tried a frame.addWindowFocusListener
}
} // end class
Controller Class
imports ...
public class CountController extends JFrame implements KeyListener {
private JLabel ...
private JTextField ...
private JButton ....
int ...
// no-argument constructor
public CountController() {
super("Title");
setLayout(null); // position GUI components explicitly
//set up JLabels in following manner
label = new JLabel("some label");
label.setBounds(47, 5, 45, 25);
label.setHorizontalAlignment(JLabel.CENTER);
add(label);
//set up JTextFields in following manner
textField = new JTextField("0");
textField.setBounds(47, 30, 45, 25);
textField.setHorizontalAlignment(JTextField.CENTER);
textField.setBackground(Color.WHITE);
textField.setEditable(false);
add(textField);
//set up JButtons in the following manner
button = new JButton("some text");
button.setBounds(15, 70, 110, 25);
button.setBackground(Color.WHITE);
add(button);
button.addKeyListener(this);
//I've tried adding requestFocusInWindow() here as well
} // end constructor
//begin KeyListener stuff
#Override
public void keyPressed(KeyEvent event){
int keyCode = event.getKeyCode();
switch(keyCode){
case #: //# is ASCII #
do some things;
call a method();
break;
}
}
#Override
public void keyReleased(KeyEvent event){
button.setBackground(Color.WHITE);
}
#Override
public void keyTyped(KeyEvent event){
// nothing but this is needed for implementing KeyListener
}
//List of methods that are called from switch
...
//I've tried to add a public void initialize(){}
}//end CountController class
I would appreciate any input on getting this to work so that I don't have to first click a button before my keys work.
Thanks!
In order for the listener to trigger an event, the component it is registered to must first BE focused AND have focus. Use the key bindings API instead.
The following uses JComponent.WHEN_IN_FOCUSED_WINDOW to define the context under which the key events will be generated. In this configuration, it won't matter what has focus, the events will still be generated, so long as the window is currently focused
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
public class Count {
public static void main(String[] args) {
CountController frame = new CountController();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(560, 150);
frame.setVisible(true);
//I've tried to add the button and requestFocusInWindow here
//as well as tried a frame.addWindowFocusListener
}
public static class CountController extends JFrame {
// no-argument constructor
public CountController() {
super("Title");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
//set up JLabels in following manner
JLabel label = new JLabel("some label");
label.setHorizontalAlignment(JLabel.CENTER);
add(label, gbc);
//set up JTextFields in following manner
JTextField textField = new JTextField("0", 20);
textField.setHorizontalAlignment(JTextField.CENTER);
textField.setBackground(Color.WHITE);
textField.setEditable(false);
add(textField, gbc);
//set up JButtons in the following manner
JButton button = new JButton("some text");
add(button, gbc);
InputMap im = button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = button.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_3, KeyEvent.SHIFT_DOWN_MASK), "Pressed.#");
am.put("Pressed.#", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
textField.setText(textField.getText() + "#");
}
});
} // end constructor
}//end CountController class
} // end class
Although I'd register the bindings against the parent container and not the buttons, but that's just me.
And, reasons for not using null layouts ... this is what your original code looks like on my PC
Although it's not quite clear to me what your code should accomplish, your problem is that you addKeyListener(this) to the button but your button doesn't have the focus and the key doesn't do anything when pressed. Try adding the KeyListener() to some other GUI component, like the textfield for example, since it's the first component and has the focus on start(from the code you've provided), and see if it works.
I'm programming a cookie clicker game remake and when I scale the JFrame window, something white appears. It disappears as soon as you hover the cursor over the button(when refreshes) and I need to fix that, because it does even the same when you launch the game.
Here's a screenshot(UNSCALED | SCALED): http://s3.postimg.org/xomifomhf/bandicam_19.png
this is the whole code of this game:
package cookieclicker.tominocz;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static int num1;
static Icon icon1 = new ImageIcon("cookie.png");
static JButton b1 = new JButton(icon1);
static JButton b2 = new JButton("You got " + num1 + " Cookies!");
#SuppressWarnings("serial")
public static void main(String[] args) {
File save = new File(".\\gamesave.cookieclicker");
if (save.exists()) {
loadGame();
}
JFrame f = new JFrame("Cookie Clicker Beta v0.1");
b2.setBackground(Color.cyan);
b2.setPreferredSize(new Dimension(10000, 14));
JPanel buttonPanel1 = new JPanel(new GridLayout(1, 1));
buttonPanel1.setBackground(Color.DARK_GRAY);
b2.setEnabled(false);
b2.setBorder(null);
buttonPanel1.add(b2);
JPanel buttonPanel2 = new JPanel(new GridLayout(1000, 1));
buttonPanel2.setBackground(Color.DARK_GRAY);
buttonPanel2.setEnabled(false);
buttonPanel2.add(new JButton("Grandma"));
buttonPanel2.add(new JButton(""));
buttonPanel2.add(new JButton(""));
JPanel east = new JPanel(new GridBagLayout());
JPanel north = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTH;
gbc.weighty = 1;
north.add(buttonPanel1, gbc);
east.add(buttonPanel2, gbc);
JPanel center = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
};
center.setBorder(BorderFactory.createLineBorder(Color.BLACK));
f.add(east, BorderLayout.EAST);
f.add(north, BorderLayout.NORTH);
f.add(center);
f.pack();
f.setSize(600, 400);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b1.setBackground(Color.lightGray);
b1.setBorder(null);
f.add(b1);
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JButton) {
addCookies();
}
}
});
}
public static void addCookies() {
saveGame();
b2.setText("You got " + ++num1 + " Cookies!");
if (num1 == 1) {
b2.setText(" You got " + 1 + " Cookie! ");
} else {
b2.setText("You got " + num1 + " Cookies!");
}
System.out.println(num1);
}
public static void saveGame() {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(
".\\gamesave.cookieclicker"));
writer.write(String.valueOf(1 + num1));
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void loadGame() {
try (BufferedReader br = new BufferedReader(new FileReader(
".\\gamesave.cookieclicker"))) {
String SavedGame;
while ((SavedGame = br.readLine()) != null) {
num1 = Integer.parseInt(SavedGame);
b2.setText("You got " + num1 + " Cookies!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
And also the other thing is, that the dark grey strip is hiding 3 buttons.
Don't count the 4th one, that's the one showing the ammount of cookies you have :).
Now where could the problem be?
You have two components sharing the CENTRE position of the frame's BorderLayout, center and b1.
b1, been the last component added, is getting the attention of the layout manager and is been laid out when the frame is resized, center is not and is remaining at the last size/position it was set to (because you called pack, which forced the frame to layout it's child components, but then you added b1 after it).
BorderLayout can only manage a single component at each of it's five pre-defined positions
Make a decision about who should be in the centre...
You should also have a read of Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? and stop messing with the preferred size of your components, let them make their own decisions in combination with appropriate layout managers
I'd also encourage you to move the content of your main method some where else (may be the class's constructor), this way, you fields won't need to be static and it solve a ton of other issues you might have in the future
First of all:
b2.setPreferredSize(new Dimension(10000, 14));
Don't specify preferred sizes for components. Each component is responsible for determining its own preferred size. Let the layout manager determine the size.
JPanel buttonPanel2 = new JPanel(new GridLayout(1000, 1));
Don't use random numbers when defining the GridLayout. If you want one column then just use: new GridLayout(0, 1). Now all components added will be displayed in the first row.
Now for your problem:
f.add(center);
f.pack();
You add an empty panel to the CENTER of the BorderLayout. Then you pack the frame so the panel now has a valid size.
f.add(b1);
But then you add a second component to the "CENTER". However BorderLayout will only manage the size of the last component added.
Swing will paint() the last component added first, so the button is painted, then the panel is painted over top of it.
If you move the mouse over the center, then the mouse event is passed to the button and the rollover logic is invoked so the button is painted.
If you resize the frame, the buttons size is recalculated by the layout manager and components are repainted. Again, the center panel is painted last so you see part of the button with the panel on top.
I don't know why you have the center panel so I can't make a specific suggestion other than to say, get rid of it. Again the main problem is you are trying to add two components to the center. Don't do this!
I have a JPanel with GroupLayout with 3 JLabels in it. I also have a hidden JButton in it.
I have added a MouseListener to JPanel showing the button in mouseEntered and hide the button in mouseExited events respectively.
At this time, their is space for button between 2 labels and their only the button is shown or hidden using setVisible(). When the btn is visible, the labels below it goes down making space for button and if the btn is hidden it again comes to its original size.
What I want - in mouseEntered, the button should show on the label itself (let it be overlap) and I should be able to click on the button. This all should happen very smoothly without screen flickering. Similarly in mouseExited, the button should be removed.
How do I achieve this ? Can anyone help me with this.
UPDATE
#Andrew, Thanks I tried with JLayeredPane and it does work. Though the button is not set to visible false. Here's my mouseMoved code :
public void mouseMoved(MouseEvent e) {
if (e.getComponent() == layeredPane) {
if (! startCustomBtn.isVisible())
startCustomBtn.setVisible(true);
startCustomBtn.setLocation(e.getX()-55, e.getY()-30);
} else {
if (startCustomBtn.isVisible()) {
startCustomBtn.setVisible(false);
revalidate();
}
}
}
Layout of the JPanel :
private void layeredLayout() {
layeredPane = new JLayeredPane();
layeredPane.addMouseMotionListener(this);
Insets insets = this.getInsets();
Dimension size = rateLabel.getPreferredSize();
rateLabel.setBounds(insets.left + 45, insets.top + 15, size.width, size.height);
size = imageLabel.getPreferredSize();
imageLabel.setBounds(insets.left + 15, insets.top + 40, size.width, size.height);
size = label.getPreferredSize();
label.setBounds(insets.left + 45, insets.top + imageLabel.getWidth() + 20 , size.width, size.height);
size = startCustomBtn.getPreferredSize();
startCustomBtn.setBounds(insets.left + 45, insets.top + 40 + size.height, size.width, size.height);
layeredPane.add(rateLabel, new Integer(0));
layeredPane.add(imageLabel, new Integer(1));
layeredPane.add(label, new Integer(2));
layeredPane.add(startCustomBtn, new Integer(1), 0);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(layeredPane);
}
Strange - I tried the layout with null, FlowLayout, but couldn't see anything. When tried with BoxLayout, components showed up.
REsult :
Main screen has a JPanel with Gridlayout(2, 3) and in each cell this JPanel (MyPanel) is added. When I come out from 1 cell (i.e. MyPanel) the button of that panel should be hidden which is not happening with the above code. What can be the reason ? I also added revalidate() & also repaint() but nothing works. ????
What I want - in mouseEntered, the button should show on the label
itself (let it be overlap) and I should be able to click on the
button. This all should happen very smoothly without screen
flickering. Similarly in mouseExited, the button should be removed.
As JLabel extends from JComponent you can add componentes to label itself, just need to set a LayoutManager first. This fact is well explained in this question.
Sample Code
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
public class Demo {
private void initGUI(){
final JButton button = new JButton("Hello!");
button.setVisible(false);
final JLabel testLabel = new JLabel("Welcome!");
testLabel.setPreferredSize(new Dimension(200, 30));
testLabel.setBorder(new LineBorder(Color.GRAY, 1));
testLabel.setLayout(new BorderLayout());
testLabel.add(button, BorderLayout.EAST);
button.addMouseListener(new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
Point mousePosition = MouseInfo.getPointerInfo().getLocation();
if(testLabel.contains(mousePosition)){
testLabel.dispatchEvent(new MouseEvent(testLabel, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, mousePosition.x, mousePosition.y, 0, false));
} else {
testLabel.dispatchEvent(new MouseEvent(testLabel, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, mousePosition.x, mousePosition.y, 0, false));
}
}
});
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "The button was pressed!");
Point mousePosition = MouseInfo.getPointerInfo().getLocation();
testLabel.dispatchEvent(new MouseEvent(testLabel, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, mousePosition.x, mousePosition.y, 0, false));
}
});
testLabel.addMouseListener(new MouseAdapter(){
#Override
public void mouseEntered(MouseEvent e) {
JLabel label = (JLabel) e.getSource();
label.setText("Here is the Button!");
button.setVisible(true);
}
#Override
public void mouseExited(MouseEvent e) {
Point point = e.getPoint();
point.setLocation(point.x - button.getX(), point.y - button.getY()); //make the point relative to the button's location
if(!button.contains(point)) {
JLabel label = (JLabel) e.getSource();
label.setText("The button is gone!");
button.setVisible(false);
}
}
});
JPanel content = new JPanel(new FlowLayout());
content.setPreferredSize(new Dimension(300,100));
content.add(testLabel);
JFrame frame = new JFrame("Demo");
frame.setContentPane(content);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().initGUI();
}
});
}
}
Output
Update
As #nIcEcOw pointed out (thanks!), there's an annoying flickering generated by mouse events' transition. I improved the example fixing this and another untreated aspects like "what happens when mouse exits from JButton?"
Questions like this are kind of frustrating. There is almost enough information to describe what you want, or what the problem is, but not quite.
It seems that you want label-label-label until the mouse enters the panel, then you want the appearance to be label-button-label. It's hard to imagine me wanting a UI to act like this.
Is there something about the appearance of the button you don't like, that you want it only to appear on mouse-over-panel? Can the button's appearance be altered so that it looks the way you want it to look, without all this hocus-pocus with the middle label and the button?
I don't have any idea why you mention a timer -- nothing that you describe is being timed, as near as I can tell. In addition, you should be able to boil down what you have to a small runnable example and post it, so that someone can see what you've got and what it does.
When I add Swing component (like a JButton) to a JPanel, it renders with it's 'preferred size'.
However, the preferred size is actually larger than the painted button. There appears to be an invisible border around it.
Here's a simple frame with my test panel:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TestPanel pnl = new TestPanel();
frame.getContentPane().add(pnl);
frame.pack();
frame.setVisible(true);
Here's my test panel ...
public class TestPanel extends JPanel {
JButton btn1 = new JButton("Test1");
JButton btn2 = new JButton("Test2");
public TestPanel() {
this.add(btn1);
this.add(btn2);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.RED);
Dimension dim = btn1.getPreferredSize();
g.drawRect(btn1.getX(), btn1.getY(), (int)(dim.getWidth()), (int)(dim.getHeight()));
}
}
Notice I painted btn1's "PreferredSize" in RED to demonstrate that the preferredSize is actually larger than the button itself.
My question is, how can I determine the width and height of the painted button, not the JButton's preferredSize?
Any help is greatly appreciated, thanks!
UPDATE
Because I actually need this to work for all Swing components, here's a screen shot with the more components.
Unfortunately, I need to figure this out, determining the "real" size of the visible widget is crucial to my application.
I don't think this is particular or practically achievable.
The problem is, the button is using the "unpainted" area to paint other elements, like the focus highlight.
You could try look at the AbstractButton#set/getMargin
If nothing better comes along, note that the authors "recommend that you put the component in a JPanel and set the border on the JPanel."
Addendum: Based on your comments below, it's clear that your question is not about rendering borders but about establishing a component's boundary. What you perceive as unused space is actually reserved by the UI delegate for any number of uses, e.g. selection highlighting or esthetic coherence. You can get an idea of how this varies by selecting different Look & Feel themes in the examples here and here.
Using getbounds():
Using setBorder():
import component.Laf;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* #see https://stackoverflow.com/a/15490187/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
// https://stackoverflow.com/a/11949899/230513
f.add(Laf.createToolBar(f));
f.add(decorate(new JButton("Test")));
f.add(decorate(new JTextField("Test")));
f.add(decorate(new JTextArea(3, 8)));
f.add(decorate(new JCheckBox("Test")));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JPanel decorate(final JComponent c) {
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle r = c.getBounds();
g.setColor(Color.red);
// NB pen hangs down and to the right
g.drawRect(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
}
};
p.add(c);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
I'm looking for a way to pass mouse events to components covered by other components. To illustrate what I mean, here's a sample code. It contains two JLabels, one is twice smaller and entirely covered with a bigger label. If you mouse over the labels, only the bigger one fires mouseEntered event however.
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;
public class MouseEvtTest extends JFrame {
public MouseEvtTest() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLayout(null);
setSize(250, 250);
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
System.out.printf("Mouse entered %s label%n", e.getComponent().getName());
}
};
LineBorder border = new LineBorder(Color.BLACK);
JLabel smallLabel = new JLabel();
smallLabel.setName("small");
smallLabel.setSize(100, 100);
smallLabel.setBorder(border);
smallLabel.addMouseListener(listener);
add(smallLabel);
JLabel bigLabel = new JLabel();
bigLabel.setName("big");
bigLabel.setBorder(border);
bigLabel.setSize(200, 200);
bigLabel.addMouseListener(listener);
add(bigLabel, 0); //Add to the front
}
public static void main(String[] args) {
new MouseEvtTest().setVisible(true);
}
}
What would be the best way to fire mouse entered event on the smaller label when cursor is moved to the coordinates above it? How would it work in case where there would be multiple components stacked on top of each other? What about the remaining mouse events, like mouseClicked, mousePressed, mouseReleased, etc.?
Take a look at Alexander Potochkin's blog entry on A Well-Behaved GlassPane
In your listener:
bigLabel.dispatchEvent(mouseEvent);
Of course, you will have to define bigLabel as final
Well to understand whats happening you need to understand how Z-Ordering works. As a quick overview, the component that was added last is painted first. So in your case you want to add the small component before the big component.
// add(bigLabel, 0); //Add to the front
add(bigLabel); // add to the end so it is painted first
The OverlayLayout might help explain this better and give you another option.