Search code examples
javaswingjpanelfocusjlayeredpane

Move jcomponent to back without using JLayeredPane


I have written a DragAndDrop MouseListener.

Component A is a "background" image that is behind Component B. Both are located on a JPanel.

I have made the image draggable. However, I want the image to remain behind component B as I drag it.

However, every time I drag the image, I suppose Java gives it focus or something, so it gets brought to the forefront.

Is there a method that can keep the image in the back even as I am dragging it?

I know I can use a JLayeredPane and use the moveToBack method every time I drag, but I would rather not use a JLayeredPane and just use a JPanel. Is there a moveToBack equivalent for JPanel?

Or is there a way to make the component preserve the current layer (maybe "not gain focus") so that I can drag it within its current layer?

HERE IS AN EXAMPLE

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class OverlapTester extends JFrame {
public static final long serialVersionUID = 172L;



public static void main(String[] args) {
    
    
    OverlapTester frame = new OverlapTester();
    
    frame.initialize();

    
}

public void initialize() {
    setLayout(null);
    
    JButton bottom = new JButton("bottom");
    JButton top = new JButton("top");
    bottom.setBounds(0,0,100,100);
    top.setBounds(0,0,50,50);
    
    add(top);
    add(bottom);
    
    int bottomZOrder = 0;

    
    bottom.addMouseListener(new MouseListener(){
        @Override
        public void mouseEntered(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
        @Override
        public void mouseExited(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
        @Override
        public void mouseReleased(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
        @Override
        public void mousePressed(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
        @Override
        public void mouseClicked(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
    });
    
    bottom.addMouseMotionListener(new MouseMotionListener(){
        @Override
        public void mouseDragged(MouseEvent e) {
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
        
        @Override
        public void mouseMoved(MouseEvent e) { 
            e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
        }
    });
    
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setExtendedState(MAXIMIZED_BOTH);
    setVisible(true);
}





}

Solution

  • Component A is a "background" image that is behind Component B. Both are located on a JPanel.

    Swing components are painted based on their ZOrder. The highest ZOrder is painted first.

    The ZOrder is assigned as a component is added to the panel. So the first component added is given ZOrder 0, and the second component ZOrder 1.

    So add your "background" image to the panel last and it will always have the highest ZOrder which means it is painted first, so other components will be painted on top of it.

    For example:

    panel.add(someOtherComponent);
    panel.add(background);
    

    Or you can use the:

    Container.setComponentZOrder(...)
    

    method to change the ZOrder dynamically after a component has been added.

    Edit:

    The answer I provided was you was an either/or solution. You only need to choose one approach.

    1. You implemented the first approach correctly, which is to add the "background" component last. So there is no need to then play with the ZOrder. This approach works great when ZOrder will remain fixed.

    2. The second approach is when you want the ZOrder to dynamically change, say when you have multiple components and you want to change it as you interact with each component. However you implemented this approach incorrectly. You used "0" for the ZOrder, which means the component will always be painted last and therefore on on top of any other component.

    In your current example there is no need to change the ZOrder dynamically, so you can remove all related code.

    As you can see the bottom button is consistently being added to the forefront

    Swing painting is optimized to assume components don't overlap.

    However, in the case of a JButton, it has automatic repaint logic when you hover over the button to repaint the Border. So when the button is repainted it still paints over top of the other component.

    So you need to tell Swing to NOT optimize the painting. Therefore when one component is repainted they will all be repainted when they overlap. You do this by overriding the isOptimizedDrawingEnabled(...) method as demonstrated below:

    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    import java.awt.event.MouseMotionListener;
    
    import javax.swing.JButton;
    import javax.swing.*;
    
    public class OverlapTester extends JFrame {
    
    public static void main(String[] args) {
    
        OverlapTester frame = new OverlapTester();
    
        frame.initialize();
    
    }
    
    public void initialize() {
    
        JPanel contentPane = new JPanel(null)
        {
            @Override
            public boolean isOptimizedDrawingEnabled()
            {
                return false;
            }
        };
        add(contentPane);
    
    //    setLayout(null);
    
        JButton bottom = new JButton("bottom");
        JButton top = new JButton("top");
        bottom.setBounds(0,0,100,100);
        top.setBounds(0,0,50,50);
    
    //    add(top);
    //    add(bottom);
        contentPane.add(top);
        contentPane.add(bottom);
    
    //    int bottomZOrder = 0;
        int bottomZOrder = 1;
    
    
        bottom.addMouseListener(new MouseListener(){
            @Override
            public void mouseEntered(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
            @Override
            public void mouseExited(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
            @Override
            public void mouseReleased(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
            @Override
            public void mousePressed(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
            @Override
            public void mouseClicked(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
        });
    
        bottom.addMouseMotionListener(new MouseMotionListener(){
            @Override
            public void mouseDragged(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
    
            @Override
            public void mouseMoved(MouseEvent e) {
                e.getComponent().getParent().setComponentZOrder(e.getComponent(), bottomZOrder);
            }
        });
    
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setExtendedState(MAXIMIZED_BOTH);
        setVisible(true);
    }
    
    
    }