Thursday, November 25, 2010

Ease Swing GUI Creation with HTML Templating

Creating GUI in Java is rather difficult. Ordinary code looks like:

JPanel south = new JPanel(new BorderLayout());  
south.add(new JLabel(«login»), BorderLayout.WEST); 
south.add(new JTextField(),BorderLayout.CENTER);
rootPanel.add(south, BorderLayout.SOUTH);


Imagine that you have 15 fields... TableLayout and MigLayout ease layout for many fields, but anyway you have to write Java code.



Background


A lot of programmers (especially in Russia) started with Delphi. The cool thing in Delphi is that you don't have to write GUI code at all. GUI markup is located in a separate file and is linked to the app by IDE.

Almost the same approach is used in Jetbrains IntelliJ Idea. The markup is located in .form file and is converted to the Java code by IDE. Also form's (controller's) fields are injected with appropriate components.

Using the Code


My approach is to put all markup into external file and to construct form in runtime with the help of framework. This approach is like IOC pattern — you do not think about managing of form, you just describe it in a declarative manner and use the results.

So here is an example:

/ru/swing/html/example/LoginForm.html

<head>
    <style type="text/css">
        .red {
            color: red;
        }
        .top {
            vertical-align: top;
        }
        #loginForm {
            x-miglayout-column-constraints: [right]related[grow,fill];
        }
        body {
            border: compound (empty 12 12 12 12) (compound (etched) (empty 12 12 12 12));
        }
    </style>
</head>
<body id="rootPanel" style="display: border; width: 400; height:300; 
 border: empty 12 12 12 12">
<table>
    <tr>
        <td rowspan="3" class="top">
            <img src="http://www.codeproject.com/img/login_icon.gif" alt=""/>
        </td>
        <td width="fill">
            <form id="loginForm">
                <p>Login:</p>
                <input id="login" type="text" align="wrap"/>
                <p>Password:</p>
                <input id="password" type="password" align="wrap" />
            </form>
        </td>
    </tr>
    <tr>
        <td>
            <p id="result" class="red"></p>
        </td>
    </tr>
    <tr>
        <td>
            <div style="display: box; x-boxlayout-direction: 
  horizontal; border: empty 0 0 0 6">
                <glue type="horizontal"/>
                <input type="button" text="OK" id="ok" icon="/img/accept.png" />
                <strut type="horizontal" style="width: 6"/>
                <input type="button" text="Cancel" id="cancel" icon="/img/cancel.png"/>
            </div>
        </td>
    </tr>
</table>
</body>
</html>


This is the HTML markup of login form. Every HTML tag will produce some swing component. For example, <body> will produce javax.swing.JPanel.

'display' attribute tells which layout manager will be used in tag's component. 'border' tells that it will be java.awt.BorderLayout.

Every direct child tag is placed in parent's component. Most layout's (BorderLayout, for example) place component using 'align' attribute as constraint. Default constraint for BorderLayout is center, so table will be placed in the center part of JPanel.

Also notice that tag <table> is used. It will produce JPanel, as <body>, but will set TableLayout as layout manager. After that, it will specially handle its children, placing all <td> contents in appropriate cells.

In a similar manner, <form> produces JPanel with MigLayout layout manager.

Now let's bind this form to the controller:

package ru.swing.html.example;
import org.jdom.JDOMException;
import ru.swing.html.Bind;
import ru.swing.html.Binder;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
public class LoginForm {

    @Bind("login")
    private JTextField login;

    @Bind("password")
    private JPasswordField password;

    @Bind("ok")
    private JButton okBtn;

    @Bind("cancel")
    private JButton cancelBtn;

    @Bind("rootPanel")
    private JPanel rootPanel;

    @Bind("result")
    private JLabel result;

    public LoginForm() {

        try {
            Binder.bind(this);
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        init();
    }

    public void init() {

        okBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                result.setText("Logging in user "+login.getText());
            }
        });

        cancelBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                result.setText("Cancel clicked");
            }
        });
    }

    public JPanel getRootPanel() {
        return rootPanel;
    }

    public static void main(String[] args) throws JDOMException, IOException {
        LoginForm loginForm = new LoginForm();
        JFrame f = new JFrame("Test");
        f.setSize(400, 200);
        f.getContentPane().add(loginForm.getRootPanel());
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}

Form's markup is loaded and binded to controller using:

try {
   Binder.bind(this);
} catch (JDOMException e) {
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
} 

This call searches for file /ru/swing/html/example/LoginForm.html (as class is named ru.swing.html.example.LoginForm), loads it, builds swing components tree and than binds it to controller, so components whose tag's id is equal to the value of @Bind annotation is injected into the field.

The final result looks like:



No comments :

Post a Comment