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

标签 java swing paintcomponent drawimage

我正在开发一个工作程序(注意:我无法共享完整的代码,因为它在很大程度上是 protected 工作产品,但我会分享我能分享的一切)。在应用程序中,我有 JPanel,它们应用了背景图像。其中一些面板还附加了鼠标监听器,我的管理层希望有一个视觉线索,表明可以单击面板来启动操作。为此,我在带有背景图像的 JPanel 顶部覆盖了一个透明 JPanel,并为其附加了一个 MouseListener,从而关闭 mouseEntered/Exited 事件。当鼠标进入图像面板时,覆盖的面板将从透明切换为半透明,当鼠标退出时又返回。

在 Linux 下,这工作得很好。在Windows下……幸好我是秃头,所以我不能把头发扯下来。似乎正在发生的事情是,当我移动鼠标时,正在发生某种图像缓存,并且 mouseEnter 事件导致鼠标一秒前的任何内容都被绘制到框架中;即,如果我将鼠标放在 GUI 上附近的按钮上,然后将鼠标放在面板上,我将看到该按钮与周围的 GUI 一起出现在面板中。

图像面板包含在不透明的 JInternalFrame 中。

另一件事需要注意,如果我在应用程序中执行某些操作会导致图像发生更改(例如,从 JComboBox 中进行新选择),图像将按预期重新绘制。无论问题是什么,它似乎与突出显示以及如何重新绘制/重绘图像有关。

我没有为 Windows 做哪些我应该做的事情?

提前致谢。

Here is a link to some images 。 Start.png 是面板打开时在两个操作系统中的样子。 GoodMouseEnter.png 是 Linux 下 mouseEnter 事件的样子。 BadMouseEnter.png 和 badMouseExit.png 是 Windows 下的样子。

这是我用来创建图像面板的类(编辑:此示例是独立的):

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. 过度使用 null 布局,我会回过头来讨论...
  2. 使用“覆盖”面板
  3. 在 Paint 方法的上下文中修改组件
  4. 依赖“神奇”数字而不是已知值
  5. 打破油漆链...

所有这些事情都在密谋反对你......

您需要知道的第一件事是 Swing 只知道如何绘制不透明或透明组件,并且仅当这些组件被标记为不透明时才知道。如果您使用带有 alpha 值的颜色,Swing 不知道它也需要在组件下绘制。

您需要知道的第二件事是 Graphics 上下文是共享资源。也就是说,在绘制周期期间更新的每个组件都会获得相同的图形上下文。

主要问题是,您的 filterPanel 使用 alpha 颜色,但 Swing 不知道它应该在其下方绘制,因此它只是“填充”具有您选择的颜色的可用区域,但因为它是 alpha 颜色,所以它不会完全清理 Graphics 上下文,因此您最终会留下绘画伪影...

事实是,您不需要 filterPane,或者更重要的是,不需要按您现在的方式使用它。您永远不应该从绘制方法中更新任何组件的状态,这将导致组件每次都请求重新绘制,这将快速消耗您的 CPU 周期,直到您的系统无法运行...

您可以使用类似的方法获得类似的结果...

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

}

看看Painting in AWT and Swing , Performing Custom Painting2D Graphics了解更多详情。

对预先计算值的依赖也可能会给您带来问题。组件的大小应通过使用其 getWidthgetHeight 属性来确定,因为这些值可能在绘制周期之间发生变化...

我还鼓励您使用 ImageIO 而不是 Toolkit,它会...

  1. 返回完整实现的图像,而不是将加载卸载到后台线程
  2. 如果由于某种原因无法读取文件,则引发异常,而不是默默地失败......

参见Reading/Loading an Image了解更多详情

这是我如何解决该问题的基本示例......

Mouse in the house

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

    }

}

关于java - 当我在 JPanel 上绘制背景图像时,它在 Windows 下的行为与在 Linux 下的行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26618566/

相关文章:

java - 使用 SpEL 在 Spring 中引用当前 bean

java - 动态更改 JTable 中的列标题文本

java - 在 JFrame(Panel) 中加载 .Jar Applet

java swing-转义键事件在外观上导致 classCastException

java - 无法在我的图片上画出三角形

java - 如何在Eclipse中进行多行搜索?

java - 检测 JDBC 连接中尚未提交的打开事务

使用执行器服务的Java异步方法调用

java - 我怎样才能让这个 Paint 组件按照我的预期行事?

java - 想要在同一个类中创建 JFrame 和 JPanel 并拥有自己的 PaintComponent?