Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 21. (Advanced topics) Pluggable Look & Feel. Easy for reading, Click here!

Custom Search
Swing Chapter 21. (Advanced topics) Pluggable Look & Feel. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 4/5 



Previous Page Previous Page (3/5) - Next Page (5/5) Next Page
Subpages: 1. Pluggable Look & Feel overview
2. Custom L&F: part I - Using custom resources
3. Custom L&F: part II - Creating custom UI delegates
4. L&F for custom components: part I - Implementing L&F support
5. L&F for custom components: part II - Third party L&F support

21.4  L&F for custom components: part I - Implementing L&F support

In this section we'll add support for existing L&Fs to a custom component, namely our InnerFrame developed in chapter 15. We'll show how to modify this component so it complies with the look-and-feel paradigm and behaves accordingly. It will use shared resources provided by the installed L&F and use these resources for rendering itself. This requires creation of a custom UI delegate as well as modification of the component itself.

To allow direct comparison with JInternalFrame, we create a desktop pane container with a menu bar to allow the creation of an arbitrary number of InnerFrames and JInternalFrames in a cascaded fashion. We also allow L&F switching at run-time (as we did in the previous examples).

Note: We use a JDesktopPane instead of our mdi package's custom MDIPane component because JInternalFrame generates a null pointer exception when activated (in Java 2 FCS) if its parent is not a JDesktopPane.

Figure 21.4 InnerFrame and JInternalFrame in the Metal L&F.

<<file figure21-4.gif>>

Figure 21.5 InnerFrame and JInternalFrame in the Windows L&F.

<<file figure21-5.gif>>

Figure 21.6 InnerFrame and JInternalFrame in the Motif L&F.

<<file figure21-6.gif>>

The Code: MdiContainer.java

see \Chapter21\3

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import javax.swing.*;

import mdi.*;

public class MdiContainer extends JFrame

{

  protected ImageIcon m_icon;

  protected Hashtable m_lfs;

  public MdiContainer() {

    super("Custom MDI: Look & Feel");

    setSize(570,400);

    setContentPane(new JDesktopPane());

    m_icon = new ImageIcon("earth.jpg");

    JMenuBar menuBar = createMenuBar();

    setJMenuBar(menuBar);

    WindowListener wndCloser = new WindowAdapter() {

      public void windowClosing(WindowEvent e) {

        System.exit(0);

      }

    };

    addWindowListener(wndCloser);

    setVisible(true);

  }

  protected JMenuBar createMenuBar() {

    JMenuBar menuBar = new JMenuBar();

    JMenu mFile = new JMenu("File");

    mFile.setMnemonic('f');

    JMenuItem mItem = new JMenuItem("New InnerFrame");

    mItem.setMnemonic('i');

    ActionListener lst = new ActionListener() {

      int m_counter = 0;

      public void actionPerformed(ActionEvent e) {

        m_counter++;

        InnerFrame frame = new InnerFrame("InnerFrame " +

          m_counter);

        int i = m_counter % 5;

        frame.setBounds(20+i*20, 20+i*20, 200, 200);

        frame.getContentPane().add(

          new JScrollPane(new JLabel(m_icon)));

        getContentPane().add(frame);

        frame.toFront();

      }

    };

    mItem.addActionListener(lst);

    mFile.add(mItem);

    mItem = new JMenuItem("New JInternalFrame");

    mItem.setMnemonic('j');

    lst = new ActionListener() {

      int m_counter = 0;

      public void actionPerformed(ActionEvent e) {

        m_counter++;

        JInternalFrame frame = new JInternalFrame(

          "JInternalFrame " + m_counter);

        frame.setClosable(true);

        frame.setMaximizable(true);

        frame.setIconifiable(true);

        frame.setResizable(true);

        int i = m_counter % 5;

        frame.setBounds(50+i*20, 50+i*20, 200, 200);

        frame.getContentPane().add(

          new JScrollPane(new JLabel(m_icon)));

        getContentPane().add(frame);

        frame.toFront();

      }

    };

    mItem.addActionListener(lst);

    mFile.add(mItem);

    mFile.addSeparator();

    mItem = new JMenuItem("Exit");

    mItem.setMnemonic('x');

    lst = new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        System.exit(0);

      }

    };

    mItem.addActionListener(lst);

    mFile.add(mItem);

    menuBar.add(mFile);

    lst = new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        String str = e.getActionCommand();

        Object obj = m_lfs.get(str);

        if (obj != null)

          try {

            String className = (String)obj;

            Class lnfClass = Class.forName(className);

            UIManager.setLookAndFeel(

              (LookAndFeel)(lnfClass.newInstance()));

            SwingUtilities.updateComponentTreeUI(

              MdiContainer.this);

          }

          catch (Exception ex) {

            ex.printStackTrace();

            System.err.println(ex.toString());

          }

      }

    };

    m_lfs = new Hashtable();

    UIManager.LookAndFeelInfo lfs[] =

      UIManager.getInstalledLookAndFeels();

    JMenu mLF = new JMenu("Look&Feel");

    mLF.setMnemonic('l');

    for (int k = 0; k < lfs.length; k++ ) {

      String name = lfs[k].getName();

      JMenuItem lf = new JMenuItem(name);

      m_lfs.put(name, lfs[k].getClassName());

      lf.addActionListener(lst);

      mLF.add(lf);

    }

    menuBar.add(mLF);

    return menuBar;

  }

  public static void main(String argv[]) {

    new MdiContainer();

  }

}

The Code: InnerFrame.java

see \Chapter21\3\mdi

package mdi;

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import javax.swing.*;

import javax.swing.event.*;

import javax.swing.border.*;

import javax.swing.plaf.ComponentUI;

import mdi.plaf.*;

public class InnerFrame extends JPanel

 implements RootPaneContainer, Externalizable

{

  // Unchanged code from section 15.7

  private Color m_titleBarBackground;

  private Color m_selectedTitleBarBackground;

  private Color m_titleBarForeground;

  private Color m_selectedTitleBarForeground;

  private Font  m_titleBarFont;

  private Border m_frameBorder;

  private Icon m_frameIcon;

  public InnerFrame() {

    this("");

  }

  // Unchanged code from section 15.7

  ////////////////////////////////////////////

  /////////////// L&F Support ////////////////

  ////////////////////////////////////////////

  static {

    UIManager.getDefaults().put(

      "InnerFrameUI", "mdi.plaf.InnerFrameUI");

    UIManager.getDefaults().put("InnerFrameButtonUI",

      "javax.swing.plaf.basic.BasicButtonUI");

  }

  public static ComponentUI createUI(JComponent a) {

    ComponentUI mui = new InnerFrameUI();

    return mui;

  }

  public void setUI(InnerFrameUI ui) {

    if ((InnerFrameUI)this.ui != ui) {

      super.setUI(ui);

      repaint();

    }

  }

  public InnerFrameUI getUI() {

    return (InnerFrameUI)ui;

  }

  public void updateUI() {

    setUI((InnerFrameUI)UIManager.getUI(this));

    invalidate();

  }

  public String getUIClassID() {

    return "InnerFrameUI";

  }

  // Unchanged code from section 15.7

  public void setSelectedTitleBarForeground(Color c) {

    m_selectedTitleBarForeground = c;

    updateTitleBarColors();

  }

  public Color getSelectedTitleBarForeground() {

    return m_selectedTitleBarForeground;

  }

  public void setTitleBarFont(Font f) {

    m_titleBarFont = f;

    updateTitleBarColors();

  }

  public Font getTitleBarFont() {

    return m_titleBarFont;

  }

  public void setBorder(Border b) {

    m_frameBorder = b;

    if (b != null) {

      Insets ins = b.getBorderInsets(this);

      if (m_northResizer != null)

        m_northResizer.setHeight(ins.top);

      if (m_southResizer != null)

        m_southResizer.setHeight(ins.bottom);

      if (m_eastResizer != null)

        m_eastResizer.setWidth(ins.right);

      if (m_westResizer != null)

        m_westResizer.setWidth(ins.left);

      if (isShowing())

        validate();

    }

  }

  public Border getBorder() {

    return m_frameBorder;

  }

  protected void updateTitleBarColors() {

    if (isShowing())

        repaint();

  }

  public void setFrameIcon(Icon fi) {

    m_frameIcon = fi;

    if (fi != null) {

      if (m_frameIcon.getIconHeight() > TITLE_BAR_HEIGHT)

        setTitleBarHeight(m_frameIcon.getIconHeight() + 2*FRAME_ICON_PADDING);

      if (m_iconLabel != null)

        m_iconLabel.setIcon(m_frameIcon);

    }

    else

        setTitleBarHeight(TITLE_BAR_HEIGHT);

    if (isShowing())

        revalidate();

  }

  public Icon getFrameIcon() {

    return m_frameIcon;

  }

  // Unchanged code from section 15.7

  protected void createTitleBar() {

  m_titlePanel = new JPanel() {

    public Dimension getPreferredSize() {

      return new Dimension(InnerFrame.this.getWidth(),

        m_titleBarHeight);

    }

    public Color getBackground() {

      if (InnerFrame.this == null)

        return super.getBackground();

      if (isSelected())

        return getSelectedTitleBarBackground();

      else

        return getTitleBarBackground();

    }

  };

  m_titlePanel.setLayout(new BorderLayout());

  m_titlePanel.setOpaque(true);

  m_titleLabel = new JLabel() {

    public Color getForeground() {

      if (InnerFrame.this == null)

        return super.getForeground();

      if (isSelected())

        return getSelectedTitleBarForeground();

      else

        return getTitleBarForeground();

    }

    public Font getFont() {

      if (InnerFrame.this == null)

        return super.getFont();

      return m_titleBarFont;

    }

  };

  // Unchanged code from section 15.7

  class InnerFrameButton extends JButton

  {

    // Unchanged code from section 15.7

    public void setBorder(Border b) { }

    public Border getBorder() { return null; }

    public String getUIClassID() {

        return "InnerFrameButtonUI";

    }

  }

  // Unchanged code from section 15.7

  class EastResizeEdge extends JPanel

   implements MouseListener, MouseMotionListener

  {

    private int WIDTH = BORDER_THICKNESS;

    private int MIN_WIDTH = ICONIZED_WIDTH;

    private boolean m_dragging;

    private JComponent m_resizeComponent;

    protected EastResizeEdge(JComponent c) {

      m_resizeComponent = c;

      setOpaque(false);

      if (m_frameBorder != null)

        WIDTH =

          m_frameBorder.getBorderInsets(InnerFrame.this).right;

    }

    public void setWidth(int w) {

      WIDTH = w;

    }

    // Unchanged code from section 15.7

  }

  // Classes WestResizeEdge, NorthResizeEdge, and SouthResizeEdge

  // are modified similarly

  public void writeExternal(ObjectOutput out) throws IOException {

    out.writeObject(m_titleBarBackground);

    out.writeObject(m_titleBarForeground);

    out.writeObject(m_selectedTitleBarBackground);

    out.writeObject(m_selectedTitleBarForeground);

    out.writeObject(m_frameBorder);

    // Unchanged code from section 15.7

  }

  public void readExternal(ObjectInput in)

   throws IOException, ClassNotFoundException {

    setTitleBarBackground((Color)in.readObject());

    setTitleBarForeground((Color)in.readObject());

    setSelectedTitleBarBackground((Color)in.readObject());

    setSelectedTitleBarForeground((Color)in.readObject());

    setBorder((Border)in.readObject());

    setTitle((String)in.readObject());

    // Unchanged code from section 15.7

    setFrameIcon((Icon)in.readObject());

    // Unchanged code from section 15.7

  }

}

The Code: InnerFrameUI.java

see \Chapter21\3\mdi\plaf

package mdi.plaf;

import java.awt.*;

import java.util.*;

import javax.swing.*;

import javax.swing.border.*;

import javax.swing.plaf.*;

import mdi.*;

public class InnerFrameUI extends javax.swing.plaf.PanelUI

{

  private static InnerFrameUI frameUI;

  protected static Color DEFAULT_TITLE_BAR_BG_COLOR;

  protected static Color DEFAULT_SELECTED_TITLE_BAR_BG_COLOR;

  protected static Color DEFAULT_TITLE_BAR_FG_COLOR;

  protected static Color DEFAULT_SELECTED_TITLE_BAR_FG_COLOR;

  protected static Font  DEFAULT_TITLE_BAR_FONT;

  protected static Border DEFAULT_INNER_FRAME_BORDER;

  protected static Icon  DEFAULT_FRAME_ICON;

  private static Hashtable m_ownDefaults = new Hashtable();

  static {

    m_ownDefaults.put("InternalFrame.inactiveTitleBackground",

      new ColorUIResource(108,190,116));

    m_ownDefaults.put("InternalFrame.inactiveTitleForeground",

      new ColorUIResource(Color.black));

    m_ownDefaults.put("InternalFrame.activeTitleBackground",

      new ColorUIResource(91,182,249));

    m_ownDefaults.put("InternalFrame.activeTitleForeground",

      new ColorUIResource(Color.black));

    m_ownDefaults.put("InternalFrame.titleFont",

      new FontUIResource("Dialog", Font.BOLD, 12));

    m_ownDefaults.put("InternalFrame.border",

      new BorderUIResource(new MatteBorder(4, 4, 4, 4, Color.blue)));

    m_ownDefaults.put("InternalFrame.icon",

      new IconUIResource(new ImageIcon("mdi/default.gif")));

  }

  public static ComponentUI createUI(JComponent c) {

    if(frameUI == null)

      frameUI = new InnerFrameUI();

    try {

      frameUI.installDefaults();

      InnerFrame frame = (InnerFrame)c;

      frame.setTitleBarBackground(DEFAULT_TITLE_BAR_BG_COLOR);

      frame.setSelectedTitleBarBackground(

        DEFAULT_SELECTED_TITLE_BAR_BG_COLOR);

      frame.setTitleBarForeground(DEFAULT_TITLE_BAR_FG_COLOR);

        frame.setSelectedTitleBarForeground(

          DEFAULT_SELECTED_TITLE_BAR_FG_COLOR);

      frame.setTitleBarFont(DEFAULT_TITLE_BAR_FONT);

      frame.setBorder(DEFAULT_INNER_FRAME_BORDER);

      frame.setFrameIcon(DEFAULT_FRAME_ICON);

      if (frame.isShowing())

        frame.repaint();

    }

    catch (Exception ex) {

      System.err.println(ex);

      ex.printStackTrace();

    }

    return frameUI;

  }

  public void installUI(JComponent c) {

    InnerFrame frame = (InnerFrame)c;

    super.installUI(frame);

  }

  public void uninstallUI(JComponent c) {

    super.uninstallUI(c);

  }

  protected void installDefaults() {

    DEFAULT_TITLE_BAR_BG_COLOR = (Color)findDefaultResource(

      "InternalFrame.inactiveTitleBackground");

    DEFAULT_TITLE_BAR_FG_COLOR = (Color)findDefaultResource(

      "InternalFrame.inactiveTitleForeground");

    DEFAULT_SELECTED_TITLE_BAR_BG_COLOR = (Color)findDefaultResource(

      "InternalFrame.activeTitleBackground");

    DEFAULT_SELECTED_TITLE_BAR_FG_COLOR = (Color)findDefaultResource(

      "InternalFrame.activeTitleForeground");

    DEFAULT_TITLE_BAR_FONT = (Font)findDefaultResource(

      "InternalFrame.titleFont");

    DEFAULT_INNER_FRAME_BORDER = (Border)findDefaultResource(

      "InternalFrame.border");

    DEFAULT_FRAME_ICON = (Icon)findDefaultResource(

      "InternalFrame.icon");

  }

  protected Object findDefaultResource(String id) {

    Object obj = null;

    try {

      UIDefaults uiDef = UIManager.getDefaults();

      obj = uiDef.get(id);

    }

    catch (Exception ex) {

      System.err.println(ex);

    }

    if (obj == null)

      obj = m_ownDefaults.get(id);

    return obj;

  }

  public void paint(Graphics g, JComponent c) {

    super.paint(g, c);

    if (c.getBorder() != null)

      c.getBorder().paintBorder(

    c, g, 0, 0, c.getWidth(), c.getHeight());

  }

  public Color getTitleBarBkColor() {

    return DEFAULT_TITLE_BAR_BG_COLOR;

  }

  public Color getSelectedTitleBarBkColor() {

    return DEFAULT_SELECTED_TITLE_BAR_BG_COLOR;

  }

  public Color getTitleBarFgColor() {

    return DEFAULT_TITLE_BAR_FG_COLOR;

  }

  public Color getSelectedTitleBarFgColor() {

    return DEFAULT_SELECTED_TITLE_BAR_FG_COLOR;

  }

  public Font getTitleBarFont() {

    return DEFAULT_TITLE_BAR_FONT;

  }

  public Border getInnerFrameBorder() {

    return DEFAULT_INNER_FRAME_BORDER;

  }

}

Understanding the Code

Class MdiContainer

This class represents a simple frame container similar to the one used in chapter 15 to demonstrate our custom MDI interface. An instance of JDesktopPane is set as the content pane for this frame to support JInternalFrames as well as our InnerFrame component.

The createMenuBar() method creates and populates a menu bar for this example. A "File" menu is constructed with three menu items:

Menu item "New InnerFrame" creates a new instance of InnerFrame and adds it to the desktop pane. The closable, maximizable, iconifiable, and resizable properties are set by default to demonstrate the maximum number of UI elements used in this component.

Menu item "New JInternalFrame" creates a new instance of Swing's JInternalFrame and adds it to the desktop pane.

Menu item "Exit" quits this application.

A "Look&Feel" menu is constructed with an array of menu items corresponding to the L&Fs currently installed. These items are handled the same way they were in previous examples and do not require additional explanation here.

Class mdi.InnerFrame

This class was introduced in chapter 15 and requires some minor modifications to support L&F. First note that the new mdi.plaf package and javax.swing.plaf.ComponentUI class are imported.

As we know, it is typical for L&F-compliant components to not perform any rendering by themselves and not explicitly hold any resources for this process. Instead all rendering and necessary resources involved (colors, icons, borders etc.) should be maintained by a UI delegate corresponding to that component. To conform to this design pattern, we remove several class variables from our InnerFrame class and move them to our custom InnerFrameUI class (see below). Specifically: DEFAULT_TITLE_BAR_BG_COLOR, DEFAULT_SELECTED_TITLE_BAR_BG_COLOR, and DEFAULT_FRAME_ICON. We will use resources provided by the currently installed L&F for these variables instead of hard-coded values defined in the component class.

Two class variables (DEFAULT_BORDER_COLOR and DEFAULT_SELECTED_BORDER_COLOR) have been removed. We don't need them any more since we'll use a Border instance to render InnerFrame's border. Eight default icon variables (ICONIZE_BUTTON_ICON, RESTORE_BUTTON_ICON, CLOSE_BUTTON_ICON, MAXIMIZE_BUTTON_ICON, MINIMIZE_BUTTON_ICON, and their pressed variants) are intentionally left unchanged. We could move them into our UI delegate class as well, and use standard icons provided by L&F for these variables. But we've decided to preserve some individuality for our custom component.

Note: Swing's JInternalFrame delegates provide only one standard icon for the frame controls, whereas our implementation uses two icons for pressed and not pressed states. Since we will use JInternalFrame UI delegate resources, this would require us to either remove our two-icon functionality, or construct separate icons for use with each L&F.

Now let's take a look at the instance variables that have been modified:

Color m_titleBarBackground: background color for the title bar of an inactive frame; now initialized by the InnerFrameUI delegate.

Color m_selectedTitleBarBackground: background color for the title bar of an active frame; now initialized by the InnerFrameUI delegate.

Color m_titleBarForeground: foreground color for the title bar of an inactive frame; now initialized by the InnerFrameUI delegate. The previous version supported only one foreground color. This version distinguishes between the foreground of active and inactive frames (that is necessary for support of various L&Fs).

Color m_selectedTitleBarForeground: new variable for foreground color of the title bar of an active frame. Two new methods, setSelectedTitleBarForeground() and getSelectedTitleBarForeground(), support this variable.

Font m_titleBarFont: new variable for the title bar's font. The previous version used a default font for to render frame's title, but this may not be acceptable for all L&Fs. Two new methods, setTitleBarFont() and getTitleBarFont(), support this variable.

Border m_frameBorder: new variable for frame's border. The previous version's border was made from the four resizable edge components which were colored homogeneously. In this example we use a shared Border instance provided by the current L&F, and store it in the m_frameBorder variable. Two new methods, setBorder() and getBorder(), support this variable.

Icon m_frameIcon: this variable was formerly defined as an ImageIcon instance. This may not be acceptable for L&F implementations which provide a default frame icon as a different instance of the Icon interface. So we now declare m_frameIcon as an Icon, resulting in several minor modifications throughout the code.

We have also removed two instance variables: m_BorderColor and m_selectedBorderColor. Their corresponding set/get methods have also been removed. Method updateBorderColors() is left without implementation and can also be removed from the code. The reason for this change is that we no longer support direct rendering of the resize edge components (they are now non-opaque, see below), and instead delegate this functionality to the m_frameBorder instance retrieved from the currently installed L&F.

Note: We also no longer support different borders for active and inactive frames since not all L&Fs provide two Border instances for internal frames.

A significant amount of code needs to be added for look-and-feel support in this component. First note that a static block places two values into UIManager's resource defaults storage. These key/value pairs allow retrieval of the fully-qualified InnerFrameUI class name, and that of the custom delegate of it's inner class title bar button component, InnerFrameButtonUI (for this we simply use BasicButtonUI):

  static {

    UIManager.getDefaults().put(

      "InnerFrameUI", "mdi.plaf.InnerFrameUI");

    UIManager.getDefaults().put("InnerFrameButtonUI",

      "javax.swing.plaf.basic.BasicButtonUI");

  }

As we've discussed in the previous examples, for all provided Swing components this information is provided by the concrete sub-classes of LookAndFeel class (added to the defaults table in the initClassDefaults() method). However, these implementations have no knowledge of our custom component, so we must place it in the table ourselves.

Note: In this case we do not need to add a corresponding java.lang.Class instance to to the defaults table, as was necessary when implementing our own LookAndFeel (see 21.1.11).

The createUI() method will be called to create and return an instance of InnerFrame's UI delegate, InnerFrameUI (see below). The setUI() method installs a new InnerFrameUI instance, and the getUI() method retrieves the current delegate (both use the protected ui variable inherited from the JComponent class).

Method updateUI() will be called to notify the component whenever the current L&F changes. Our implementation requests an instance of InnerFrameUI from UIManager (using getUI()), which is then passed to setUI(). Then invalidate(). is called to mark this component for revalidation because the new L&F's resources will most likely change the sizing and position of InnerFrame's constituents.

The getUIClassID() method returns a unique String ID identifying this component's base UI delegate name. This String must be consistent with the string used as the key for the fully qualified class name of this component's delegate that was placed in UIManager's resource defaults table (see above):

    public String getUIClassID() { return "InnerFrameUI"; }

Several simple setXX()/getXX() methods have been added that do not require any explanation. The only exception is setBorder(), which overrides the corresponding JComponent method. The Border parameter is stored in our m_frameBorder variable, to be painted manually by InnerFrameUI, and we do not call the super class implementation. Thus, the border is not used in the typical way we are used to. Specifically, it does not define any insets for InnerFrame. Instead, it is just painted directly on top of it.  We do this purposefully because we desire the border to be painted over each resize edge child.

To do this, we make the resize edge components NorthResizeEdge, SouthResizeEdge, EastResizeEdge, and WestResizeEdge transparent, and preserve all functionality (changing the mouse cursor, resizing the frame, etc.). Thus, they form an invisible border whose width is synchronized (in the setBorder() method) with the width of the Border instance stored in our m_frameBorder variable. As you'll see below in our InnerFrameUI class, this Border instance is drawn explicitly on the frame's surface. Figure 21.3 illustrates. In this way we can preserve any decorative elements (usually 3D effects specific to each L&F), and at the same time leave our child resize edge components directly below this border to intercept and process mouse events as normal.

Figure 21.3 Invisible resize edge components around InnerFrame.

<<file figure21-3.gif>>

Note: The setBorder() method checks whether the component is shown on the screen (if isShowing() returns true) before calling validate(). This precaution helps avoid exceptions during creation of the application. A similar check needs to be made before update(), repaint(), etc., calls if they can possibly be invoked before InnerFrame is displayed on the screen.

Method updateTitleBarColors() has lost his importance, as the overridden methods in the title bar provide the current color information (so we don't have to call setXX() methods explicitly each time the color palette is changed).

Method createTitleBar() creates and initializes the title bar for this frame. The anonymous inner class defining our m_titlePanel component receives a new getBackground() method, which returns getSelectedTitleBarBackground() or getTitleBarBackground() depending on whether the frame is selected or not. Take note of the following code, which may seem strange:

    public Color getBackground() {

      if (InnerFrame.this == null)

        return super.getBackground();

      if (isSelected())

        return getSelectedTitleBarBackground();

      else

        return getTitleBarBackground();

    }

The reason that a check against null is made here is because this child component will be created before the creation of the corresponding InnerFrame instance is completed. By doing this we can avoid any possible NullPointerExceptions that would occur from calling parent class methods too early.

The m_titleLabel component is also now defined as an anonymous inner class. It overrides two JComponent methods, getForeground() and getFont(). These methods return instance variables from the parent InnerFrame instance. Thus we can avoid keeping track of the font and foreground of this child component, as long as the corresponding instance variables are properly updated. Note that we check the InnerFrame.this reference for null here as well.

Finally, methods writeExternal() and readExternal() have been modified to reflect the changes in the instance variables we have discussed.

Class mdi.InnerFrame.InnerFrameButton

This inner class has received two minor changes. First, we override JComponent's setBorder() and getBorder() methods to hide the default implementation and eliminate the possibility of assigning any border to these small custom buttons (otherwise every time the L&F is changed the standard JButton border will be set).

We also override the getUIClassID() method which returns a string ID representing this child component's UI delegate. Referring back to the static block in the InnerFrame class, we see that this ID is associated with javax.swing.plaf.basic.BasicButtonUI class. Thus, we directly assign the BasicButtonUI class as the delegate for this component instead of allowing the current L&F to take control.

Class mdi.InnerFrame.EastResizeEdge, WestResizeEdge, NorthResizeEdge, SouthResizeEdge

This EastResizeEdge inner class has received a few minor changes. The opaque property is now set to false, and we drop any code for the background color since this component is now transparent. Second, the WIDTH variable is initially set to the right inset of the frame's border. This variable is also accessible using the new setWidth() method, which was constructed primarily for synchronizing the size of this component with the width of the Border drawn around the frame (see InnerFrame's setBorder() method above). Each resize edge component is modified similarly.

Class mdi.plaf.InnerFrameUI

This class extends javax.swing.plaf.PanelUI and defines the custom UI delegate for our InnerFrame component. The basic idea behind this class is to retrieve and reuse the rendering resources defined by the current L&F for JInternalFrame. It is perfectly reasonable to use resources already defined in the LookAndFeel implementations for standard Swing components. By doing so we can more easily provide consistent views of custom components under different L&Fs, and reduce the amount of required coding. However, if we cannot rely on pre-defined resources in a particular LookAndFeel, we need to define our own custom resources. The next section will show how to deal with this situation (which might arise when using third party L&Fs).

Class variables:

InnerFrameUI frameUI: a shared instance of this class returned by createUI().

Color DEFAULT_TITLE_BAR_BG_COLOR: default background color for the title bar of an inactive frame.

Color DEFAULT_SELECTED_TITLE_BAR_BG_COLOR: default background color for the title bar of an active frame.

Color DEFAULT_TITLE_BAR_FG_COLOR: default foreground color for the title bar of an inactive frame.

Color DEFAULT_SELECTED_TITLE_BAR_FG_COLOR: default foreground color for the title bar of an active frame.

Font DEFAULT_TITLE_BAR_FONT: default title bar font.

Border DEFAULT_INNER_FRAME_BORDER: default frame border.

Icon DEFAULT_FRAME_ICON: default frame icon.

Hashtable m_ownDefaults: a collection of resources stored in this UI delegate, and used when a particular resource is not implemented in the current L&F.

Note: The resource variables listed above are default in the sense that they're used unless other values are set explicitly using setXX() methods of InnerFrame (e.g. setTitleBarFont(), setBorder(), etc.).

The static block defined in this class creates and populates Hashtable m_ownDefaults. Method createUI() creates the InnerFrameUI shared instance (if it is not created yet) and calls installDefaults()to refresh its attributes with resources provided by the current L&F. Several setXX() methods are then called to update the properties of the specified InnerFrame instance.

Methods installUI() and uninstallUI() simply delegate their calls to the super-class implementation. They are included here because, in some cases, we may need to override these methods (as we saw in the previous section).

Method installDefaults() retrieves resources provided by the current L&F by calling our custom findDefaultResource() method, and stores them in the class variables. Remember that the keys for these resources are not documented, but can easily be found in the Swing LookAndFeel implementation source code. "InternalFrame.inactiveTitleBackground" is used to retrieve the background for JInternalFrame's inactive title bar, "InternalFrame.icon" is used for the JInternalFrame's icon, etc.

The custom method findDefaultResource() takes a resource ID String and searches for it in UIManager's UIDefaults table. If the resource is not found or an exception occurs, a resource stored in our m_ownDefaults collection under the same ID is used.

The paint() method renders a given component using a given graphical context. As we've discussed above, it explicitly paints the border around InnerFrame by calling the border's paintBorder() method.

Several remaining getXX() methods allow simple retrieval of the default rendering resources defined in this class, and do not require explanation here.

Running the Code

Create several InnerFrames and JInternalFrames using the "File" menu. Select different L&Fs and note that the appearance of InnerFrame changes accordingly. Compare the appearance of InnerFrame to JInternalFrame in each available L&F. Figures 21.4, 21.5, and 21.6 shows MdiContainer displaying two InnerFrames and two JInternalFrames in the Metal, Windows, and Motif L&Fs respectively.

Bug Alert! The Motif L&F does not supply proper resources for JInternalFrame rendering. A quick look at the MotifLookAndFeel source code shows that no resources are placed in the defaults table corresponding to JInternalFrame. This is why the appearance of InnerFrame is not at all consistent with JInternalFrame under the Motif L&F. (This is actually more of a design flaw than a bug.)

Note: The selection of JInternalFrames and InnerFrames is not synchronized. A single instance of each can be selected at any given time. Also note that because of known flaws in JDesktopPane, InnerFrame (as well as JInternalFrame) will not receive resize events from it, which cripples maximize functionality. Placing these components in an instance of our MDIPane would fix this. However, we would then see null pointer exceptions each time a JInternalFrame's title bar is pressed.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy