Swing and CSS Blog

Version 2


    In the old days of the Web, you had to write everything by hand, and you had to do it over and over agai n. If there was anything common between pages, you copied and pasted. Later, templating systems became commonplace, but were limited in scope and often restricted to compile-time tasks. If your content came from a database or other structured source, then you could set fonts and colors programmatica lly. But if you were in charge of, say, a magazine, then you couldn't change the look of a given article without laboriously parsing it all with regular expressions.

    Then along came CSS.

    Though support wasn't great at first, CSS finally lets you separate style from content in a way that works. Now you can set all of your paragraphs to be in a sans-serif font on a blue background, or you can make everything a rollover, if you want. The important thing is that the style can be kept in a separate file and then applied at runtime by the browser. Now you can set a style for your whole site at once, enforcing consistency, and change it later.

    In this article we'll take a similar journey. We'll consider how to take advantage of some of the styling benefits of CSS in a Swing application. When you have a large application written by tens (or even hundreds) of developers over years, setting a consistent style can be very helpful. Just as CSS allows you to maintain a consistent look across a complex web site, we will use the same technique to achieve this consistency across many screens in a complicated Swing application.

    CSS Vs. Look and Feel

    Before we get into a CSS implementation, let's consider the alternative: a custom look and feel. Swing Look and Feels (L&Fs) are sets of classes that implement the actual drawing of components at a very low level (think lines and bitmaps). They can be swapped out for new ones at runtime, often to implement the look of a native platform; i.e., the JDK for OSX has a set of classes that make Swing apps look like native Aqua apps, with candy buttons and blue tint. Custom L&Fs are powerful, but not trivial or quick to build. You will usually have to touch 20 or so classes and implement a whole bunch of special drawing code.

    Custom L&Fs also have the disadvantage of being global. To change the look of one button, you have to change them all. Our system lets you change just certain objects and leave the rest alone, and changing the style later with our system will be quick and painless. Want a new style? Just edit a text file and reload.

    On the other hand, since a custom L&F has access to low-level rendering, it can do effects and styles that are impossible at our level. We can imagine gradients and images for backgrounds, gaussian blurs for disabled buttons, and animation for tabbed pane transitions. In a future article, I hope to create a hybrid system that will exploit these possibilities.

    A Sample Application

    Consider an application that has hundreds of screens: all related, but different enough to require separate coding. If we want a common style, then our options may include the following:

    • Create a complete custom look and feel.
    • Go through every file for every screen and change every Swing constructor to a factory method for pre-styled components.

    Why can't we use something like CSS? A running Swing application is basically a tree of objects, so couldn't we apply the style at runtime as rules laid on top of the tree? By analogy, web pages are represented as the document object model (or DOM), which, for our purposes, means a tree of objects, one object for each element (paragraph, text field, image, etc.). CSS is a set of styles (such as colors and fonts) applied to the objects according to rules. The rules are described in the top of the web page or in a separate file, using the CSS syntax. The rules can be simple (make all P elements be bold) or complex (make every other P node inside of each span node named "header" be bold).

    We are going to do the same thing with Swing. Instead of HTML nodes, we have Swing components. The rules will be described in an external XML file that we will apply at runtime. Since we are just starting, we will apply only simple rules and styles to keep the process clear. Later, we can add more complex effects to make the system really useful.

    We are going to use a simple instant messenger screen as our test application. Below is the minimum amount of code needed to get the widgets on the screen.


    public class IMScreen1 { public static JFrame initFrame() { JLabel label = new JLabel("Ugliest IM in the world"); JTextArea textarea = new JTextArea("joshy: What do you think?\n" + "\n" + "lizi: I think that it's awful!"); JTextField textfield = new JTextField("Is it really that bad?"); JButton button = new JButton("Send"); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2,2)); panel.add(textarea); panel.add(label); panel.add(textfield); panel.add(button); JFrame frame = new JFrame(); frame.setContentPane(panel); return frame; } public static void main(String[] args) { JFrame frame = initFrame(); frame.pack(); frame.show(); } }

    We have a JFrame containing one each of the following: button, label, text field, and text area. All are aligned with a grid layout.

    Compile and run to get this:

    Brute Force: Setting Properties

    As a first approach, let's make changes to the components by just setting properties. You can do a lot with borders, fonts, and colors. Here we have changed the background color of the panel and button, added more space around the text components, right-aligned the label, and made the button bold. And just for good measure, we switch the layout to a vertical box.


     panel.setLayout(new GridLayout(2,2)); panel.add(label); panel.add(textarea); panel.add(textfield); panel.add(button); // lets set the style now Color pink = new Color(255,130,130); panel.setBackground(pink); button.setBackground(pink); // create a border Border border = BorderFactory.createEmptyBorder(5,5,5,5); Border ta_border = BorderFactory.createCompoundBorder(textarea.getBorder(),border); textarea.setBorder(ta_border); Border tf_border = BorderFactory.createCompoundBorder(textfield.getBorder(),border); textfield.setBorder(tf_border); // set alignment and make the text field transparent label.setHorizontalAlignment(SwingConstants.RIGHT); // make the button be bold button.setFont(button.getFont().deriveFont(Font.BOLD)); // install a new vertical layout panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));

    Compile and run. Now, we get this:

    We've made a big visual change with just a few commands. Not bad, but this is a small project. If we were working on something bigger, it would be nice to do it programmatically so that we can reuse each setting.

    Simplifying the Process Using Rules

    As a second approach, we will start using rules. To keep it simple, we will select only by class, with each property specified by a string name and a string value. We would like it to be something along these lines:addRule(JButton,"font-style","bold"). This means that for all JButtons, set the font-style tobold. This is very similar to the CSS equivalent:JButton { font-style : bold }. With rules, we can rewrite our screen to remove the style settings frominitFrame(), and add these lines:


     public static void main(String[] args) { // set up the styles first RuleManager rm = new RuleManager(); rm.addClassRule(JPanel.class,"background","#ff9999"); rm.addClassRule(JButton.class,"background","#ff9999"); rm.addClassRule(JTextField.class,"margin","5"); rm.addClassRule(JTextArea.class,"margin","5"); rm.addClassRule(JLabel.class,"alignment","right"); rm.addClassRule(JButton.class,"font-style","bold"); rm.addClassRule(JPanel.class,"layout","column"); JFrame frame = initFrame(); rm.style(frame); frame.pack(); frame.show(); }

    Now we have a RuleManager that we can use to add and apply our rules. Each line creates a new rule that applies one property setting to one class type. If you want two properties on the same class (say, color and font) then you have to call it twice. C'est facile!

    Under the hood, the RuleManager looks like this:


    public class RuleManager { private List rules; public RuleManager() { rules = new ArrayList(); } public void addClassRule(Class clss, String property, String value) { Rule rule = new ClassRule(clss, property, value); rules.add(rule); } public void style(Component comp) { // loop over the rules to find matches Iterator it = rules.iterator(); while(it.hasNext()) { Rule rule = (Rule)it.next(); // apply the rule if it matches if(rule.matches(comp)) { rule.apply(comp); } } // loop over the children and call style recursively if(! (comp instanceof Container)) { return; } Component[] comps = ((Container)comp).getComponents(); for(int i=0; i<comps.length; i++) { style(comps[i]); } } }

    Laying the Foundation for CSS

    To anticipate future types of matching, we have created an interface called Rule that specifies matching and applying, and then a concrete implementation that matches on class types, called ClassRule. Each call toaddClassRule() creates a ClassRule object that implements the property settings. To actually apply the rules, the style() method is called. This does a pre-order traversal of the entire tree, first looking for all rules that match the current node and then recursing over all of the children.

    Rule and ClassRule look like what you would expect:


    public interface Rule { public boolean matches(Object obj); public void apply(Object obj); }


     public boolean matches(Object obj) { if(clss.isInstance(obj)) { return true; } return false; } public void apply(Object obj) { JComponent comp = (JComponent)obj; if(property.equals("background")) { comp.setBackground(Color.decode(value)); } if(property.equals("margin")) { int margin = Integer.parseInt(value); Border m_border = BorderFactory.createEmptyBorder(margin,margin,margin,margin); Border c_border = BorderFactory.createCompoundBorder(comp.getBorder(),m_border); comp.setBorder(c_border); } if(property.equals("alignment")) { if(comp instanceof JLabel) { int align = -1; if(value.equals("left")) { align = SwingConstants.LEFT; } if(value.equals("center")) { align = SwingConstants.CENTER;   } if(value.equals("right")) { align = SwingConstants.RIGHT;     } ((JLabel)comp).setHorizontalAlignment(align); } } if(property.equals("font-style")) { if(value.equals("bold")) { comp.setFont(comp.getFont().deriveFont(Font.BOLD)); } if(value.equals("italics")) { comp.setFont(comp.getFont().deriveFont(Font.ITALIC)); } } if(property.equals("layout")) { if(value.equals("column")) { comp.setLayout(new BoxLayout(comp,BoxLayout.Y_AXIS)); } } }

    ClassRule's apply method is just the programmatic version of the styling code we had before. I should note that if this system was expanded to include all of the possible Swing settings, then ClassRule would become too much to handle. At some point, we would have to refactor the system to separate the matching (or selecting, in CSS-speak) from the application of the rules. For this article, though, I thought it best to keep it simple.

    Style Sheets for Swing Applications

    Now that we can programmatically set up rules, it is a simple matter to load the rules from an XML file. I've created simple XML language to match the in-memory structure. It's just a list of rules with the class, property, and value specified for each one. We can easily imagine expanding this in the future as we design more elaborate selectors and properties.


    <css> <rule class="JPanel" property="background" value="#ff9999"/> <rule class="JButton" property="background" value="#ff9999"/> <rule class="JTextField" property="margin" value="5"/> <rule class="JTextArea" property="margin" value="5"/> <rule class="JLabel" property="alignment" value="right"/> <rule class="JButton" property="font-style" value="bold"/> <rule class="JPanel" property="layout" value="column"/> </css>

    Then I've created another class called CSSLoader to load the XML file and parse each rule element into a call to theRuleManager; pretty straightforward code using the XML APIs. In particular, notice the call tocss.getElementsByTagName() instead of callinggetChildNodes(). This ensures that we skip both white space and non-"rule" elements (since we may add other kinds of rules in the future).


    public class CSSLoader { public static void load(String xml, RuleManager rm) throws Exception { DocumentBuilder builder = DocumentBuilderFactory.newInstance(). newDocumentBuilder(); Document doc = builder.parse(xml); Element css = doc.getDocumentElement(); NodeList list = css.getElementsByTagName("rule"); for(int i=0; i<list.getLength(); i++) { Element rule = (Element)list.item(i); String clss = rule.getAttribute("class"); String property = rule.getAttribute("property"); String value = rule.getAttribute("value"); clss = "javax.swing."+clss; Class real_class = Class.forName(clss); rm.addClassRule(real_class,property,value); } } }

    And now we can simplify our main function again to look like this:


     public static void main(String[] args) throws Exception { // set up the styles first RuleManager rm = new RuleManager(); CSSLoader.load("css.xml",rm); JFrame frame = initFrame(); rm.style(frame); frame.pack(); frame.show(); }

    The great thing about refactoring is that your code tends to getsmaller and easier to read as you push things out into other classes.

    Now we have a system to work with. From this base, we can build support for many more kinds of styling. The Swing framework allows almost anything to be done at runtime, and now we can take advantage of that by using a single function call per frame to style all of the components in our application. In a future article, I hope to explore more advanced styling techniques.

    There's only one problem now. What do you do if you don't have access to the source code? Some desktop toolkits allow you to customize your theme based on user preferences that apply to all applications. Our system can do that too, if there is a standardized location for the CSS file to reside, much like other user preferences. This only works for applications that have been CSS enabled, though. What about existing applications? Couldn't we do something for them just like existing HTML can be re-rendered using user-specified stylesheets? Of course we can, because of a very important but rarely seen object called theToolkit.

    Formatting Components as They are Created

    java.awt.Toolkit is special because it forms the bridge between the virtual world of AWT and Swing and the real graphics APIs on the underlying platform (Win32 GDI under Windows, Quartz under OSX, and X11 under Unix). It can give you information about the current environment, allocate system objects, and do a few other tricks, like setting the keyboard lights (see my blogs). It also lets you listen to the event queue. By creating anAWTEventListener registered with the Toolkit, we can see every Swing event in the entire system from one place. For our system, we want to know whenever a component is added to a container, since that is the most likely time to do other Swing adjustments.

    I've created a SwingWatcher class that does this. It is a singleton, to make sure we only have one at a time running in the system. Each time this class detects that a component has been added, it runs the style system on it. Then I created a Launcher program. Its sole purpose is to set up the CSS system and then start the real program.


    public class SwingWatcher implements AWTEventListener { private static SwingWatcher watcher = new SwingWatcher(); private RuleManager rm; private SwingWatcher() { Toolkit tk = Toolkit.getDefaultToolkit(); tk.addAWTEventListener(this,AWTEvent.CONTAINER_EVENT_MASK); } public void eventDispatched(AWTEvent evt) { if(evt instanceof ContainerEvent) { ContainerEvent cevt = (ContainerEvent)evt; if(cevt.getID() == ContainerEvent.COMPONENT_ADDED) { Component comp = cevt.getChild(); if(rm != null) { rm.style(comp); } } } } public static void start(RuleManager rule_m) { watcher.rm = rule_m; } }


    public class IMLauncher { public static void main(String[] args) throws Exception { // set up the styles first RuleManager rm = new RuleManager(); CSSLoader.load("css.xml",rm); SwingWatcher.start(rm); // now start the real program IMScreen5.main(args); } }

    With these two pieces, we can now run an unaltered Swing application with our styling, as long as we know the name of the startup class.


    This is just a taste of what we can do with our system. We have taken a tree of Swing components and modified them programmatically to implement custom styling. We then refactored the system to separate the styling mechanism from the normal Swing code and take advantage of low-level AWT services to detect and apply the style at runtime from a text file. This system gives us an impressive amount of flexibility. Non-programmers can style completely unmodified applications for which they don't even have the code. But this is just the start.

    In the future, we can add advanced selectors and component control. Swing's underlying design allows us to enhance the system for any number of new features, some useful and some just plain fun.

    • Named components: Just as in HTML, Swing lets you to assign a text name to each component. We could implement CSS-likeclass and id selectors. This allows the kind of high-level design CSS web applications enjoy: where different logical portions of an application are marked separately and the system takes care of custom styling.

    • Platform-specific styling: Native platform look and feels may not play nicely with our CSSish styles, or at least not look very pretty. A color scheme that looks good with Sun's grey Metal L&F might look awful on top of OSX's candy buttons. Marking certain styles for different platforms would help greatly.

    • Accessibility and non-visual styles: Imagine a Swing app that not only looks great, but also sends UI descriptions to a braille reader as well, and not just a default listing of menu items, but a navigation scheme optimized for a linear experience.

    • Graphics hacks: Creative use of theglasspane and proxy Graphicsobjects could let us do animations, rollovers, and visual effects that were impossible before. Imagine being able to pump a scrollbar through an arbitrary matrix operation (scale, shear, rotate) without the original component ever knowing.

    • Flexible Formatting: CSS lets web developers specify formatting at a higher level than table layout. Swing CSS lets us use constructs higher than the GridBagLayout. Prefab layouts like editor with toolbar, wizard, andthree-paned email client would greatly speed development and keep application screens consistent.

    I hope you've enjoyed exploring the dynamic side of Swing with me today. Swing is a rich and flexible toolkit with endless possibilities. By adding a new layer of separation between style and content, we have made it all the more powerful. Through innovation and creative design, Swing lets us make ever better software, and in the end, that's why we're all here.