3

I just can't get past square one on JLayeredPanes. (See my original question of yesterday. I have been studying the JLayeredPane tutorial and API. These tutorials are geared somewhat differently to what I am ultimately trying to produce.

Going back to square one, I took Oracle's JFrame Example and modified it to include Layered panes.

Here is the code:

package components;

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

/* FrameDemo.java requires no other files. */
public class FrameDemo {
    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("FrameDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel mainLayer = new JPanel(new BorderLayout());
        mainLayer.setPreferredSize(new Dimension(640, 480));
        frame.setContentPane(mainLayer);
        frame.getLayeredPane().add(mainLayer, JLayeredPane.DEFAULT_LAYER, 0);

        JLabel emptyLabel = new JLabel("LABEL");
        emptyLabel.setPreferredSize(new Dimension(320, 240));
        mainLayer.add(emptyLabel, BorderLayout.NORTH);

        JPanel subLayer = new JPanel(new BorderLayout());
        JLabel subLabel = new JLabel("SUBLABEL");
        subLabel.setPreferredSize(new Dimension( 200, 100));
        subLabel.setBackground(Color.YELLOW);
        subLayer.add(subLabel, BorderLayout.SOUTH);
        subLayer.setVisible(true);
        subLabel.setVisible(true);
        frame.getLayeredPane().add(subLayer, JLayeredPane.PALETTE_LAYER, 0);


        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

Why doesn't it work? IOW, why doesn't the sublabel show up? It's at a higher level than the main layer.

One thought is why am I adding mainLayer to both the Content Pane and the Layered Pane? If I don't do that, nothing shows up. I.e, by commenting out this line, I just get a blank frame.

//        frame.setContentPane(mainLayer);

Obviously, I'm not understanding something. But what is it?

I should add that obviously, this simple demo can be done without Layered Panes. But my ultimate goal is to have a layer that can be turned on and off programatically. But I can't even get this simple case to work. If I can get over this hump, I think the rest will be easier.

ADDENDUM:

What I want to acheive is illustrated by the following Code, which is very similar to what TrashGod set up below and it works. There is a JLayeredPane with a constant layer (layered at Integer(0)) and a floating layer layered initially at Integer(-1) but togglable by the F7 and F8 keystrokes between the Integer(-1) layer and the Integer(1) layer, thereby allowing it to float above or below the constant layer.

package components;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;


/* MyLayeredPaneDemo.java requires no other files. */
public class MyLayeredPaneDemo {
    private JFrame frame;
    private JLayeredPane mainPanel;
    private JPanel constantLayer;
    private JPanel floatingLayer;
    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private MyLayeredPaneDemo() {}
    private void createAndShowGUI() {
        //Create and set up the window.
        this.frame = new JFrame("MyLayeredPaneDemo");
        this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.frame.setPreferredSize(new Dimension(640, 480));
        mainPanel = new JLayeredPane();
        constantLayer = new JPanel(new BorderLayout(0,0));
        floatingLayer = new JPanel(new BorderLayout(0,0));
//        constantLayer.setPreferredSize();
        constantLayer.setOpaque(true);
        constantLayer.setBackground(Color.BLUE);


        JLabel constantLabel = new JLabel("MAIN LAYER");
        constantLayer.setPreferredSize(new Dimension(640, 480));
        constantLayer.add(constantLabel, BorderLayout.CENTER);

        JLabel subLabel = new JLabel("SUB LAYER");
        floatingLayer.setBackground(Color.YELLOW);
        floatingLayer.add(subLabel, BorderLayout.SOUTH);
        floatingLayer.setOpaque(true);
        floatingLayer.setVisible(true);
        floatingLayer.setVisible(true);
        subLabel.setBackground(Color.YELLOW);

        mainPanel.add(constantLayer, new Integer(0), 0);
        constantLayer.setBounds(0,0,640,480);
        mainPanel.add(floatingLayer, new Integer(-1), 0);
        floatingLayer.setBounds(100, 360, 300, 90 );

        frame.add(mainPanel, BorderLayout.CENTER);

        //Display the window.
        mapKeyToAction(frame.getRootPane(), 
                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
                KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0),
                "Hide Layer", 
                new AbstractAction() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        System.out.println("F7 pressed");
                        mainPanel.setLayer(floatingLayer, new Integer(-1));
                    }       
                }); 
        mapKeyToAction(frame.getRootPane(), 
                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
                KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
                "Show Layer", 
                new AbstractAction() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        System.out.println("F8 pressed");
                        mainPanel.setLayer(floatingLayer, new Integer(1));
                    }       
                }); 
        frame.pack();
        frame.setVisible(true);
        frame.getRootPane().setFocusable(true);
        boolean ok = frame.getRootPane().requestFocusInWindow();
        System.out.println("focus ok: " + ok);

    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MyLayeredPaneDemo().createAndShowGUI();
            }
        });
    }

    private static void mapKeyToAction(JComponent component, 
            int whichMap, KeyStroke keystroke,String key, Action action) {
        component.getInputMap(whichMap).put(keystroke, key);
        component.getActionMap().put(key, action);
    }

}

However, I am having trouble getting this to work in my real case. The difference between the two is that here, my JLayeredPane is owned by the Frame, whereas in my real application, I want the JLayeredPane to be owned by a JPanel is that some levels down in the containment hierarchy from the Frame, and whose size is set by a GridBagLoyout in its parent, and the size is therefore unknowable at the time its constructor is called, making it difficult to call setBounds() which I need to do on a child of a JLayeredPane.

FURTHER ADDENDUM. I know that the Oracle Tutorials mention a case where Layouts rather than absolute positioning is used with a JLayeredPane. The difference between this case and mine is that in my case the layers occupy the same horizontal space on different layers, whereas in this case, the components on different layrers occupy different horizontal spaces. It's almost as if we need a 3D Layout Manager!

Community
  • 1
  • 1
Steve Cohen
  • 4,679
  • 9
  • 51
  • 89
  • A further comment. I've looked at Oracle's RootLayeredPaneDemo tutorial. This is a very confusing demo. The frame passes its Layered Pane to the main class which is a JPanel that becomes the frame's content pane. Several components are then added to that layered pane in the constructor of this JPanel even though there is no containment of the layered pane within the main (JPanel) class. It seems that my code above is following the containment model of the RootLayeredPaneDemo, but it isn't. Does the order in which things are instantiated, added to containers, and made visible matter? – Steve Cohen Jul 17 '12 at 19:02
  • I don't have time right to o detailed anlysis, but one thing t jumped out at me is, you set the maimLayer as the content, then added it to frames layered pane. A component can only have one parent, this effectively removed the content psne – MadProgrammer Jul 17 '12 at 20:02
  • This comment is quite right. That got me off the dime. Unfortunately, I continue to have issues with JLayeredPanes which I will describe here or in a new issue shortly. – Steve Cohen Jul 19 '12 at 17:02

2 Answers2

4

"By default, a layered pane has no layout manager."—How to Use Layered Panes

Addendum: I need to avoid using the Frame's layered pane and instead add a layered pane to the window.

Yes, The Root Pane is an instance of JRootPane, which contains a JLayeredPane. In particular, "The layered pane contains the menu bar and content pane, and enables Z-ordering of other components."

enter image description here

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

public class FrameDemo {

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

        JLayeredPane mainLayer = new JLayeredPane();
        frame.add(mainLayer, BorderLayout.CENTER);

        JLabel label = new JLabel("LABEL", JLabel.CENTER);
        label.setBounds(100, 100, 200, 100);
        label.setOpaque(true);
        label.setBackground(Color.cyan);
        mainLayer.add(label, 1);

        JPanel subLayer = new JPanel(new BorderLayout());
        JLabel subLabel = new JLabel("SUBLABEL", JLabel.CENTER);
        subLabel.setBounds(20, 20, 200, 100);
        subLabel.setOpaque(true);
        subLabel.setBackground(Color.yellow);
        subLayer.add(subLabel, BorderLayout.SOUTH);
        mainLayer.add(subLabel, 2);

        frame.pack();
        frame.setSize(320, 240);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

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

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for this interesting example. The main difference from mine seems to be that you ignore the frame's JLayeredPane and instead add a new JLayeredPane to the frame, and then add all the components to the layered pane instead of the frame, with appropriate indexes. This may be worth exploring. – Steve Cohen Jul 19 '12 at 17:14
  • I THOUGHT I had something working in my application in which I left the content pane alone and introduced a JLayeredPane with content that was switchable to Layers above or below the default level. This SEEMED to work, until I deployed it into my application, which updates from time to time due to activity of an external thread. What I'm finding to my great consternation, is that updates to content on the content pane will sometimes write over content on the layered pane even though the layered pane is on a higher layer than the content pane. – Steve Cohen Jul 19 '12 at 17:42
  • What I need to do, I think, is to avoid using the Frame's layered pane and instead add a layered pane to the window whose content is sometimes to be overlaid. All content in this window, on whatever layer, needs to be contained by the layered pane. It seems that to use LayeredPanes effectively every component that wants to overlay or be overlaid by other components needs to be contained by the layered pane. Would you agree? – Steve Cohen Jul 19 '12 at 17:44
  • Yes; I've elaborated above. Going forward, can you amplify on your goal? – trashgod Jul 19 '12 at 18:56
  • When embedding your layered pane, don't set the [preferred size](http://stackoverflow.com/q/7229226/230513), as demos often do; override `getPreferredSize()` and use a layout that respects preferred size. I often use `GridLayout` for this, `GridBagLayout` with default constraints is similar. – trashgod Jul 20 '12 at 00:19
  • Trashgod: The only reason I preferred my solution to yours is that I am embedding this functionality into an already established GUI that uses GridBagLayout with non-default constraints to size and position the window into which I am adding the layered pane. No Preferred Size is ever set for this component as we let the GridBagLayout assign whatever space is left over to this component. Deferring the setBounds() until the parent component is resized works like a charm. Phew! But thanks again for putting me on the right path. – Steve Cohen Jul 20 '12 at 14:43
1

The solution I came up and thanks to trashgod which I expect is good advice too is to implement ComponentListener and capture the component resize event. At that point you can get the actual bounds of the container and use it to set the actual bounds of the layer JPanels which are always in some fixed relation to the bounds of the component that contains them. It works.

Trashgod's solution would also work I believe but I have not tried it.

Steve Cohen
  • 4,679
  • 9
  • 51
  • 89