When I paint a background image onto a JPanel, it behaves different under Windows than it does under Linux

不羁岁月 提交于 2019-12-23 02:01:21

问题


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...

  1. The over use of null layouts, I'll get back to that...
  2. The use of a "overlay" panel
  3. Modification of a component from within the context of the a paint method
  4. Reliance of "magic" numbers instead of known values
  5. 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...

  1. Return a full realised image instead of off loading the loading to a background thread
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!