Monday, November 10, 2008

Layout Prototyping Part 1: XML

GUI Layout is hard, but is it harder then it should be in Java? Can we do something to better the situation? My next few blogs will be about prototyping different approaches in an attempt to find something better. For my first article I'm going to attempt to apply XML to the GUI layout. The goal is to approximate some of the features of MXML from Flex.

In a previous article I compared layout logic in Flex/Flex Builder to Java. I found only one layout manager that had no Java equivalent. I wrote CoordinateLayout to mimic absolute layout from Flex. Here is a very basic example of it's usage.


public class CoordinateLayoutDemo extends JPanel {

public CoordinateLayoutDemo() {
setLayout(new CoordinateLayout());

add(new JLabel("Last Name:"), "x=10, y=10");
add(new JTextField(), "x=87, y=8, width=223");

add(new JLabel("First Name:"), "x=318, y=10");
add(new JTextField(), "x=395, y=10, width=207");

add(new JLabel("Phone:"), "x=10, y=36");
add(new JTextField(), "x=87, y=34, width=223");

add(new JLabel("Email:"), "x=318, y=36");
add(new JTextField(), "x=395, y=34, width=207");

add(new JLabel("Address:"), "x=10, y=62");
add(new JTextField(), "x=87, y=60, width=515");

add(new JLabel("City:"), "x=10, y=88");
add(new JTextField(), "x=87, y=86, width=223");

add(new JLabel("State"), "x=318, y=88");
add(new JTextField(), "x=395, y=86, width=207");
}
}



This lets one layout a screen pixel by pixel. It also has an "anchor mode" that allows one to determine how a component will resize by specifying the location of an anchor on one the four sides.


public class CoordinateLayoutDemo2 extends JPanel {
/**
*
*/
private static final long serialVersionUID = 5744676397576694341L;

public CoordinateLayoutDemo2() {
setLayout(new CoordinateLayout());

add(new JScrollPane(new JTextArea()),
"left=35, top=40, right=10, bottom=10");
add(new JSlider(SwingConstants.VERTICAL), "bottom=10, left=10, top=40");
add(new JComboBox(), "right=287, left=35, top=10");
add(new JButton("Button"), "right=10, top=10");
}
}


This code results in the following screen.



The only other thing I need is XML bindings and I have functionality similar to absolute layout.

XMLBeans to the Rescue.

Without going into too much detail, XMLbeans takes an XSD (XML Schema Definition) and creates java classes that can parse XML that conforms to that schema.

Here is a tiny schema that represents CoordinateLayout.


<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="CoordinateLayout">
<xs:complexType>
<xs:sequence maxOccurs="unbounded">
<xs:element name="item">
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="x" type="xs:int" />
<xs:attribute name="y" type="xs:int" />
<xs:attribute name="width" type="xs:string" />
<xs:attribute name="height" type="xs:string" />
<xs:attribute name="top" type="xs:int" />
<xs:attribute name="left" type="xs:int" />
<xs:attribute name="bottom" type="xs:int" />
<xs:attribute name="right" type="xs:int" />
<xs:attribute name="vcenter" type="xs:int" />
<xs:attribute name="hcenter" type="xs:int" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

If I run this XSD through the XMLBeans schema compiler (scomp) I get a jar with classes that parse this schema. Now with a little drudgery to move the values from the schema generated objects to my layout I am able to represent my UI as an XML file.

Here is an XML file that conforms the XSD and arranges the components the same way as the first example.

<?xml version="1.0" encoding="UTF-8"?>
<CoordinateLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <item name="lastNameLabel" x="10" y="10" />
    <item name="lastName" x="87" y="8" width="223" />

    <item name="firstNameLabel" x="318" y="10" />
    <item name="firstName" x="395" y="10" width="207" />

    <item name="phoneLabel" x="10" y="36" />
    <item name="phone" x="87" y="34" width="223" />

    <item name="emailLabel" x="318" y="36" />
    <item name="email" x="395" y="34" width="207" />

    <item name="addressLabel" x="10" y="62" />
    <item name="address" x="87" y="60" width="515"/>

    <item name="cityLabel" x="10" y="88" />
    <item name="city" x="87" y="86" width="223"/>

    <item name="stateLabel" x="318" y="88"/>
    <item name="state" x="395" y="86" width="207"/>
</CoordinateLayout>


After all that I took a long hard look at my XML systems and asked myself two questions.

1. Is this roughly equivalent to absolute layout from Flex?
2. Does this make layout any easier?

The answer to number one is, yes. I consider it close enough to the the layout functionality of absolute layout.

Number two, well no. XML doesn't help. No matter which way you slice it specifying pixel locations is not the simplest way of laying out components.

Not wanting to abandon this XML idea I figured I'd apply it to other layout managers.

The first one I tried was TableLayout. I applied the same steps.

1. Create an XML schema that represents the TableLayout finctionality.
2. Use XMLBeans to generate a jar file with the parsing code.
3. Glue the XMLBeans generated object to TableLayout.

This allowed me to create the following XML file.


<?xml version="1.0" encoding="utf-8"?>
<tablelayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    vgap="1" hgap="1">

    <columnsizes>
        <size>10</size><size>FILL</size><size>10</size><size>PREFERRED</size>
        <size>10</size><size>PREFERRED</size><size>10</size>
    </columnsizes>

    <rowsizes>
        <size>10</size><size>PREFERRED</size>
        <size>5</size><size>PREFERRED</size><size>10</size>
        <size>PREFERRED</size><size>5</size>
        <size>PREFERRED</size><size>10</size>
        <size>PREFERRED</size><size>5</size>
        <size>PREFERRED</size><size>10</size>
        <size>PREFERRED</size><size>10</size>
    </rowsizes>

    <item name="nameLabel" col1="1" row1="1" col2="5" row2="1" />
    <item name="name" col1="1" row1="3" col2="5" row2="3" />

    <item name="addLabel" col1="1" row1="5" col2="5" row2="5" />
    <item name="address" col1="1" row1="7" col2="5" row2="7" />

    <item name="cityLabel" col1="1" row1="9" />
    <item name="city" col1="1" row1="11" />

    <item name="stateLabel" col1="3" row1="9" />
    <item name="state" col1="3" row1="11" />

    <item name="zipLabel" col1="5" row1="9" />
    <item name="zip" col1="5" row1="11" />

    <item name="button" col1="1" row1="13" col2="5" row2="13" />
</tablelayout>


Which results in this:



I did the same thing with GridBagLayout.

<?xml version="1.0" encoding="utf-8"?>
<gridbag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <defaults anchor="EAST" top="5" left="5" right="5" />
    <item name="lastNameLabel" />
    <item name="lastNameTF" fill="HORIZONTAL" weightx="1" />
    <item name="firstNameLabel" />
    <item name="firstNameTF" fill="HORIZONTAL" weightx="1" gridwidth="REMAINDER" />
    <item name="phone" />
    <item name="phoneTF" fill="HORIZONTAL" weightx="1" />
    <item name="email"/>
    <item name="emailTF" fill="HORIZONTAL" weightx="1" gridwidth="REMAINDER" />
    <item name="address" />
    <item name="addressTF" fill="HORIZONTAL" weightx="1" gridwidth="REMAINDER" />
    <item name="city" bottom="5"/>
    <item name="cityTF" fill="HORIZONTAL" weightx="1" bottom="5"/>
    <item name="state" bottom="5"/>
    <item name="stateTF" fill="HORIZONTAL" weightx="1" bottom="5"/>
</gridbag>


Which actually looks like less code then writing this in Java. That's not hard though. Gridbag is rather cumbersome to use.

I decided to go through this process once more. This time with MigLayout. In the end I was disappointed. Not because it didn't work. Not because of any failure in MigLayout. Just because it was too simple. Now I could have built an XSD around the type-safe constraint objects available in MigLayout. Unfortunately that was going to make for some very verbose XML. Instead I just packaged up the string version of the constraints in some XML and called it a day.

<?xml version="1.0" encoding="utf-8"?>
<miglayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    layout="ins 20" column="[para]0[][100lp, fill][60lp][95lp, fill]" row="">
   
    <item name="man" params="skip" />
    <item name="manSep" params="span 3, growx, wrap" />
   
    <item name="compLbl" params="skip" />
    <item name="company" params="span, growx" />
    <item name="contactLabel" params="skip" />
    <item name="contact" params="span, growx" />
    <item name="orderLabel" params="skip" />
    <item name="order" params="wrap para" />

    <item name="ins" params="skip" />
    <item name="insSep" params="span 3, growx, wrap" />
   
    <item name="namelabel" params="skip" />
    <item name="name" params="span, growx" />
    <item name="refNoLabel" params="skip" />
    <item name="refNo" params="wrap" />
    <item name="statLabel" params="skip" />
    <item name="stat" params="wrap para" />
   
    <item name="ship" params="skip" />
    <item name="shipSep" params="span 4, growx, wrap" />
   
    <item name="yardLabel" params="skip" />
    <item name="yard" params="span, growx" />
    <item name="regLabel" params="skip" />
    <item name="reg" params=""/>
    <item name="hullLabel" params="right" />
    <item name="hull" params="wrap" />
    <item name="typeLabel" params="skip" />
    <item name="type" params=""/>
</miglayout>


About five minuets after I finished I realized I created an XSD for what could be more simply represented as a properties file.

Here is a properties file version of the same layout.
layout = ins 20 
column = [para]0[][100lp, fill][60lp][95lp, fill]
row =
man = skip
manSep = span 3, growx, wrap

compLbl = skip
company = span, growx
contactLabel = skip
contact = span, growx
orderLabel = skip
order = wrap para

ins = skip
insSep = span 3, growx, wrap

namelabel = skip
name = span, growx
refNoLabel = skip
refNo = wrap
statLabel = skip
stat = wrap para

ship = skip
shipSep = span 4, growx, wrap

yardLabel = skip
yard = span, growx
regLabel = skip
reg =
hullLabel = right
hull = wrap
typeLabel = skip
type=


So what did I accomplish? Well I can say I successfully built a system that decouples layout parameters from components. Certainly decoupling can be a good design pattern. Gridbag showed that there was some benefit to the XML way of doing things. Yet MigLayout showed that in the end it's all about the capability of the layout manager itself and not the markup. I don't think just applying XML is going to make GUI layout easier all by itself.

I have a web start app demonstrating my prototype.



The source is also available.

xmllayoutdemo-src.zip

No comments: