Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

In this tutorial you will learn how to:

  • use custom widgets with SwingBuilder,
  • implement observer pattern (also known as subject-observer pattern) in Swing & Groovy,
  • use action listners with SwingBuilder,
  • build simple currency converter (smile).

Swing introduced modified MVC pattern (for more details see http://java.sun.com/products/jfc/tsc/articles/architecture/). To update model I'll use observer pattern (if you're not familiar with it, see http://en.wikipedia.org/wiki/Observer_pattern) which is directly supported in Java by Observable class and Observer interface in java.util package.

For the example let's choose currency converter application fromJava Generics and Collections book by Maurice Naftalin and Philip Wadler (http://www.oreilly.com/catalog/javagenerics/) from chapter 9.5. It will show explicitly the benefits from using dynamic language like Groovy over static typed Java with generic observer pattern introduced in the book. If you would like to see implementation of generic observer pattern you can download examples from the book website and have a look.

OK. Let's start with the model:

Code Block
class Model extends Observable {
    static CURRENCY = [ "USD", "EURO", "YEN" ]

    private Map rates = new HashMap()
    private long value

    void initialize(initialRates) {
        (0..CURRENCY.size() - 1).each {
            setRate(CURRENCY[it], initialRates[it])
        }
    }

    // setting rate for currency
    void setRate(currency, f) {
        rates.put(currency, f);
        setChanged();
        notifyObservers(currency);
    }

    // setting new value for currency
    void setValue(currency, double newValue) {
        value = Math.round(newValue / rates[currency]);
        setChanged();
        notifyObservers(null);
    }

    // getter for value for particular currency
    def getValue(currency) {
        value * rates[currency]
    }
}

The converter model allows conversions over three different currencies. As you can see it extends Observable class to provide Model class observable behaviour (for more details see java.util.Observable).

Now let's create two custom widgets for displaying rate and value.

Code Block
class RateView extends JTextField implements Observer {
    private Model model;
    private currency;

    public void setModel(Model model) {
        this.model?.removeObserver(this)
        this.model = model
        model.addObserver(this)
    }

    public void update(Observable o, Object currency) {
        if (this.currency == currency)
            text = String.format("%15.2f", model.rates[currency])
    }
}

class ValueView extends JTextField implements Observer {
    private Model model
    private currency

    public void setModel(Model model) {
        this.model?.removeObserver(this)
        this.model = model
        model.addObserver(this)
    }

    public void update(Observable o, Object currency) {
        if (currency == null || this.currency == currency)
            text = String.format("%15.2f", model.getValue(this.currency));
    }
}

These classes extends JTextField to hold model and currency which is representing. They also implement Observer interface to be noticed when the model is changed. As you can see in update method there are not class casts required although it receives Object, because as dynamic nature of Groovy. Also in setModel method safe dereferencing is shown to protect from throwing NullPointerException when initially model is null.

Now let's put it all together.

Code Block
swing = new SwingBuilder()
model = new Model()

frame = swing.frame(title: "Groovy SwingBuilder MVC Demo", layout: new GridLayout(4, 3), size: [300, 150],
    defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE) {

        label("currency")
        label("rate")
        label("value")

        for (c in Model.CURRENCY) {
            label(c)
            widget(new RateView(), model: model, currency: c,
                     action: swing.action(closure: { event ->
                            event.source.model.setRate(event.source.currency, event.source.text.toDouble());
                      }))
            widget(new ValueView(), model: model, currency: c, action: swing.action(closure: {event ->
                            event.source.model.setValue(event.source.currency, event.source.text.toDouble());
                      }))
        }
    }

frame.show()
model.initialize([1.0, 0.83, 0.56]);

Frame is constructed by using swing.frame(). To frame there are provided title, layout, defaultCloseOperation, size properties. You can think of it like creating a new instance of JFrame and invoking methods setTitle(), setLayout(), setDefaultCloseOperation(), setSize(). Then 12 components are added to frame:

  • JLabel components using label("label's text"),
  • RateView components using widget() builder method and setting model, currency attributes,
  • ValueView components in the same way like RateView.

When new rate or value is entered all action listeners of that component are noticed with actionPerformed() method (java.awt.ActionListener). To construct classes which implements ActionListner interface SwingBuilder provides action() builder method. One of this method's attributes is closure when we are able to provide our closure with application logic. The closure argument has ActionEvent type.

Download the source code of the example: SwingBuilderObserver.groovy