问题
I'm developing a program for work (notice: I can not share complete code, as it is in large part protected work product, but I will share everything I can). In the application, I have JPanels that have background images applied to them. Some of these panels also have mouse listeners attached & my management wants there to be a visual clue that the panel can be clicked on to initiate an action. To that end, I've overlaid a transparent JPanel on top of the JPanel with the background image, and attached a MouseListener to that, keying off the mouseEntered/Exited events. When the mouse enters the image panel, the overlaid panel will switch from transparent to translucent, and back when the mouse exits.
Under Linux, this works perfectly. Under Windows... it's a good thing I'm bald so I can't tear my hair out. What appears to be happening is that some kind of image caching is occuring as I move the mouse around, and the mouseEnter event is causing whatever the mouse was over a second ago to be painted into the frame; i.e. if I place the mouse over a nearby button on the GUI & then mouse over the panel, I'll see the button appear in the panel, with the surrounding GUI.
The Image Panel is contained within a JInternalFrame that is opaque.
One other thing to note, if I do something in the application that would cause the image to change (e.g. making a new selection from a JComboBox), the image repaints as expected. Whatever the trouble is, it seems related to the highlighting and how the image is being repainted/redrawn.
What am I not doing for Windows that I should be?
Thanks in advance.
Here is a link to some images. Start.png is what it looks like in both OS's when the panel opens. GoodMouseEnter.png is what the mouseEnter event looks like under Linux. BadMouseEnter.png & badMouseExit.png are what it looks like under Windows.
This is the Class I am using to create the image panel (EDIT: This example is self contained):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ImageTest {
JFrame frm = null;
ImagePanel imgP = null;
public static void main(String[] args) throws MalformedURLException
{
ImageTest it = new ImageTest();
it.initialize();
}
public void initialize() throws MalformedURLException
{
String path = "file:/C:/CylinderTank.png";
URL imagePath = new URL(path);
System.out.println(imagePath.toString());
frm = new JFrame();
imgP = new ImagePanel(true);
int fW = 500;
int fH = 700;
int pW = 450;
int pH = 650;
frm.setLayout(null);
frm.setPreferredSize(new Dimension(fW,fH));
frm.setSize(fW,fH);
imgP.getFilterPanel().addMouseListener(new PanelListener());
imgP.useCustomSizing(pW, pH);
imgP.setImageURL(imagePath);
imgP.setBounds(0, 0, pW, pH);
frm.add(imgP);
frm.pack();
frm.setVisible(true);
}
private class PanelListener implements MouseListener {
public PanelListener() { }
@Override
public void mouseClicked(MouseEvent e) { }
@Override
public void mousePressed(MouseEvent e) { }
@Override
public void mouseReleased(MouseEvent e) { }
@Override
public void mouseEntered(MouseEvent e)
{
imgP.highlightImage(true);
imgP.repaint();
}
@Override
public void mouseExited(MouseEvent e)
{
imgP.highlightImage(false);
imgP.repaint();
}
}
}
@SuppressWarnings("serial")
class ImagePanel extends JPanel implements ImageObserver {
private final JPanel filterPanel;
private BufferedImage image;
private Dimension panelSize;
private final Toolkit kit;
private boolean highlight = false;
private URL imagePath;
private int imgW, imgH;
private final Color blueFilter = new Color(0, 0, 255, 38);
private final Color redFilter = new Color(255, 0, 0, 38);
private final Color greenFilter = new Color(0, 255, 0, 38);
private final Color clear = new Color(0, 0, 0, 0);
private final Color bgColor = new Color(116, 169, 255, 255);
private boolean customSize = false;
public ImagePanel(boolean opaque)
{
super();
this.kit = Toolkit.getDefaultToolkit();
setLayout(null);
setOpaque(opaque);
setBackground(bgColor);
filterPanel = new JPanel();
filterPanel.setBackground(clear);
}
public ImagePanel(URL imagePath, boolean opaque)
{
super();
this.imagePath = imagePath;
this.kit = Toolkit.getDefaultToolkit();
setLayout(null);
setOpaque(opaque);
setBackground(bgColor);
filterPanel = new JPanel();
filterPanel.setBackground(clear);
readImage();
}
@Override
protected void paintComponent(Graphics g)
{
Graphics2D g2D = (Graphics2D) g;
if (highlight)
filterPanel.setBackground(blueFilter);
else
filterPanel.setBackground(clear);
int X = 0, Y = 0;
if (image != null)
{
image.flush();
kit.prepareImage(image, -1, -1, this);
if (customSize)
{
X = (panelSize.width - imgW) / 2;
Y = (panelSize.height - imgH) / 2;
}
if (isOpaque())
g2D.drawImage(image, X, Y, bgColor, this);
else
g2D.drawImage(image, X, Y, this);
}
else
super.paintComponent(g2D);
}
public void highlightImage(boolean highlight)
{
this.highlight = highlight;
}
private void readImage()
{
try
{
image = ImageIO.read(imagePath);
imgW = image.getWidth();
imgH = image.getHeight();
if (customSize)
panelSize = getPreferredSize();
else
panelSize = new Dimension(imgW, imgH);
setPreferredSize(panelSize);
setMinimumSize(panelSize);
setMaximumSize(panelSize);
int X = (panelSize.width - imgW) / 2;
int Y = (panelSize.height - imgH) / 2;
filterPanel.setBounds(X, Y, imgW, imgH);
add(filterPanel);
}
catch (IOException ex)
{
Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void setImageURL(URL img)
{
this.imagePath = img;
readImage();
}
public Dimension getDisplayedImageSize()
{
if (image == null)
return null;
return new Dimension(imgW, imgH);
}
public JPanel getFilterPanel()
{
return filterPanel;
}
public void useCustomSizing(int W, int H)
{
if (W < 0)
W = getPreferredSize().width;
if (H < 0)
H = getPreferredSize().height;
if ((W>0) || (H>0))
customSize = true;
Dimension cDim = new Dimension(W,H);
setPreferredSize(cDim);
setMinimumSize(cDim);
setMaximumSize(cDim);
repaint();
}
}//end class ImagePanel
回答1:
There are a cascade of problems presenting themselves...
- The over use of
null
layouts, I'll get back to that... - The use of a "overlay" panel
- Modification of a component from within the context of the a paint method
- Reliance of "magic" numbers instead of known values
- Breaking the paint chain...
All these things are conspiring against you...
The first thing you need to know is Swing only knows how to paint opaque or transparent components and only when those components are flagged as been opaque or not. If you use a color with an alpha value, Swing doesn't know that needs to paint under the component as well.
The second thing you need to know is that the Graphics
context is a shared resource. That is, every component been updated during a paint cycle get the SAME Graphics
context.
The MAIN problem is, your filterPanel
is using a alpha
color, but Swing doesn't know that it should be painting under it, so it simply "fills" the available area with the color you have chosen, but because it's a alpha color, it doesn't completely clean the Graphics
context, so you will end up with paint artifacts been left behind...
The fact is, you don't need the filterPane
or, more importantly, don't need to use it the way you are. You should NEVER update the state of any component from within a paint method, this will cause the component to request a repaint, every time, which will quickly consume your CPU cycles till you system won't run...
You could achieve a simular resulting using something like...
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
int X = 0, Y = 0;
if (image != null) {
if (customSize) {
X = (panelSize.width - imgW) / 2;
Y = (panelSize.height - imgH) / 2;
}
g2D.drawImage(image, X, Y, this);
}
if (highlight) {
g2D.setColor(blueFilter);
g2D.fillRect(X, Y, image.getWidth(), image.getHeight());
}
}
Take a look at Painting in AWT and Swing, Performing Custom Painting and 2D Graphics for more details.
The reliance on pre-calculated values could also cause you issues. The size of a component should be determined by using it's getWidth
and getHeight
properties as the values could have changed between paint cycles...
I would also encourage you to use ImageIO
over Toolkit
, it will...
- Return a full realised image instead of off loading the loading to a background thread
- Raise an exception if it can't read the file for some reason, rather the silently failing...
See Reading/Loading an Image for more details
This is a basic example of how I might approach the problem...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MouseOverTest {
public static void main(String[] args) {
new MouseOverTest();
}
public MouseOverTest() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
BufferedImage background = ImageIO.read(new File("C:\\hold\\thumbnails\\_MTCGAC__Pulling_Cords_by_Dispozition.png"));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(background));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public static class TestPane extends JPanel {
protected static final Color BLUE_FILTER = new Color(0, 0, 255, 38);
private BufferedImage background;
private Rectangle imageBounds;
private boolean mouseInTheHouse;
public TestPane(BufferedImage background) {
this.background = background;
MouseAdapter ma = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
mouseInTheHouse = getImageBounds().contains(e.getPoint());
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
mouseInTheHouse = false;
repaint();
}
};
addMouseMotionListener(ma);
addMouseListener(ma);
}
@Override
public Dimension getPreferredSize() {
return background == null ? new Dimension(200, 200) : new Dimension(background.getWidth(), background.getHeight());
}
@Override
public void invalidate() {
imageBounds = null;
super.invalidate();
}
protected Rectangle getImageBounds() {
if (imageBounds == null) {
if (background != null) {
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
imageBounds = new Rectangle(x, y, background.getWidth(), background.getHeight());
} else {
imageBounds = new Rectangle(0, 0, 0, 0);
}
}
return imageBounds;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = getImageBounds();
if (background != null) {
g2d.drawImage(background, bounds.x, bounds.y, this);
}
if (mouseInTheHouse) {
g2d.setColor(BLUE_FILTER);
g2d.fill(bounds);
}
g2d.dispose();
}
}
}
来源:https://stackoverflow.com/questions/26618566/when-i-paint-a-background-image-onto-a-jpanel-it-behaves-different-under-window