Show button on JLabel in Swing - java

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.

Related

Why is my JFrame window doing that?(check description)

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!

Overlapping issue in Swing

I'm encountering this issue in Java Swing:
Both Magenta and Green components are JButtons. I'm using absolute layout for this. When hovering to Green, it overlaps Magenta even if no layout manager is applied nor JLayeredPane used.
Any reason for this behavior? How can I make sure the Magenta stays on top when hovering to Green?
Edit 2:
Just to be clear with my goal, the idea for this is to make a UI similar to Android Notification Bar with Assistive Touch. Assume that the Notification Bar is a layer and the Assistive Touch is the topmost layer. The problem with using a transparent layer in JLayeredPane is that if a layer/panel occupies the whole frame even when set to transparent, the layers underneath it are not drawn.
The answer is very simple: don't use absolute layout, use a real LayoutManager. In this case, it seems that BorderLayout will do the job just fine.
See this example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class TestLayout {
protected void initUI() {
JFrame frame = new JFrame("test");
Container cp = frame.getContentPane();
cp.setLayout(new BorderLayout());
cp.add(createColoredButton(Color.BLACK, Color.MAGENTA, "Hello World 1"), BorderLayout.NORTH);
cp.add(createColoredButton(Color.BLACK, Color.GREEN, "Hello World 2"), BorderLayout.EAST);
cp.add(createColoredButton(Color.WHITE, Color.BLUE, "Hello World 3"), BorderLayout.CENTER);
// frame.pack();
frame.setSize(600, 600);
frame.setVisible(true);
}
private JButton createColoredButton(Color fgColor, Color bgColor, final String text) {
final JButton button = new JButton(text);
button.setBorderPainted(false);
button.setFocusPainted(false);
button.setForeground(fgColor);
button.setBackground(bgColor);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(button, "You just clicked: " + text);
}
});
return button;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestLayout().initUI();
}
});
}
}

How to make a JLabel change once based on time

I've ben researching how to use swing timers for 2 days now and am trying to figure out how to change the image I have at the center of my JFrame. As of now my program runs properly, but the image does not change the way I want it to. This class was just used as a test so it might not have proper java syntax.
package hauntedHouseAdventure;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class timertest {
static JFrame sceneOne = new JFrame();
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
ImageIcon image = new ImageIcon(
"/Users/computerscience2/Desktop/dark-forest-night-image.jpg");
JLabel imageLabel = new JLabel("", image, JLabel.CENTER);
JPanel panel = new JPanel(new BorderLayout());
panel.add(imageLabel, BorderLayout.NORTH);
sceneOne.add(panel);
sceneOne.setResizable(false);
imageLabel.setVisible(true);
sceneOne.pack();
JButton leave=new JButton("Leave");
JButton stay= new JButton ("Stay");
leave.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
//Execute when button is pressed
sceneOne.setVisible(false);
}
});
stay.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent f)
{
//Execute when button is pressed
sceneOne.setVisible(false);
}
});
panel.add(leave, BorderLayout.EAST);
panel.add(stay, BorderLayout.WEST);
JLabel label1 = new JLabel("Test");
label1.setText("<html><font color='red'> It was approximately 11:30 pm. The night sky was black not a single star piercing through the darkness"
+ "except the thick and powerful moonlight."
+ "<br>"
+ "You are alone leaving a costume party at a friend's place."
+ "It was rather boring and you decided to leave early."
+ "A stutter is heard and your"
+ "<br>"
+ "car begins to shake"
+ "Your headlights and car lights crack. The engine is left dead silent."
+ "You are left in a total silence"
+ "and baked in merely the moonlight."
+ "<br>"
+ "There is a mere second of silence till a harsh chill ripes through the"
+ "car like a bullet through paper. You are left dumbfounded. What do you do?</font><html>");
label1.setHorizontalAlignment(JLabel.CENTER);
label1.setVerticalAlignment(JLabel.BOTTOM);
label1.setVisible(true);
label1.setOpaque(false);
panel.add(label1);
final ActionListener updater = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
ImageIcon image = new ImageIcon(
"/Users/computerscience2/Desktop/image-slider-5.jpg");
JLabel imageLabel = new JLabel("", image, JLabel.CENTER);
JPanel panel = new JPanel(new BorderLayout());
panel.add(imageLabel, BorderLayout.NORTH);
sceneOne.add(panel);
}
};
Timer timer = new Timer(1000, updater);
timer.start();
sceneOne.setSize(2000,1000);
sceneOne.setTitle("The Car");
sceneOne.setVisible(true);
sceneOne.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sceneOne.setLocation(250, 200);
}
}
I see two problems in your code.
You are adding the new image to your panel, but not removing the old image.
You are not telling to your frame that your components changed and he needs to redraw.
I think the best way to do this, is just changing the icon of your imageLabel. Like this:
final JLabel imageLabel = new JLabel("", image, JLabel.CENTER);
......
final ActionListener updater = new ActionListener() {
public void actionPerformed(ActionEvent e) {
ImageIcon image = new ImageIcon("/Users/computerscience2/Desktop/image-slider-5.jpg");
imageLabel.setIcon(image);
imageLabel.updateUI(); //Tell to your frame he needs to redraw the image
}
};
I didn't look in detail but to change the image on the label then you don't need to create a new control, you just need to change the image on the existing one.
public void actionPerformed(ActionEvent event) {
ImageIcon image = new ImageIcon(
"/Users/computerscience2/Desktop/image-slider-5.jpg");
imageLabel.setIcon(image);
}
You will also need to make imageLabel either a member variable or final so that the actionPerformed method can see it.

How to get the painted size of a Swing component?

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();
}
});
}
}

Swing won't trigger mouseEntered/mouseExited properly after mouse wheel events?

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.

Categories