In the old days of the Web, you had to write everything by hand, and
you had to do it over and over again. 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 programmatically. 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.
IMScreen1.java
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.
IMScreen2.java
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 to bold. This is very
similar to the CSS equivalent: JButton { font-style : bold }.
With rules, we can rewrite our screen to remove the style settings from
initFrame(), and add these lines:
IMScreen3.java
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:
RuleManager.java
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 to addClassRule() 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:
Rule.java
public interface Rule {
public boolean matches(Object obj);
public void apply(Object obj);
}
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.
Then I've created another class called CSSLoader to load the
XML file and parse each rule element into a call to the
RuleManager; pretty straightforward code using the XML APIs. In
particular, notice the call to css.getElementsByTagName() instead of
calling getChildNodes(). This ensures that we skip both white space and non-"rule" elements (since we may add other kinds of rules in the future).
CSSLoader.java
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:
IMScreen4.java
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 get
smaller 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 the
Toolkit.
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 an AWTEventListener 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 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.
Summary
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-like
class 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 the glasspane and proxyGraphics objects 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, and three-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.
Joshua Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.
Language neutral UI design in needed
2003-10-24 02:32:47 zhangling
[Reply | View]
UI design should not bind to specific programming language. It should be a model that can be interpreted to many development tool and platform.
We need a neural UI editors that really "draw" UI controls, then save it as a UI model files according to future UI spec.
Any languages and its tools compiles that model to its own implementation, regardless whether is for .NET or Java.
Programmer should escape from the UI "drawing" disaster, especially for Java developer, cause it is really slow!!
Language neutral UI design in needed
2003-10-29 06:06:17 gdbolin
[Reply | View]
http://www.mycgiserver.com/~thinlet/
Thinlet is a pretty good solution as apposed to the CSS way described in this article. Though it only partains to JAVA and the XML file is specific for JAVA. Not that someone couldn't write a program to parse it for other Languages. It works pretty well.
However, initial loading time for this kind of approach has to be slower. And Java's current load time could be better as is without adding the complexity of rendering the entire GUI from an XML file.
Language neutral UI design in needed
2003-10-24 05:42:49 joshy
[Reply | View]
I agree completely. I would love to see a language neutral representation of a UI screen, which can then be interpreted/compiled into some other implemenetation. One UI model wouldn't be able to handle everything, but it could certainly handle all of the common objects that would show up on desktop screens. Maybe we could then have different models for smaller devices (though something like CSS for Swing could go a long way for letting one model serve lots of different devices.).
I agree that coding UIs under Java is a disaster. Creating UIs is inherently a visual task and should be done with visual editors.
Language neutral UI design in needed
2003-10-29 06:02:36 gdbolin
[Reply | View]
QT Designer actually does this. When you create widgets in QT designer, all you are doing is creating an XML file. Then, you run a sort of qmake on the XML files and it generates the C++ code for you. Then you of course run make or nmake and 'viola'. There is a port of the qmake for JAVA, I think it is called something like QtJava. It's basically a java program that takes QT's XML files and writes your JAVA code for you with all the SWING components. It's still not where it could be because you are allowing QT to set the standard for the Widget descriptions in the XML file, but at least someone has the right idea.
I think it is misleading to call this approach CSS. CSS implies the web standard, not a generic style sheet. I like the idea of dynamically configurable UIs and wish that it wasn't so much work to graft the approach onto Swing after the fact. I suppose one could extend key Swing classes to be styled versions of the class.
Well, this was meant to be CSS ish. I realize that the syntax is actuall in XML instead of the curly brace markup of CSS, but a true CSS parser could be easily written. It was just beyond the scope of this article. However I do think it's fair to call it CSS because it uses the CSS design of separating style (description of what something looks like) from structure (the actual tree of ui components).
Swing is completely dynamic and has the ability to do lots of configurable things ilke this, it's just that no one has ever used it. Maybe this is something Sun should push. Or maybe we need to form a community to work on it.l
I had the idea to use XML to represent the layout of Swing component, and posted it here:
http://soronthar.blog-city.com/read/182037.htm
And hacked an implementation that can be found at :
http://sourceforge.net/projects/xmlgridlayout/
Because time constraints I haven't been able to continue working on this (or improving the documentation).
What's it good for ?
2003-10-16 04:10:19 nightrider
[Reply | View]
I don't really see the usefullness of this approach.
Users expect consistent UIs, not UIs with lots of different colors and styles.
One interesting thing, that wasn't addressed in the article, is localization. What about including translated text for menus, dialogs, etc ?
What's it good for ?
2003-10-24 05:50:07 joshy
[Reply | View]
This has definately been on my mind. It wouldn't be too hard, though perhaps not terribly reliable. A Swing GUI doesn't usually have paragraphs of text, just single words and phrases. I can imagine a hybrid approach where the designer codes in English (or whatever the native language) and then the phrases are replaced at runtime with localized versions. Some of the replacements are specified by widget in a config file. (ie: this button's text should go to 'Fini' for French). Some are replaced by phrase (replace all 'End's with 'Fini's), and some would come from a standard dictionary so that no project specific work was done. The important thing is that all localized text is done in a config file which can be modified and applied by a localization expert, not by the programmer.
What about SkinLF
2003-10-15 11:08:05 jonathansimon
[Reply | View]
Aside from the hierarchical perspective (which Im not totally convinced on yet) what about Skin Look and Feel?
What about SkinLF
2003-10-15 12:10:19 joshy
[Reply | View]
What do you mean Skin L&F? Do you mean a standard syntax for defining look and feels, like the GTK Themes?
What about SkinLF
2003-10-18 12:09:20 carloscs
[Reply | View]
http://www.l2fprod.com/
Looked at SAC?
2003-10-15 11:05:27 ba22a
[Reply | View]
Very nice!
I was just wondering if it might be possible to use SAC, so you could use "real" css rather than just a CSS-a-like.
http://www.w3.org/Style/CSS/SAC/
SAC is the "simple api for css", there's a couple of java css parsers that use it. It doesn't tie you to the css properties used by html, but it does support the css selection model, and a "well known" syntax. Might be useful.
Looked at SAC?
2003-10-15 12:11:07 joshy
[Reply | View]
Ooh. Very nifty. Yeah, that would be very nice to work with. I'd still want to support the XML syntax for integration with other tools but for hand use the CSS syntax is certainly friendlier.
- Joshua
Looked at SAC?
2007-05-31 08:55:15 gullcatcher
[Reply | View]
Two things:
* xpath seems just as good a selector mechanism, without having to go down the route of re-implementing or bringing down another jar.
* if you want to go down the CSS route, why not go the whole way - layout and styling in CSS against unstyled html. XHTML Forms could even be re-used for buttons/drop downs/etc.
Looked at SAC?
2007-05-31 09:36:42 joshy
[Reply | View]
Actually, I ended up doing pretty much exactly this. Take a look at http://xhtmlrenderer.dev.java.net/
What about UIDefaults?
2003-10-15 02:53:02 jdf
[Reply | View]
Your scheme for setting default properties of JComponents seems a bit arcane, given that Swing comes with a mechanism for doing just that. What's wrong with the existing infrastructure for implementing L&F?
Re: What about UIDefaults?
2003-10-15 05:38:05 joshy
[Reply | View]
I designed it this way because it can do more than just set defaults by class. What if you would like to set defaults by location in the component tree, or by the getName property? It also lets us introduce new behaviors that are supported by default, like adding a sound to button presses or pulling in images. And most importantly we can do this without the knowledge of the existing program.
I also wanted to do it in a way that was similar to CSS, since many people already understand how that works.
Using scripts?
2003-10-15 00:40:24 velhonoja
[Reply | View]
Interesting article
What if we use script language like BeanShell (http://www.beanshell.org) to set up styles?
It gives more flexibility, though it makes XML file little more complex.
For example...
<beanshell>
gray = new Color( 250,250,250 );
green = new Color( 150,250,150 );
/*
put all properties for JButton in one method
*/
handleJButton( button ){
button.setBackground( gray );
handleBorder( button, 3 );
button.setForeground( green );
}
</beanshell>
<rules>
<!--
current is Swing component which is being edited
-->
<rule class="JPanel"
beanshell="current.setBackground( green );"/>
<rule class="JTextField"
beanshell="handleBorder( current, 5 );"/>
<rule class="JButton"
beanshell="handleJButton( current );"/>
</rules>
Re: Using scripts?
2003-10-15 05:39:59 joshy
[Reply | View]
Very Cool!
I like the idea of allow adhoc settings for advanced users. Now if only we use this idea in webpages by having Javascript embedded into our CSS.
CSS to Swing
2003-10-14 11:06:36 mardigrasboy
[Reply | View]
I see a lot of potential here. Any luck in going th eother way, Swing to web?
CSS to Swing
2003-10-17 20:58:05 hparra
[Reply | View]
There is a open source project that does that:
WebOnSwing
http://webonswing.dev.java.net/
It's really nice and functional.
CSS to Swing
2003-10-15 12:24:54 jvillano
[Reply | View]
Check out XTT (XML Tunneling Technology) from InsiTech Group.
The XTT Framework delivers a robust RAD environment for creating rich, ultra-thin, Swing-based GUI J2EE applications with the benefit of having only a single code base to manage. XTT integrates with Sun ONE Studio, NetBeans and JBuilder IDEs, for integrated development and deployment of J2EE and rich GUI J2SE applications.
The Free Community Edition of XTT can be downloaded from www.netbeans.org or www.insitechgroup.com.
CSS to Swing
2003-10-14 13:26:30 tlaurenzo
[Reply | View]
Check out the "echo" framework: http://www.nextapp.com/products/echo/
It is open source, despite the commercial looking website. It is not based on the Swing API but duplicates the ideas and structures in many places. Basically, I would say that any Swing programmer should have no problem picking it up. Definitely play with the interactive demos on the site. Once you find out how much code it takes to do all of the cool stuff in the demos, you will be salivating.
CSS to Swing
2003-10-15 05:30:12 joshy
[Reply | View]
I have to say that Echo is quite impressive. They've done a great job of putting a Swing component model into a web framework. I have my doubts, however, if this is the best route. The web is great for web style interaction. When we try to shoehorn desktop application kinds of interaction in to the web we end up with compromise solutions that users typically do not like. I think that we will have to evolve towards more thick client technologies like Java WebStart.