Sunday, January 11, 2009

Easier Swing

Google has released 0.3 of Google SwingBuilder.  The download includes eight jar files that need to be included in your project. SwingBuilder is a declarative UI framework.  The view is defined in a YAML file, the model in POJOs.  SwingBuilder incorporates data binding, input validation and background thread processing (via SwingWorker).

Below is the source that defines and displays a simple screen.  Be aware that a tab in the YAML file will cause the parsing of the YAML file to fail.

src/main/java/person/app/PersonApp.yaml
JFrame(name=frame, title=frame.title, size=packed, defaultCloseOperation=exitOnClose):
  - JLabel(name=fNameLbl, text=label.firstName)
  - JLabel(name=lNameLbl, text=label.lastName)
  - JLabel(name=emailLbl, text=label.email)
  - JTextField(name=fName)
  - JTextField(name=lName)
  - JTextField(name=email)
  - JButton(name=save, text=button.save, onAction=($validate,save))
  - JButton(name=cancel, text=button.cancel, onAction=($confirm,cancel))
  - MigLayout: |
      [pref]    [grow,100]  [pref]    [grow,100]
      fNameLbl  fName       lNameLbl  lName
      emailLbl email+*
      >save+*=1,cancel=1
bind:
  - fName.text: this.person.firstName
  - lName.text: this.person.lastName
  - email.text: this.person.email
validate:
  - fName.text: {mandatory: true, label: label.firstName}
  - lName.text: {mandatory: true, label: label.lastName}
  - email.text: {mandatory: true, emailAddress: true, label: label.email} 
src/main/java/person/app/PersonApp.java
package person.app;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.javabuilders.BuildResult;
import org.javabuilders.annotations.DoInBackground;
import org.javabuilders.event.BackgroundEvent;
import org.javabuilders.event.CancelStatus;
import org.javabuilders.swing.SwingJavaBuilder;

public class PersonApp extends JFrame {
    private Person person;
    private BuildResult result;

    public PersonApp() {
        person = new Person();
        person.setFirstName("John");
        person.setLastName("Smith");
        result = SwingJavaBuilder.build(this);
    }

    public Person getPerson() {
        return person;
    }

    private void cancel() {
        setVisible(false);
    }

    @DoInBackground(cancelable = true, indeterminateProgress = false,
            progressStart = 1, progressEnd = 100)
    private void save(BackgroundEvent evt) {
//simulate a long running save to a database
        for (int i = 0; i < 100; i++) {
            evt.setProgressValue(i + 1);
            if (evt.getCancelStatus() != CancelStatus.REQUESTED) {
//sleep
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            } else {
//cancel requested, let's abort
                evt.setCancelStatus(CancelStatus.COMPLETED);
                break;
            }
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
//activate internationalization
                SwingJavaBuilder.getConfig().addResourceBundle("PersonApp.properties");
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    new PersonApp().setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
} 
src/main/java/person/app/Person.java
package person.app;

public class Person {
    private String firstName;
    private String lastName;
    private String emailAddress;

    /**
     * @return the firstName
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * @param firstName the firstName to set
     */
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * @return the lastName
     */
    public String getLastName() {
        return lastName;
    }

    /**
     * @param lastName the lastName to set
     */
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    /**
     * @return the emailAddress
     */
    public String getEmailAddress() {
        return emailAddress;
    }

    /**
     * @param emailAddress the emailAddress to set
     */
    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }
} 
src/main/java/PersonApp.properties
button.save=Save
button.cancel=Cancel
label.firstName=First Name:
label.lastName=Last Name:
label.email=Email:


build.xml
<project name="GoogleSwingBuilder" default="run">
 
 <!-- properties -->
 <property name="src" location="src/main/java"/>
 <property name="build" location="build"/>
 <property name="dist"  location="dist"/>
 <property name="lib.dir" value="../../../lib/Googleswingbuilder"/>
 <path id="classpath">
   <fileset dir="${lib.dir}" includes="**/*.jar"/>
 </path>

   <target name="clean">
     <delete dir="${build}"/>
     <delete dir="${dist}"/>
   </target>

   <target name="compile" depends="clean">
     <mkdir dir="${build}"/>
     <javac srcdir="${src}"
            destdir="${build}" 
            classpathref="classpath"/>
     <copy todir="${build}">
       <fileset dir="${src}" excludes="**/*.java"/>
     </copy>
   </target>

   <target name="jar" depends="compile">
     <mkdir dir="${dist}"/>
     <jar destfile="${dist}/${ant.project.name}.jar"
          basedir="${build}"/>
   </target>

   <target name="run" depends="jar">
     <java fork="true" classname="person.app.PersonApp">
       <classpath>
           <path refid="classpath"/>
           <path location="${dist}/${ant.project.name}.jar"/>
       </classpath>
     </java>
   </target>

</project>