So, I want to Z-order some components in a JFrame
.
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);
}
}
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();
}
});
}
}
Then, I want to render out those component using the repaint()
invocation.
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.");
}
}
What should render on top/below should correspond to the Z-index of the component in the frame. (Component at index 1 should render on top of component an index 0, for example)
However, when the renderer (JPanel
) is added onto the window (JFrame
), and paintComponent
is invoked, nothing occurs. Literally nothing paints.
Commenting out the Z-order code in the main class makes it so that at least something does in fact paint, but red paints on top of blue (because in the PaintComponent method red is painted last, and thus on top), which is not what I want.
//window.setComponentZOrder(BLUEBox, 0);
//window.setComponentZOrder(REDBox, 0); //puts red on 0, moving blue up to 1.
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
?
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);
}
}
Ok, no. This is most defiantly not how you should be trying to perform custom painting or using custom components. You are not responsible for the rendering of the components, so your PanelRenderer
makes no sense, nor should you really be using static
this way, it's just lazy and error prone.
Either make it 100% custom painting or 100% component orientated, not both
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?
Because you're not responsible for painting the components and you are messing with the system
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);
}
}
}