Java Programming, Lecture Notes # 87, Revised 07/01/98.
myJFrameObject.getContentPane().add(myChildComponent); |
The purpose of this lesson is to help you understand why you need to use getContentPane() or some similar method call to add components to a JFrame object, remove components from a JFrame object, or set the layout for a JFrame object.
Note that this also applies to container objects of type JInternalFrame
and JDialog as well.
The viewable area of Frame or a JFrame is the bounds minus the insets. Insets are used to account for the space covered by the borders and the values of the insets on all four sides are available by invoking the method named getInsets() on the object. |
An object of type JRootPane is automatically placed in the JFrame completely covering the viewable area of the JFrame object.
One way or another, if you want to place components in the JFrame, you must deal with the JRootPane object.
One way to deal with it, and this is the way that you will find recommended in many current discussions on the subject, is simply ignore the technical details and insert a call to the getContentPane() method between the reference to the JFrame and the add() method.
That is not my approach. My approach is to first understand why you need to do that, an in those cases where that is the appropriate thing to do, go ahead and do it.
In those cases where invoking getContentPane() is not the most appropriate thing to do, do the thing that is most appropriate.
Note: You will find much discussion in this lesson about some
components being on top of other components. These discussions apply
only to Swing lightweight components. Remember that heavyweight
components are always on top of lightweight (Swing) components regardless
of what the text in this lesson may say.
The instance of JRootPane that is automatically placed in a JFrame
object contains at least two other objects.
The layeredPane and the glassPane also fill the viewable area of the JFrame object. The glassPane is on top of the layeredPane, and as the name would imply, it is normally transparent.
Components on the layeredPane are visible through the glassPane, and mouse events are capable of penetrating the glassPane and reaching components on the layeredPane as long as the glassPane is transparent.
The layeredPane contains another component of type JPanel that we will refer to as the contentPane. This is the same pane that we are dealing with when we invoke the getContentPane() method on the JFrame object.
The layeredPane has a very interesting behavior.
In the same sense that we can add components to the contentPane, we can also add components to the layeredPane.
When we add components to the contentPane, they are all added at the same layer and components added first are painted on top of components added later.
However, when we add components to the layeredPane, we can specify the layer number that we want the component to be drawn on.
Components drawn on layers with high (algebraically signed) numbers are painted on top of components on layers with smaller (algebraically signed) numbers. (Large negative numbers are smaller than small positive numbers in this case with the smallest allowable layer number being -29,999..)
In addition, a variety of methods are available to move components from one layer to another at runtime. This gives us the ability to not only control the order in which components are painted initially, but also to modify that order later at runtime.
This layering concept is a much more powerful approach than simply inserting getContentPane() between the reference to the JFrame object and the invocation of the add() method. (Layering has a long history of beneficial results in computer graphics such as Computer Aided Design and game programming.)
So, where does the contentPane object that belongs to the layeredPane fit into this?
Components on the contentPane are painted behind all components
that may be added directly to the layeredPane. In fact, the
layer position for the contentPane is effectively layer number -30,000.
The smallest layer number that can be used to place a component directly
on a layer is -29,999 (we will place one there in our sample program later).
Unfortunately, the syntax for dealing with these panes is somewhat different
from what you are accustomed to. For example, the typical syntax
for adding a component to a container would look something like the following:
parentContainer.add(childObject); |
myJFrameObject.getContentPane().add(myChildComponent); myJFrameObject.getLayeredPane().add( myChildComponent, new Integer(5)); |
The same concepts apply when removing components, setting layout managers, etc.
The contentPane has a BorderLayout manager by default. The layeredPane has no layout manager (null) by default as you will see in the sample program that follows later in this lesson.
If a JMenuBar component is set on the JRootPane, it is
positioned along the upper edge
of the frame. The contentPane is adjusted in location and size
to fill the remaining
area.
If you examine the documentation for JLayeredPane, you will see
that the class has the following fields which are all public static final
Integer symbolic constants.
DEFAULT_LAYER - Object defining the Default layer. Equivalent to new
Integer(0).
PALETTE_LAYER - Object defining the Palette layer. Equivalent to new Integer(100). MODAL_LAYER - Object defining the Modal layer. Equivalent to new Integer(200). POPUP_LAYER - Object defining the Popup layer. Equivalent to new Integer(300). DRAG_LAYER - Object defining the Drag layer. Equivalent to new Integer(400). FRAME_CONTENT_LAYER - Object defining the Frame Content layer. This layer is normally only used to position the contentPane and menuBar components of JFrame. Equivalent to new Integer(-30000). |
Also, as mentioned earlier, this object provides the ability to add components at several layers. This is very useful when working with popup menus, dialog boxes, during dragging, or for any other situation in which you might want to separate graphic objects onto different layers for display purposes.
Also as alluded to earlier, the glassPane is always added as the first child of the JRootPane. This causes the glassPane to always be on the top of the stack. By default, the glassPane is not visible and is transparent. Thus, mouse events can normally penetrate the glassPane and impinge upon the components below it.
It is also possible to draw components on the glassPane (although I haven't been able to purposely do it as of 5/12/98). When this is done, those components shadow the components below them preventing mouse events from impinging on the components down below.
Components drawn on the glassPane will always be above all other lightweight components in the stacking order of components (but not over top of heavyweight components). Thus, the glassPane can be used to assure that such components as popup menus and tool tips are always on top of the other components on the screen.
As mentioned earlier, by default, the glassPane is not visible. Developers should use setVisible on the glassPane to control when the glassPane displays over the other children.
The custom, layout manager used by JRootPane insures that the
following is true:
|
So now we know that the JRootPane object contains an object of type JLayeredPane.
While JLayeredPane manages it's list of children like Container, it also allows for the definition of a several layers within itself.
Children in the same layer are managed exactly like the normal Container object. However, children in higher layers display above the children in lower layers. Each layer has a distinct integer number.
Apparently each Swing Component has a layer attribute (but I haven't been able to find out anything about it other than as described below).
There are at least three ways to set the layer attribute on a Component:
1. Passing an Integer object with a literal integer value during the add call:
Layers with higher numbers display on top of layers with lower numbers.
Higher and lower in this case includes the algebraic sign of the layer. For example, a large negative number is a lower layer than a small positive number.
These layers are simply a logical construct and LayoutManagers will affect all child components without regard for layer settings. Therefore, to take advantage of this layering capability, you may need to design your own layout manager or use absolute layout (null layout manager).
We will see some examples in the sample program that follows.
The program places a JFrame object on the screen as the primary GUI.
A red JTextField and a JLabel are placed on the contentPane
on the JFrame object. (Note that the JLabel is rendered
as gray in the metal L&F.)
The JTextField is placed in the Center of the JFrame
using the default border layout. The JLabel is placed in the
South position on the JFrame object.
A green JButton and a yellow JButton are placed on the layeredPane. Apparently the default layout for the layeredPane is absolute or null. The JButton objects are purposely placed so as to partially overlap. Both buttons appear on top of the red JTextField object on the contentPane. (Components on layered panes are always on top of components on the contentPane).
The green JButton is initially placed at layer position +1 on the layered pane and the yellow JButton is initially placed at layer position -29999 which is the limit in the negative direction.
Action listeners are registered on the two buttons. When the top button is clicked, the action is to swap the layer positions of the two buttons causing the one on the bottom to move to the top.
Several lines of code are included to investigate the parent child relationships
of the various panes. The output from this part of the program follows.
Note that line breaks were manually inserted here to force the material
to fit in this format.
Root pane is: class com.sun.java.swing.JRootPane Parent of root pane is SwingPane01 [frame0,0,0,0x0,invalid,hidden, layout=java.awt.BorderLayout,resizable,title=] Glass pane is: class com.sun.java.swing.JPanel Parent of glass pane is com.sun.java.swing.JRootPane [,0,0,0x0,invalid, layout=com.sun.java.swing.JRootPane$RootLayout] Layered pane is: class com.sun.java.swing.JLayeredPane Parent of layered pane is com.sun.java.swing.JRootPane [,0,0,0x0,invalid, layout=com.sun.java.swing.JRootPane$RootLayout] Content pane is: class com.sun.java.swing.JPanel Parent of content pane is com.sun.java.swing.JLayeredPane [null.layeredPane,0,0,0x0,invalid] An interpretation of the above is: The root pane is a child of the JFrame object. The glass pane is a child of the root pane. The layered pane is a child of the root pane. The content pane is a child of the layered pane. The root pane is of type JRootPane. The glass pane is of type JPanel. The layered pane is of type JLayeredPane. The content pane is of type JPanel. |
The JavaSoft documentation indicates that the contentPane is placed at an equivalent layer position of -30000 in the layeredPane.
The glassPane is something of a mystery to me at this point in time (5/12/98). I was unable to draw on the glassPane. An attempt to place either a JButton object or a JToolTip object on the glassPane was rejected by the compiler with error messages that the JButton and the JToolTip could not be converted to type JPopupMenu. I don't know if this is proper behavior or a bug.
The program was tested using JDK 1.1.6 and Swing 1.0.1 under Win95.
import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; |
I have also included the main() method in this fragment simply
to provide continuity.
class SwingPane01 extends JFrame{//subclass JFrame JLayeredPane theLayeredPane; JButton greenButton; JButton yellowButton; public static void main(String[] args){ new SwingPane01(); }//end main |
SwingPane01(){//constructor theLayeredPane = this.getLayeredPane(); int frameWidth = 300; int frameHeight = 200; |
System.out.println("Root pane is: " + this.getRootPane().getClass()); System.out.println("Parent of root pane is " + this.getRootPane().getParent() + "\n"); System.out.println("Glass pane is: " + this.getGlassPane().getClass()); System.out.println("Parent of glassPane is " + this.getGlassPane().getParent() + "\n"); System.out.println("Layered pane is: " + this.getLayeredPane().getClass()); System.out.println("Parent of layeredPane is " + this.getLayeredPane().getParent() + "\n"); System.out.println("Content pane is: " + this.getContentPane().getClass()); System.out.println("Parent of contentPane is " + this.getContentPane().getParent() + "\n"); |
For the metal L&F, the JLabel displays as gray and appears to be part of the border at the bottom of the JFrame object.
The South JLabel provides instructions regarding the use
of the JButton objects that will also be placed on the JFrame
object.
JLabel theLabel = new JLabel( " Click buttons to swap their layer positions."); this.getContentPane().add(theLabel,"South"); JTextField redTextField = new JTextField( " redTextField on contentPane"); redTextField.setBackground(Color.red); this.getContentPane().add(redTextField,"Center"); |
Note that an ActionListener is registered on the JButton
object. We will see the class that defines the behavior of the listener
object later.
greenButton = new JButton( "greenButton on Layered Pane"); greenButton.setBackground(Color.green); greenButton.setBounds(10,10,240,40); greenButton.addActionListener(new MyActionListener()); theLayeredPane.add(greenButton,new Integer(1)); |
yellowButton = new JButton( "yellowButton on Layered Pane"); yellowButton.setBackground(Color.yellow); yellowButton.setBounds(40,20,240,40); yellowButton.addActionListener(new MyActionListener()); theLayeredPane.add(yellowButton,new Integer(-29999)); |
this.setTitle("Copyright 1998, R.G.Baldwin"); this.setSize(frameWidth,frameHeight); this.setVisible(true); //===================================================// //Anonymous inner class to terminate program. this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0);}});//end addWindowListener }//end constructor |
The behavior of the actionPerformed() method in this case is to cause the layer positions of the two JButton objects to be swapped whenever the button on top is clicked. This causes the JButton object immediately below the one on the top to move to the top of the stack.
Note that this method invokes the setLayer() method of the JLayeredPane
class to rearrange the positions of the two JButton objects at runtime.
class MyActionListener implements ActionListener{ public void actionPerformed(ActionEvent e){ if(e.getActionCommand().equals( "greenButton on Layered Pane")){ theLayeredPane.setLayer(greenButton,-29999); theLayeredPane.setLayer(yellowButton,1); }else{ theLayeredPane.setLayer(greenButton,1); theLayeredPane.setLayer(yellowButton,-29999); }//end else }//end actionPerformed() }//end class MyActionListener |
/*File SwingPane01 Copyright 1998, R.G.Baldwin The purpose of this program is to illustrate the use of the contentPane and the layeredPane. The program places a JFrame object on the screen as the primary GUI. A red JTextField and a JLabel are placed on the contentPane on the JFrame object. (The label is actually rendered as gray in the metal L&F.) The JTextField is placed in the Center of the JFrame using the default border layout. The JLabel is placed in the South position on the JFrame object. A green JButton and a yellow JButton are placed on the layeredPane of the JFrame object. Apparently the default layout for the layeredPane is absolute or null. The JButton objects are purposely placed so as to partially overlap. Both buttons appear on top of the JTextField object on the contentPane. The green JButton is initially placed at layer position +1 on the layeredPane and the yellow JButton is initially placed at layer position -29999 which is the limit in the negative direction. Action listeners are registered on the two buttons. When the top button is clicked, the action is to swap the layer positions of the two buttons causing the other one to move to the top layer position. Several lines of code are included to investigate the parent child relationships of the various panes. The output from this part of the program follows. Note that line breaks were manually inserted here to force the material to fit in this format. Root pane is: class com.sun.java.swing.JRootPane Parent of root pane is SwingPane01 [frame0,0,0,0x0,invalid,hidden, layout=java.awt.BorderLayout,resizable,title=] Glass pane is: class com.sun.java.swing.JPanel Parent of glassPane is com.sun.java.swing.JRootPane [,0,0,0x0,invalid, layout=com.sun.java.swing.JRootPane$RootLayout] Layered pane is: class com.sun.java.swing.JLayeredPane Parent of layeredPane is com.sun.java.swing.JRootPane [,0,0,0x0,invalid, layout=com.sun.java.swing.JRootPane$RootLayout] Content pane is: class com.sun.java.swing.JPanel Parent of contentPane is com.sun.java.swing.JLayeredPane [null.layeredPane,0,0,0x0,invalid] An interpretation of the above is: The root pane is a child of the JFrame object. The glass page is a child of the root pane. The layeredPane is a child of the root pane. The contentPane is a child of the layeredPane. The root pane is of type JRootPane. The glassPane is of type JPanel. The layeredPane is of type JLayeredPane. The contentPane is of type JPanel. The JavaSoft documentation indicates that the contentPane is placed at layer position -30000 in the layeredPane. The glassPane is something of a mystery at this point. I was unable to draw upon the glassPane. An attempt to place either a JButton object or a JToolTip object on the glass plane was rejected by the compiler with error messages that the JButton and the JToolTip could not be converted to type JPopupMenu. Tested using JDK 1.1.6 and Swing 1.0.1 under Win95. **********************************************************/ import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; class SwingPane01 extends JFrame{//subclass JFrame JLayeredPane theLayeredPane; JButton greenButton; JButton yellowButton; public static void main(String[] args){ new SwingPane01(); }//end main //-----------------------------------------------------// SwingPane01(){//constructor //Get a ref to the layeredPane for later use. theLayeredPane = this.getLayeredPane(); int frameWidth = 300; int frameHeight = 200; //Get and display types of different panes along with // parent-child hierarchy. System.out.println("Root pane is: " + this.getRootPane().getClass()); System.out.println("Parent of root pane is " + this.getRootPane().getParent() + "\n"); System.out.println("Glass pane is: " + this.getGlassPane().getClass()); System.out.println("Parent of glassPane is " + this.getGlassPane().getParent() + "\n"); System.out.println("Layered pane is: " + this.getLayeredPane().getClass()); System.out.println("Parent of layeredPane is " + this.getLayeredPane().getParent() + "\n"); System.out.println("Content pane is: " + this.getContentPane().getClass()); System.out.println("Parent of contentPane is " + this.getContentPane().getParent() + "\n"); //Put instructions in a JLabel on the contentPane. JLabel theLabel = new JLabel( " Click buttons to swap their layer positions."); this.getContentPane().add(theLabel,"South"); //Put a red JTextField in the Center of the JFrame on // the contentPane. JTextField redTextField = new JTextField( " redTextField on contentPane"); redTextField.setBackground(Color.red); this.getContentPane().add(redTextField,"Center"); //Put a green JButton on the layeredPane at a layer // position of +1. greenButton = new JButton( "greenButton on Layered Pane"); greenButton.setBackground(Color.green); greenButton.setBounds(10,10,240,40); greenButton.addActionListener(new MyActionListener()); theLayeredPane.add(greenButton,new Integer(1)); //Put a yellow JButton on the layeredPane at a layer // position of -29999. yellowButton = new JButton( "yellowButton on Layered Pane"); yellowButton.setBackground(Color.yellow); yellowButton.setBounds(40,20,240,40); yellowButton.addActionListener(new MyActionListener()); theLayeredPane.add(yellowButton,new Integer(-29999)); //Set title, size, and visibility of JFrame object. this.setTitle("Copyright 1998, R.G.Baldwin"); this.setSize(frameWidth,frameHeight); this.setVisible(true); //===================================================// //Anonymous inner class to terminate program. this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0);}});//end addWindowListener }//end constructor //=====================================================// //Inner class for listener objects which swap the layer // positions of the two JButton objects when the one on // the top is clicked. class MyActionListener implements ActionListener{ public void actionPerformed(ActionEvent e){ if(e.getActionCommand().equals( "greenButton on Layered Pane")){ theLayeredPane.setLayer(greenButton,-29999); theLayeredPane.setLayer(yellowButton,1); }else{ theLayeredPane.setLayer(greenButton,1); theLayeredPane.setLayer(yellowButton,-29999); }//end else }//end actionPerformed() }//end class MyActionListener }//end class SwingPane01 //=======================================================// |