简体   繁体   English

当我在JPanel上绘制背景图像时,它在Windows下的行为与在Linux下的行为不同

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

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. 在应用程序中,我具有对其应用了背景图像的JPanels。 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. 为此,我将透明的JPanel用背景图像覆盖在JPanel的顶部,并附加了MouseListener,以取消mouseEntered / Exited事件。 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. 在Linux下,这非常有效。 Under Windows... it's a good thing I'm bald so I can't tear my hair out. 在Windows下...光头是一件好事,所以我不能拔掉头发。 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; 似乎正在发生的事情是,当我四处移动鼠标时,正在发生某种图像缓存,而mouseEnter事件导致一秒钟之前的鼠标被绘制到框架中。 ie 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. 例如,如果我将鼠标放在GUI上附近的按钮上,然后再将鼠标放在面板上,我会看到该按钮以及周围的GUI出现在面板上。

The Image Panel is contained within a JInternalFrame that is opaque. 图像面板包含在不透明的JInternalFrame中。

One other thing to note, if I do something in the application that would cause the image to change (eg making a new selection from a JComboBox), the image repaints as expected. 需要注意的另一件事是,如果我在应用程序中执行某些操作会导致图像发生更改(例如,从JComboBox中进行新选择),则图像将按预期重新绘制。 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? 我不应该为Windows做些什么?

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. 面板打开时,Start.png就是两个操作系统的外观。 GoodMouseEnter.png is what the mouseEnter event looks like under Linux. 在Linux下,mouseEnter事件的外观类似于GoodMouseEnter.png。 BadMouseEnter.png & badMouseExit.png are what it looks like under Windows. BadMouseEnter.png和badMouseExit.png是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

There are a cascade of problems presenting themselves... 出现了一系列问题……

  1. The over use of null layouts, I'll get back to that... 过度使用null布局,我将回到这一点...
  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. 您需要了解的第一件事是Swing仅知道如何绘制不透明或透明组件,并且仅当这些组件被标记为不透明时才知道。 If you use a color with an alpha value, Swing doesn't know that needs to paint under the component as well. 如果您使用具有Alpha值的颜色,Swing也不知道该颜色是否也需要在组件下方绘制。

The second thing you need to know is that the Graphics context is a shared resource. 您需要知道的第二件事是Graphics上下文是共享资源。 That is, every component been updated during a paint cycle get the SAME Graphics context. 也就是说,在绘制周期中更新的每个组件都将获得SAME Graphics上下文。

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... 主要问题是,您的filterPanel使用的是alpha颜色,但是Swing不知道它应该在其下方绘制,因此它只是用您选择的颜色“填充”可用区域,但是由于它是Alpha颜色,它不能完全清除Graphics上下文,因此最终会留下一些绘画瑕疵...

The fact is, you don't need the filterPane or, more importantly, don't need to use it the way you are. 事实是,您不需要filterPane或更重要的是,您无需按filterPane使用它。 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... 您永远不要从paint方法中更新任何组件的状态,这将导致该组件每次都请求重新绘制,这将迅速消耗您的CPU周期,直到您的系统无法运行为止。

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. 看一看AWT和Swing中的 绘画执行自定义绘画2D图形以了解更多详细信息。

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... 组件的大小应该通过使用其getWidthgetHeight属性来确定,因为这些值可能在绘制周期之间发生了变化...

I would also encourage you to use ImageIO over Toolkit , it will... 我也鼓励您使用ImageIO over Toolkit ,它将...

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

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM