Swing JLayeredPane 未在 paintAll 调用上绘制所有元素

Posted

技术标签:

【中文标题】Swing JLayeredPane 未在 paintAll 调用上绘制所有元素【英文标题】:Swing JLayeredPane not painting all elements on paintAll call 【发布时间】:2016-03-22 06:35:17 【问题描述】:

我有JLayeredPane,它在0级包含Canvas(在Paint方法中填充自己的黄色)和JPanel在1级(在构造函数中将它的背景设置为红色)。

在按钮上单击paintAllToImage 方法我创建BufferedImage 并使用component.paintAll(image.getGraphics()); 在此图像上绘制JLayerePane 问题是,该图像只有Canvas 绘制(它完全填充为黄色)。请看附图。

(按钮上方是实际绘制的,按钮下方是图像,由JLayeredPane创建)

这里是完整的代码:

public class LayeredPaneEx extends JPanel 

    private JLayeredPane layeredPane;
    public LayeredPaneEx()    
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));
        layeredPane.setLayout(null);

        Canvas panel = new CustomCanvas();
        panel.setSize(300, 400);
        CustomPanel customPanel = new CustomPanel();
        layeredPane.add(panel, new Integer(0));
        layeredPane.add(customPanel, new Integer(1));

        add(layeredPane);

        JButton paintBtn = new JButton("Paint All");
        paintBtn.addActionListener(new ActionListener() 
            @Override
            public void actionPerformed(ActionEvent e) 
                ImageIcon icon = new ImageIcon(paintAllToImage(layeredPane));
                JLabel imageLabel = new JLabel(icon);
                add(imageLabel);
            
        );
        add(paintBtn);

        JLabel paintLabel = new JLabel();
        paintLabel.setPreferredSize(new Dimension(300, 300));
    

    private class CustomCanvas extends Canvas  
        @Override
        public void paint(Graphics g) 
            g.setColor(Color.YELLOW);
            g.fillRect(0, 0, getWidth(), getHeight());
        
    

    private class CustomPanel extends JPanel 
        CustomPanel() 
            setSize(100, 100);
            setBackground(Color.RED);
        
    

    private static void createAndShowGUI() 
        JFrame frame = new JFrame("LayeredPaneDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JComponent newContentPane = new LayeredPaneEx();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    

    public static void main(String[] args) 
        javax.swing.SwingUtilities.invokeLater(new Runnable() 
            public void run() 
                createAndShowGUI();
            
        );
    

    public static BufferedImage paintAllToImage(Component component) 
        BufferedImage image;
        image = new BufferedImage(
                component.getWidth(),
                component.getHeight(),
                BufferedImage.TYPE_INT_RGB
        );
        component.paintAll(image.getGraphics());
        return image;
    

【问题讨论】:

谨防将重量级 (Canvas) 与重量轻的组件混合在一起。因为 AWT 组件没有 z-ordering 的概念,您会发现这将导致您无穷无尽的问题。此外,您应该更喜欢 printAll 而不是 paintAll 【参考方案1】:

编辑:新答案

通过改编这个Stack Overflow answer,似乎可以将轻量级CustomPanel 放在重量级Panel 中,并将它们放在另一个重量级Panel 之上。截图如下:

这是程序:

import java.awt.*;
import javax.swing.*;

/**
 * Adapted from https://***.com/a/1428298/1694043.
 */
public class GuiTest 
    public static void main(String[] arguments) 
        new GuiTest();
    

    public GuiTest() 
        JFrame frame = new JFrame("Heavyweight versus lightweight");
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        addPanelsToFrame(frame);

        SwingUtilities.invokeLater(() -> frame.setVisible(true));
    

    private void addPanelsToFrame(JFrame frame) 
        CustomCanvas customCanvas = new CustomCanvas(300, 400, Color.YELLOW);
        Panel awtPanel1 = new Panel(new BorderLayout());
        awtPanel1.setSize(300, 400);
        awtPanel1.add(customCanvas, BorderLayout.CENTER);
        frame.getLayeredPane().add(awtPanel1, JLayeredPane.DEFAULT_LAYER);

        CustomPanel customPanel = new CustomPanel(100, 100, Color.RED);
        Panel awtPanel2 = new Panel(new BorderLayout());
        awtPanel2.setSize(100, 100);
        awtPanel2.add(customPanel, BorderLayout.CENTER);
        frame.getLayeredPane().add(awtPanel2, JLayeredPane.PALETTE_LAYER);
    


    private class CustomCanvas extends Canvas 
        private Color backgroundColor;

        public CustomCanvas(int width, int height, Color backgroundColor) 
            setSize(width, height);
            this.backgroundColor = backgroundColor;
        

        @Override
        public void paint(Graphics g) 
            g.setColor(backgroundColor);
            g.fillRect(0, 0, getWidth(), getHeight());
        
    


    private class CustomPanel extends JPanel 
        public CustomPanel(int width, int height, Color backgroundColor) 
            setSize(width, height);
            setBackground(backgroundColor);
        
    


旧答案

使用MadProgrammer 的建议来避免Canvas 类,您可以使用CustomPanel 类的两个实例。此类扩展了基于 Swing 的轻量级 JPanel,而不是基于重量级 AWT 的 Canvas。有关轻量级和重量级 Java GUI 组件的更多信息,请参阅 https://***.com/a/13769255/1694043。

这是截图:

这里是修改后的代码:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class LayeredPaneEx extends JPanel 
    private JLayeredPane layeredPane;

    public LayeredPaneEx()    
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));
        layeredPane.setLayout(null);

        //Canvas panel = new CustomCanvas();
        //panel.setSize(300, 400);
        //CustomPanel customPanel = new CustomPanel();
        //layeredPane.add(panel, new Integer(0));
        //layeredPane.add(customPanel, new Integer(1));
        layeredPane.add(new CustomPanel(300, 400, Color.YELLOW), new Integer(0));
        layeredPane.add(new CustomPanel(100, 100, Color.RED), new Integer(1));

        add(layeredPane);

        JButton paintBtn = new JButton("Paint All");
        paintBtn.addActionListener(new ActionListener() 
            @Override
            public void actionPerformed(ActionEvent e) 
                ImageIcon icon = new ImageIcon(paintAllToImage(layeredPane));
                JLabel imageLabel = new JLabel(icon);
                add(imageLabel);
            
        );
        add(paintBtn);

        JLabel paintLabel = new JLabel();
        paintLabel.setPreferredSize(new Dimension(300, 300));
    

//    private class CustomCanvas extends Canvas  
//        @Override
//        public void paint(Graphics g) 
//            g.setColor(Color.YELLOW);
//            g.fillRect(0, 0, getWidth(), getHeight());
//        
//    

    private class CustomPanel extends JPanel 
        public CustomPanel(int width, int height, Color backgroundColor) 
            setSize(width, height);
            setBackground(backgroundColor);
        
    

    private static void createAndShowGUI() 
        JFrame frame = new JFrame("LayeredPaneDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JComponent newContentPane = new LayeredPaneEx();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    

    public static void main(String[] args) 
        SwingUtilities.invokeLater(new Runnable() 
            public void run() 
                createAndShowGUI();
            
        );
    

    public BufferedImage paintAllToImage(Component component) 
        BufferedImage image = new BufferedImage(
                component.getWidth(),
                component.getHeight(),
                BufferedImage.TYPE_INT_RGB
        );
        component.paintAll(image.getGraphics());

        return image;
    

【讨论】:

谢谢,那肯定行得通,但是如果我必须在 Canvas 上绘制我的 JPanel 怎么办?正如“混合重量级和轻量级组件”Oracle 文章所说,Java 8(我正在使用)应该没有问题。虽然在这种情况下 Canvas 是在 JPanel 之上绘制的。 我的建议是使用两个轻量级面板。那么哪个面板在顶部应该无关紧要。在我看来,没有理由在这个程序中使用重量级组件。如果要绘制,可以覆盖面板的paintComponent方法。 一个很好的Swing自定义绘画教程:Performing Custom Painting. 遗憾的是,这个程序只是一个真实任务的沙盒版本,CanvasJPanel 重叠在一个 Container 中。 我了解,您有遗留问题。用JPanel 替换Canvas 组件有多难?

以上是关于Swing JLayeredPane 未在 paintAll 调用上绘制所有元素的主要内容,如果未能解决你的问题,请参考以下文章

Applet 和 Swing 未在 Eclipse 中显示组件

Java Swing - 如何在另一个面板上显示一个面板?

在java中面板和框架啥区别?

Java:为不同层组件定义mouseAdapter的jLayeredPane问题

尝试使用 JLayeredPane 拖动某些东西

在现有的 JPanel 上绘制 JLayeredPane