Custom painting and Z-order:查询重绘顺序

Custom painting and Z-order: query re painting order

所以,我想对 JFrame 中的一些组件进行 Z 排序。

组件:

public class aBLUEBox extends JPanel{
    int xPos = 19;
    int yPos = 20;
    int width = 10;
    int height = 80;
    
    public void paintBox(Graphics g){
         g.setColor(Color.BLUE);
         g.fillRect(xPos,yPos,width,height);
    }
}

框架:

public class CreateWindow extends JFrame{
    CreateWindow(){
        this.setTitle("Layering Test");
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        this.setSize(1920/2,1080/2);
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        
        this.setVisible(true);
    }
}

将组件添加到 frame/Main class:

public class LayerMain {
    CreateWindow window;

    static aBLUEBox BLUEBox;
    static aREDBox REDBox; //A Different Component just like aBLUEBox, but with an altered PaintBox() method which paints a red box instead of a blue one.

    PanelRenderer RendererP;
    
    LayerMain(){
        BLUEBox = new aBLUEBox();
        REDBox = new aREDBox();
        
        RendererP = new PanelRenderer();  //holds the PaintComponent Method. Class for this is shown below.
        
        window = new CreateWindow();
        window.add(BLUEBox);
        window.add(REDBox);

        window.setComponentZOrder(BLUEBox, 0);
        window.setComponentZOrder(REDBox, 0); //puts red on 0, moving blue up to 1.
//So now, BLUEBox's Z-order is 1, thus BLUEBox is on top of REDBox.

        System.out.println("Z-order of blue = " + window.getComponentZOrder(BLUEBox)); //Prints 1
        System.out.println("Z-order of Red = " + window.getComponentZOrder(REDBox)); //Prints 0

        window.add(RendererP);

        RendererP.repaint();  //Should Paint both box's.
     }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() { 
                new LayerMain();
            }
        });
    }
}

然后,我想使用 repaint() 调用渲染这些组件。

渲染器:

public class PanelRenderer extends JPanel{

    public void paintComponent(Graphics g){ 
        super.paintComponent(g); 
        //JPanels:
        LayerMain.BLUEBox.paintBox(g);  //Paints Blue first, not that it should matter.
        LayerMain.REDBox.paintBox(g);   //Paints Red Second, not that it should matter.

        System.out.println("PaintComponent invoked.");
    }
}

top/below 上应呈现的内容应对应于框架中组件的 Z 索引。 (例如,索引 1 处的组件应在索引 0 的组件顶部呈现)

但是,当渲染器 (JPanel) 添加到 window (JFrame) 并调用 paintComponent 时,没有任何反应。字面上什么都不画。

注释掉主要 class 中的 Z-order 代码使得至少某些东西确实在绘制,但是红色绘制在蓝色之上(因为在 PaintComponent 方法中最后绘制红色,因此在上面),这不是我想要的。

//window.setComponentZOrder(BLUEBox, 0);
//window.setComponentZOrder(REDBox, 0); //puts red on 0, moving blue up to 1.

为什么组件按照paintComponent中调用的顺序显示,而不是JFrame中设置的顺序显示?

MRE / SSCCE

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

public class LayerMain {
    CreateWindow window;

    static ColoredBox blueBox;
    //A Different Component just like aBLUEBox, but with an altered 
    // PaintBox() method which paints a red box instead of a blue one.
    static ColoredBox redBox; 

    PanelRenderer rendererP;

    LayerMain(){
        blueBox = new ColoredBox(Color.BLUE);
        redBox = new ColoredBox(Color.RED);

        //holds the PaintComponent Method. Class for this is shown below.
        rendererP = new PanelRenderer();  

        window = new CreateWindow();
        window.add(blueBox);
        window.add(redBox);

        window.setComponentZOrder(blueBox, 0);
        window.setComponentZOrder(redBox, 0); //puts red on 0, moving blue up to 1.
        //So now, blueBox's Z-order is 1, thus blueBox is on top of redBox.

        System.out.println("Z-order of blue = " + window.getComponentZOrder(blueBox)); //Prints 1
        System.out.println("Z-order of Red = " + window.getComponentZOrder(redBox)); //Prints 0

        window.add(rendererP);

        rendererP.repaint();  //Should Paint both box's.
     }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new LayerMain();
        });
    }
}

class PanelRenderer extends JPanel{

    @Override
    public void paintComponent(Graphics g){ 
        super.paintComponent(g); 
        //JPanels:
        LayerMain.blueBox.paintBox(g);  //Paints Blue first, not that it should matter.
        LayerMain.redBox.paintBox(g);   //Paints Red Second, not that it should matter.

        System.out.println("PaintComponent invoked.");
    }
}

class CreateWindow extends JFrame{
    CreateWindow(){
        this.setTitle("Layering Test");
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);

        this.setSize(1920/2,1080/2);
        this.setLocationRelativeTo(null);
        this.setResizable(false);

        this.setVisible(true);
    }
}

class ColoredBox extends JPanel {
    int xPos = 19;
    int yPos = 20;
    int width = 10;
    int height = 80;
    Color color;
    
    ColoredBox(Color color) {
        super();
        this.color = color;
    }

    public void paintBox(Graphics g){
         g.setColor(color);
         g.fillRect(xPos,yPos,width,height);
    }
}

好的,不。这绝对不是您应该尝试执行自定义绘画或使用自定义组件的方式。您不负责组件的呈现,因此您的 PanelRenderer 没有意义,您也不应该真正以这种方式使用 static,它只是懒惰且容易出错。

要么让它 100% 定制绘画,要么 100% 面向组件,不能两者兼而有之

Why do the components display in the order that they are called within the PaintComponent, and not displayed in the order that they are set to within the JFrame?

因为你不负责绘制组件而且你在搞乱系统

Component/ZOrder 例子

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JPanel blueBox;
        private JPanel redBox;

        private JPanel top, bottom;

        public TestPane() {
            blueBox = makeBox(Color.BLUE);
            redBox = makeBox(Color.RED);

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            // Filler
            add(emptyBox(), gbc);

            gbc.gridwidth = 2;
            gbc.gridheight = 2;
            gbc.fill = GridBagConstraints.BOTH;
            add(blueBox, gbc);

            gbc.gridx++;
            gbc.gridy++;
            add(redBox, gbc);

            // Filler
            gbc.gridx++;
            gbc.gridy++;
            gbc.gridwidth = 1;
            gbc.gridheight = 1;
            gbc.fill = GridBagConstraints.NONE;
            add(emptyBox(), gbc);

            JButton flip = new JButton("Flip");
            flip.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    setComponentZOrder(bottom, 0);
                    JPanel temp = top;
                    top = bottom;
                    bottom = temp;
                    repaint();
                }
            });

            gbc.gridx = 0;
            gbc.gridy = 4;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            add(flip, gbc);

            top = blueBox;
            bottom = redBox;
        }

        protected JPanel emptyBox() {
            JPanel box = new JPanel() {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(50, 50);
                }
            };
            box.setOpaque(false);
            return box;
        }

        protected JPanel makeBox(Color color) {
            JPanel box = new JPanel() {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(100, 100);
                }
            };
            box.setBackground(color);
            return box;
        }

    }

}

自定义绘画

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<Box> boxes = new ArrayList<Box>(2);

        public TestPane() {
            boxes.add(new Box(Color.BLUE, 0, 0));
            boxes.add(new Box(Color.RED, 50, 50));

            setLayout(new BorderLayout());

            JButton flip = new JButton("Flip");
            flip.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Ok, this is a cheat as I only have to boxes,
                    // if you had more, you'd need to move the box you 
                    // higher in the rendering order closer to the end 
                    // of the list
                    Collections.reverse(boxes);
                    repaint();
                }
            });
            add(flip, BorderLayout.SOUTH);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(150, 150);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            // Cheat, I should looping over the box array to calculate the
            // area the boxes actuall occupy
            int x = (getWidth() - 150) / 2;
            int y = (getHeight() - 150) / 2;

            Graphics2D g2d = (Graphics2D) g.create();
            g2d.translate(x, y);

            for (Box box : boxes) {
                box.paint(g2d);
                x += 50;
                y += 50;
            }
            g2d.dispose();
        }
    }

    public class Box {

        private Color color;

        private int x, y;

        public Box(Color color, int x, int y) {
            this.color = color;
            this.x = x;
            this.y = y;
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(color);
            g2d.fillRect(x, y, 100, 100);
        }
    }

}