Java GUICreating Style Sheets for Java GUIsTechniques that allow you to fine-tune design styles with ease through-out the development life cycle
- By Paul E. Cooley
- March 9, 2000
AH, THE SEXY opportunities that a GUI builder provides. No more hassling with coding font sizes, component foreground, or background colors. Just give me a JavaBeans-enabled IDE and I am home free! After all, these new IDEs offer us "drag-and-drop programming." Just drop the bean on my form container, right click, and I can set all of its properties. Sure, I know I'll have to repeat this process about 20 to 30 times for each form in an enterprise Java application, but no problemthat's why I have an ergo-mouse.
I also know that each enterprise Java application may have 15 to 30 forms, but I'm not worried about this either. I have a well-managed client and have determined the GUI look and feel for the entire project; I can rest assured that my team will never have to open up any of those forms and change the properties on any of the components. Until, of course, the project sponsor sees the GUI for the application. Or perhaps the in-house designers lay their eyes on it, or ...
I work for a consulting company. Our clients have a spectacular track record for changing their minds about an application's look and feel moments before it is distributed. I'm sure that many of you out there in Industry, servicing internal clients, have much the same difficulties.
Most application developers have had to face up to this ugly factGUIs change. They change when end users see the system for the first time. They change when the project sponsor sees the first prototype. This is fine. This is what is expected. But life gets very complicated when you have to open every single form in your application and alter every single component using the Holy Grail of JavaBean drag-and-drop GUI buildingthe right mouse button.
Cascading Style Sheets
HTML offers developers a simple facility for drastically changing the look and feel of a Web site without changing any HTML codeCascading Style Sheets (CSS). Developers who want to retain their sanity can't go through each and every page in a site and change their hard-coded color and size values every time a client decides to change his/her mind. CSS allows a change to be made in one single part of the site, and then every other page in the site recognizes and implements the change.
Styles in HTML are based on a keyword"Header1", for instanceand a set of attributes that are associated with the keyword. After creating a style sheet for an HTML document, you merely reference the style keyword between your HTML component tags and you are done.
<INPUT type=Button STYLE="stylizedButton">
Somewhere in the style sheet that this HTML document uses there will be an entry for a "
stylizedButton". The "
stylizedButton" style might set the Button component's background to blue with yellow text. If the GUI design changes, then the HTML developer need only change the style located in the style sheet. For instance, perhaps the text on the Button now needs to be white instead of yellow. The developer simply changes the "
stylizedButton" entry and swaps out the color yellow with white to change all the "
stylizedButton" components on the entire page. Now that is convenience.
What Could Be Easier?
In the world of client-side Java GUIs, there is no analog to HTML's CSS. For Java 2 SDK and those of you programming with the Java Foundation Classes, the Pluggable Look and Feel (PLAF) exists. But it still does not get you out of trouble. While it may provide the power to change the entire window management look and feel, Swing's PLAF will not help your form discern a label that serves as a form title from a label that is nothing more than a title for a textfield or some other control. Therefore, when clients decide to change their minds, it is necessary to open up every single GUI class and change the fonts (both typeface and sizing). Unless you plan ahead for such an unfortunate situation, your development team will spend hours right-clicking its way through the application's GUI classes.
However, do not despair. There is a way to prevent this situation and it does not involve finding a perfectly managed client. While Java does not currently have style sheet features, a client-side Java style sheet is just a few lines of code away. Let me explain. In short, an HTML style sheet works like this:
- Define styles in a style sheet.
- Reference the style sheet in an application's HTML code.
- Code the application's components to use the various styles.
While these concepts may seem very easy to grasp, Java does not have any built-in method for defining style sheets. With a little work upfront, though, it is a relatively easy task to create styles for any control and any set of circumstances.
- Define styles in a .properties file.
- Create a StyleDefinition object to read and interpret the .properties file.
- Create a StyleObject to apply styles to an application's controls.
- The objects are initialized at the beginning of any application, and each separate form (Dialog, Window, Frame, etc.) applies the styles before adding the components to a container.
Before I discuss how to implement the style sheet, let's talk a little more about styles.
Defining Styles
Style, as it relates to java.awt GUIs, mainly involves manipulating a component's font, foreground color, and background color. For any Java style, developers need to create a mechanism for specifying and interpreting the following:
- Font name ("Dialog", "Serif", "SansSerif", etc.)
- Font style (Font.PLAIN, Font.BOLD, Font.ITALIC, etc.)
- Font size
- Foreground color (color used to render text)
- Background color (color of the component itself)
Just as HTML style sheets break up style characteristics into separate properties, I have chosen a similar strategy in defining styles. Each style consists of a style prefix (the name of the style), style type (font or color), and a particular property of the style type. I define a style called "
Header1" as follows:
Header1.font.face = SansSerif
Header1.font.style = G
Header1.font.size = 12
Header1.color.foreground = 255 255 0
Header1.color.background = 0 0 255
This style uses a 24-point "
SansSerif" gaudy font, and sets the foreground to yellow and background to blue. Breaking up a style definition into a simple, readable nomenclature greatly simplifies the style-parsing process. Using a delimited text format also ensures that any additional settings that certain components might offer (e.g., insets, vertical gaps, or border colors) will not require a drastic redesign of your style-parsing mechanism.
Parsing a Style
After establishing a structured nomenclature for styles, the real code work begins. Instead of having a single class responsible for both creating styles from a property sheet and applying those styles to components, I have broken out the responsibilities into two separate classes. The first class, StyleDefinition, is responsible for the following functionality:
- Parsing styles from the .properties file.
- Creating a font, foreground color, and background color for each style.
- Returning these objects for each style.
The first problem entails parsing the information from the
.properties file and creating the appropriate font and color objects. The easiest way to parse a
.properties file is to load it as a
ResourceBundle. In looking at the code for my
StyleDefinition object (Listing 1, lines 3857), you will notice that it has no default constructor. It requires a filename, filename and locale, or a
ResourceBundle object to properly function.
Since a style sheet .properties file could contain any number of styles, a StyleDefinition object should use the public Enumeration ResourceBundle.getKeys() to traverse all the styles in the ResourceBundle. Using an instance of StringTokenizer, I simply extract the style prefix from the ResourceBundle key. Once the style prefix has been parsed, it is added to an instance of Hashtable that stores all the keys from the ResourceBundle. While enumerating through the ResourceBundle, I ignore any duplicate keys that the StringTokenizer finds.
Creating the Styles for Each Style Name
Once all the keys have been parsed from the style sheet .properties file, I use another enumerator to go through each style prefix and associate an Object[] for each key in the style Hashtable. This Object array consists of a font and two colors. The Object[] will be used by the StyleObject to apply the font and two colors to any component that is passed to it. First, let me explain how I create a Font from the style sheet .properties file.
Each style key existing in the .properties file defines a font face, a font style, and a font size. Using the style key, the font's face is set to the value returned from ResourceBundle.getString(key.font.face). Parsing the size and style are equally simple tasks. For convenience, I have used a shorthand for font styles: "P" for Font.PLAIN, "I" for Font.ITALIC, "B" for Font.BOLD, and "G" for Font.ITALIC + Font.BOLD. This shorthand allows a simple character case statement to determine the proper font style. The StyleDefinition's createFont(String sKeyName) (Listing 1, line 130) method demonstrates creating a font object from the properties file.
There are many different ways to represent a color in the .properties file. I chose an array of int primitives in the property file to simplify the process by using the public Color(int red, int green, int blue) constructor for creating the necessary colors for each style. Although I should have used the ResourceBundle.getStringArray() method to return the primitive int values, I have not been able to get it working using a .properties file. If anyone knows how, please drop me a line. Instead, I used a StringTokenizer to parse and store each individual int. Once parsed, the StyleDefinition object creates a color using the int array and stores it in the Object[] for that style (Listing 1, line 170).
If you look at java.awt.Component, you will notice there are two different methods for applying colorsthe setForeground and setBackground methods. To determine which is which, you either have to rely on making the ordinals correct when you unpack your Object[] in the StyleObject, or use some constants to ensure the proper color is used for the background and foreground. Once the StyleDefinition object has created the font and colors for each style, each style is stored in a memory-resident Hashtable with an Object[]. All that remains is to write a public method, which allows other classes to request the Object[] by using the style prefix.
Applying Styles Using a StyleObject
After establishing your style nomenclature, creating both a .properties file and a StyleDefinition object, you need only decide how to apply your styles to components. The StyleDefinition object's getStyle(String styleName) method (Listing 1, line 202) returns an Object array consisting of a font and two colors. How do you apply these attributes to your components? By using a StyleObject.
A StyleObject is nothing more than a means to extract the Object[] from the StyleDefinition class and then apply each property to a component. A StyleObject should implement the StyleFormatter interface (Listing 2) and take a StyleDefinition object in its constructor. By following these two rules, we ensure a consistent API and that an initialized StyleDefinition object exists in the StyleObject.
In most cases, developers will want to wrap up a whole slew of similarly styled components and send them to the StyleObject in one call. For this reason, my StyleObject has three different methods developers can use. The first methodpublic component applyStyleDefinition(Component c, String key) (Listing 3, line 36)is responsible for most of the work. For each component passed to this method, it retrieves the Object[] from the StyleDefinition instance, calls the applyFont and applyColor methods, and then returns the styled component.
Since there is only a single way to apply a font to a java.awt.Component, the applyFont method is very simple to code. However, because Components have both foreground and background colors, I use a constant as an argument for the applyColor method (Listing 3, line 73). The two constants used to determine background from foreground are stored in the StyleDefinition object (Listing 1, lines 2021).
As I said, this method does most of the work for you. But formatting an entire user interface component by component is apt to make anyone cranky. After all, the whole point of this exercise is to make your life easier. So in the StyleFormatter interface, I have included two overloaded applyStyleDefinition methods (Listing 3, lines 4653, 5565).One takes a Component[] and returns a Component[]. The other takes a Vector and returns a Vector. In both cases, each individual component is removed from its storage class, styled, and then placed back in the storage class.
Results
Although the styles I have demonstrated are very simple, this article hopefully proved the value of using styles in your GUIs. While the styles I have shownfont, background, and foreground colorsare very simplistic, many opportunities exist for formatting more complex controls. Tools such as SilverStream Application Server 2.5 use a variety of java.awt extension controls that provide developers the power to change insets, header row information for table controls, and other such GUI esoterica. Using the concept of styles, extending both the StyleDefinition and StyleObject classes to handle other controls (as well as creating new style nomenclature), allows the style sheet to format nearly any control.
Naturally, all developers hope their clients will just accept any GUI decisions already made and simply wait for the next version of the product before tinkering around with the look and feel. Just as naturally, they are clients and userstherefore, they won't. But using styles will certainly help you in the fight against constant change.
Now if I could just create a business-rule style sheet....