Changing UI Default Settings in Java

By: Daniel Horn

Abstract: Learn techniques for changing default properties of visual components and tips on how JBuilder's inspector and visual designer interact with "Look and Feel".

Changing UI Default Settings in Java

Changing UI Default Settings in Java

By Daniel Horn

 

Abstract:  

This paper discusses simple methods for changing default settings for UI elements in Swing.  These methods can simplify testing of UI layouts that otherwise might be dependent on the "Look and Feel" (LAF) of an underlying platform.   They can also be used to fix some of the problems (for example, with fonts or foreground and background colors) that may occur.   

The source code for a sample Java application demonstrates techniques for changing default properties of visual components and gives its user a simple way of getting information on default UI settings for LAFs; in particular, changing a default UI setting is easy once you know the "name" of the setting.

The purpose of this paper is to introduce the Java UIManager and UIDefaults classes, to provide some tips on how the inspector and the visual designer in JBuilder interact with LAFs, and to discuss how these affect the appearance of your applications and applets.

 

Introduction:

When using the designer in JBuilder to create a GUI component such as a panel, frame, or dialog, one should always be careful that your layout does not depend solely on the current look and feel (LAF).  At design time, JBuilder makes it easy to test against different LAFs; simply right click on the designer and choose "Metal", "CDE/Motif", or "Windows" from the "Look and Feel" submenu.

However, design time testing is not enough.  If you develop primarily on one platform, say Windows, and only test your Java app rarely on other platforms (e.g., Linux), you will encounter some unpleasant surprises.  

As different operating systems and JDK versions will affect a LAF, runtime testing of your application or applet is also necessary.  The layout and readability of a panel depends on such properties as the preferred size of contained components, while, for example, the component's size may depend on the font selected during design time.  In particular, one will find that default fonts for visual components will change depending on the underlying platform, and so one's layout may appear quite different than expected.  Because components have default fonts that are not necessarily the same on different platforms, you might find that fields and labels that look fine on one platform will be truncated on another.  

As you switch between different LAFs, you might observe different appearances and behaviors that you want to change.  For example, fonts as well as foreground and background colors may change.  A developer who doesn't take these changes into account may come up with, for example, a panel that looks fine on Windows but is surprisingly different on Linux.  Some specific examples are:

  1. The font in a textarea (JTextArea) may differ from that of a JLabel, JTextPane, JTextField, or JEditorPane..  
  2. The foreground and background colors in a textarea may differ from those of a JLabel, JTextPane, or JEditorPane.  
  3. The selection colors in a textarea or textfield (JTextField) may differ from those of a JTextPane, or JEditorPane.
  4. The text in a label (JLabel) is bold in the Metal LAF but not bold in every other LAF.

You may wish all text components of this type to look alike, regardless of the LAF, (examples 1, 2, and 3), or you may simply not like the default behavior and wish to change it (as in 4).  In our sample code, you will see that we use a JTextArea to display information as if it were a multi-line label, so we want we change its colors and font to match those of a JLabel.

This paper discusses simple methods for changing default settings for UI elements in Swing.  These methods can be applied with advantage not only to simplifying testing of UI layouts that need to be deployed to platforms with different default LAFs but also to fixing some of the problems that will occur.

 

 

The Sample Program:

The source code (http://codecentral.borland.com/codecentral/ccWeb.exe/listing?id=19851) for a sample Java application, the UI Defaults Browser, demonstrates simple techniques for changing default properties of visual components.  In addition, as its name suggests, it gives the user a simple way of getting information on default UI settings for LAFs.  As you will see, changing a default UI setting is easy once you know the key or "name" of the setting; the UI Defaults Browser gives you an easy way to find the names you may be interested in.

 

 

Using the Program:

When the program starts, the default LAF of the underlying system is loaded.  The main window contains a table of all the UI Default settings for the current LAF.  A row in the table consists of the key (the setting name), its value, the type of the value (such as Font, String, Icon, Integer, Color, Method, or Class); the fourth column may contain information that is simply a more readable or abbreviated version of information that appears in another column.

You may sort the table by clicking on the different column headers.  Clicking a header a second time will reverse the sort order.

Double-click on a table row to open a modeless dialog to view that row's information in a slightly more readable fashion.  This gives a convenient method for comparing information from different rows in the table or from similar rows in different versions of table data (e.g., when the LAF is changed).

Press the "Show Test Window" button to bring up a simple modeless window that shows various text components (a JTextField, a JTextArea, a JEditorPane, a JTextPane, and a JLabel) using the current UI defaults.  Change the LAF, using the "Look and Feel" menu, and press the "Show Test Window" button again to see how the appearance (in particular, the default fonts and colors) of these components changes.

Changing the LAF with the "Look and Feel" menu items results in the table data being refreshed with the settings for the currently selected LAF.

Press the "Do Text Area Fix" button to make some changes to the default appearance of a TextArea.  Press the "Show Test Window" button again to see how its appearance changes; you will notice that its font now matches the default font for a TextPane, its background color matches that of a Label, and its foreground color matches that of a TextPane.  Press the "Refresh Table" button to see these changes appear in the table.  

After pressing the "Do Text Area Fix" button, change the LAF, and then press the "Show Test Window" button again.  You will note that the changes via the "Text Area Fix" for the previous LAF still apply to the new LAF.  While this is arguably a bug, the real lesson here is that, if your application allows runtime changing of the LAF, then the LAF "fixes" need to be reapplied after each LAF change.

You can force "Text Area Fix" settings for the application's initial LAF by adding "/doFix" to the command line switch for launching the Java application.

The sample program also demonstrates how to force your application to use a specific LAF when it starts up.  See the information in the "About Box" for the command line arguments to use to indicate a desired LAF.  

If you are launching the application from within the JBuilder environment, see the "UIDefaults Application Debug" runtime configuration to automatically add the /doFix and a /laf: command line switch.  (Choose Run | Configurations... from the main menu to view or edit the configuration).

 

 

How It Works:  First Example: The About Box

Let's start with the simplest case of changing default attributes in a visual component.

Take a look at the application's "About" box (choose Help | About from the main menu).  While this about box is an example of window that is not particularly interesting nor well laid out, if you change between the different LAFs and open the about box for each one, you will see how much its appearance changes.  Using a text area to display information, we are able to permit scrolling (by adding the area to a JScrollPane), to disable editing, and to allow (clipboard) selection and copy operations.  The main reason we chose to use a JTextArea, however, is that we really want something like a multi-line label (something which does not appear to be available in the standard Swing components).  The problem with using a JTextArea is that it looks quite a bit different from a JLabel.  We fix that at runtime by adding the following lines to the Frame1_AboutBox.jbInit() method:

// Here's the manual way to change the settings of a text area to
// look like a multi-line label.
jTextArea1.setBackground(label1.getBackground());
jTextArea1.setForeground(label1.getForeground());
jTextArea1.setFont(label1.getFont());

In other words, we pick a label on the same panel and set the textarea to use the same font and the same foreground and background colors as the label.

(Make sure that this group of lines follows any custom settings made for "label1" in jbInit().  You must do this, say, if you change the default background color of label1 and you still want jTextArea1 to have a matching color.)

You may now be wondering why we do not use the Inspector in JBuilder's visual designer to accomplish this.  There are a couple of problems with using the Inspector.  If you simply change between the different LAFs available in the designer, you will note the information listed in the following table:

Component Look and Feel Default Foreground Color Default Background Color Default Font
JTextArea Metal Black White Dialog, Size=12
CDE/Motif Black R=174, G=178, B=195 Monospaced, Size=12
Windows Black White Monospaced, Size=12
JLabel Metal Black R=204, G=204, B=204 Dialog, Size=12, Bold
CDE/Motif Black R=174, G=178, B=195 Dialog, Size=12
Windows Black R=236, G=233, B=216 MS Sans Serif, Size=11
JTextPane Metal Black White Dialog, Size=12
CDE/Motif Black White Serif, Size=12
Windows Black White MS Sans Serif, Size=11

(For all of the specific examples described here and below, JBuilder 8 on Windows XP was used; by now you should not be surprised to see differences if you use a different JDK version or different underlying OS.  Also, note that the "Dialog" font seems to indicate the default font for a specific platform; for example, the "Dialog" font for Linux may be a different font family and/or size from those for Windows).

The first problem arises because a change in the visual Inspector in JBuilder does not always result in a corresponding line being added to the jbInit() method.  Suppose that you wanted a JTextArea to always have a white background, regardless of the LAF, but that you make this selection in the designer while the LAF is Windows or Metal.  You then go and run your app with a LAF of Motif to find that a JTextArea has a grayish background.  The problem here is that when the setting being made in the JB designer is the default value with respect to the LAF currently selected in the designer then no line is added to jbInit().  

A much better way to use the Inspector in the case of colors is to make your selection one of the descriptive choices in the dropdown list.  Select a JTextArea object; then, in the Inspector, choose the "background" property.  Click on the value cell to display a dropdown list (if you click to far to the right, you display a dialog box which is not what we want here).  Do not pick one of the concrete values such as "White" or "Red" from the list but instead a descriptive value such as "Label.background".  This will result in a line being added to jbInit() that looks like:

    jTextArea1.setBackground(UIManager.getColor("Label.background"));

The earlier example showed how to use the same background color as a specific label.  This example says to set the same background to the default background color for labels; this will often be a better choice to make.

The problem with making this choice through the Inspector is not a problem at all with the code but with confusion that may arise from relying too heavily on the visual designer.  If you make a color choice such as "Label.background" and then change the LAF in the designer, the color value displayed in the Inspector may not match what you think it does.  If you switch to the source view and look at the code in the jbInit() method, you will see that the "setBackground" line of code is exactly what you want it to be, even if the Inspector fails to report the value you expect.  The lesson in this case is: if in doubt, check the actual code in the jbInit() method; that's what the compiler will see and use.

Fonts present a slightly different problem in that the designer does not let us choose UI defaults from a drop down list as we could with colors.  Also, you will see that some components have "Dialog" as their default font in some LAFs.  "Dialog" seems to mean use the default font for the current LAF and can change with different underlying platforms.

As you will see, you may prefer to set a font based on UI default value for a type rather than using the font of a specific object.

 

 

The UIManager Class

As you've seen from the above example, the UIManager class is important to understand when dealing with LAFs and UI default settings.

With UIManager, it is possible to get a list information on the LAFs installed with your JDK:

UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels();
for (int i = 0; i < info.length; i++)
{
    System.out.println(info[i].toString());
}

A LookAndFeelInfo object is really just a pair of strings, the name of a LAF and the name of an associated class that supports it.

Given the classname of a LAF, you can set it as the current LAF by:

UIManager.setLookAndFeel(strLAF);

You should make a call like this before creating any application UI elements such as your main window.  Typically you make the call before creating your application class in the application class's main() method (see virtually any Java application class created using JBuilder).  If you don't make this call, the default LAF, Metal, is used.  Passing in an invalid string to setLookandFeel() or naming a LAF that is unsupported in your JDK (such as "Mac" on a Windows platform) will result in a ClassNotFoundException; if the exception is caught, the default LAF would be used in this case.

UIDefaultsApplication.java provides example code for all of this.  In addition, one can see how to specify the application's LAF via a command line argument.  You may deem this functionality desirable for all users or for testing purposes only.

Changing the LAF after an application has already started (i.e., after the application's main window has been created) is slightly more complicated.  One needs to invalidate and repaint every window to display the newly selected look:

try
{
    UIManager.setLookAndFeel(strLAF);
    SwingUtilities.updateComponentTreeUI(this);
}
catch (Exception exc)
{
}

An example of this appears in Frame1.SetLookAndFeel().  Note that you will need to apply updateComponentTreeUI() to each top level window in your application.  Again, this may not be general functionality that you wish to expose to all users for all applications; still, it may be handy to know during testing.

 

 

The UIDefaults Class

As we saw earlier, one can get the default background Color object for all labels by making a call:

     UIManager.getColor("Label.background");

"Label.background" is not the only property we can make use of.  Here's how we get a list of all of the UI default property names and values associated with them:

UIDefaults uiDefaults = UIManager.getDefaults();
Enumeration enum = uiDefaults.keys();
while (enum.hasMoreElements())
{
    Object key = enum.nextElement();
    Object val = uiDefaults.get(key);
    System.out.println("[" + key.toString() + "]:[" +
        (null != val ? val.toString() : "(null)") +
        "]");
}
 

The TableModel used to supply the information in the main window's table gets its data in a similar manner; see UIDefaultsTableModel.java for details.

The important thing to note is that once we have the key for a UI default setting (say, by scanning the table in the UI Defaults Browser main window), we can then retrieve the associated default UI object to copy for use elsewhere.  (It is probably not a good idea to change the object directly as it may be referred to by more than one key).

For example, the developer of UIDefaultsApplication may have decided that he does not like the default appearance of JTextArea objects.  Maybe JTextArea just seems too different from the other text components.  He may decide that he wants to only use these objects as multi-line labels, and that by default, he wants their appearance, regardless of LAF, to follow the following guidelines:

  • the default font used for a JTextArea should match that of a JTextPane;
  • the default background color of a JTextArea should match that of a JLabel; and
  • the default foreground color should match that of a JTextPane.

Now, the developer could go through his app and change each JTextArea object to match these settings.  This can be tedious, take a lot of time, and be error-prone; if he has many of these objects scattered throughout his app, he can easily miss one or forget to make the change if he adds a new JTextArea object later on.  A better solution is to make these changes once, especially as in this case, it is the default appearance of these objects he wants to change.  This is what happens with the addition of the following code to the app:

UIDefaults uiDefaults = UIManager.getDefaults();

uiDefaults.put("TextArea.font", uiDefaults.get("TextPane.font"));
uiDefaults.put("TextArea.background", uiDefaults.get("Label.background"));
uiDefaults.put("TextArea.foreground", uiDefaults.get("TextPane.foreground"));

(In the above example, one would probably also want JLabel objects to have a matching font or at least a non-bold font (as it does with the Metal LAF).  We leave it as an exercise to the reader to figure out the single line of code that needs to be added to the app).

(If desired, one could use other methods from UIDefaults, such as getColor() and getFont(), which provide a bit more type safety as they return null if the returned object is not of the expected type).

Look at UIDefaultsApplication.DoTextAreaFix() to see this in the sample code.  It is called whenever the user presses the "Do Text Area Fix" button or if the user starts the application with the "/doFix" command line switch.  Note that this type of fix needs to be reapplied each time, if any, the LAF changes at runtime; in practice, it would probably be best to place this call between the calls to setLookAndFeel() and updateComponentTreeUI().

Finally, just in case there's anybody left who would doubt why you any of this should be useful, take a look at the following dialog from an application you're all familiar with:

  

It demonstrates a visual incongruity that appears in a great deal of Java code.  Now you know how to make a simple change to avoid it in your own code.

 

 

Whats next?

The sample code was intended to help you get started learning about LAFs, the UIManager and UIDefaults classes, and how they affect the appearance of your applications and applets.  There are many ways it can be expanded to further your explorations.

One possible future direction would be to turn this functionality into a component that you can include in debug builds of your own app to allow runtime testing of windows change with different LAFs.  One might wish to have this component apply such changes to all windows in an app or only a specified few.

Another possible improvement to the UI Defaults Browser would be to display visual examples for values such as images (e.g., icons), colors, and fonts.

Another possibility would be to allow the user to change UI defaults on the fly by copying one value entry from the table to another that is of a compatible type; that is, a font (or color) value from one row could be copied to another row's value if it was also a font (or color, respectively).

One could use the ideas presented above to create one's own custom LAF by copying values from pre-existing LAFs and resetting some values to one's own objects.

Questions, comments, and suggestions may be sent to the author at dan@nerds.com ; use the title of the article or the words BDN Article in the email subject.

P.S. If you didn't spot the incongruity I mentioned in the previous section, compare the font in the Description text area with the text that appears elsewhere in the dialog.


Server Response from: ETNASC03