Java Gnome Widget Models

From thecentric

This is a really a continuation of the Java-Gnome HMVC Tutorial. Here again I will talk about an effective way to go about creating a Java Gnome application. In particular - how to bring the Swing widget model based architecture to Java Gnome (with a twist!). My examples will show the ability to separate presentation (e.g. Buttons!) code from domain code. If this is can be achieved it also lets you as developers modify the presentation on screen of the same data - without the overkill of lots of presentation updating code for each model change.

I’m going go take a simple Gtk Java Gnome widget (button!) and show how to implement a model architechture on it.

Firstly lets refresh your memory and define some of the things that can be done with a gtk button.

    Button button = new Button();    // create a new button
    button.setLabel("click me!");    // set the label text on the button
    button.setSensitive(true);       // disable or enable the button
    button.setMinimumSize(-1, -1);   // set the minimum width and height of the button

    // below we add a listener for the click of the button
    button.addListener(new ButtonListener() {
        public void buttonEvent(ButtonEvent event) {
            if (event.getType().equals(ButtonEvent.Type.CLICK))
                System.out.println("Hello World - Button Clicked!n");
        }
    });

That should be enough things to demonstrate my point effectively! Now - if we did have a model for GtkButton - what would it look like if the features we wanted in the model were text, sensitive, height, width, and clicked? How about something below?

    public class GtkButtonModel {
        public String getText() {
            return "click me!";
        }

        public boolean isEnabled() {
            return true;
        }

        public void clicked() {
                System.out.println("Hello World - Button Clicked!n");
        }

        public int getWidth() {
            return -1;        // default is -1
        }

        public int getHeight() {
            return -1;        // default is -1
        }
    }

So what use is this you ask yourself? Well the idea is that you will now never go messing around with adding click listeners to Gtk Buttons ever again, or manually setting their text, width, height or sensitivity. Instead you will create a GtkButtonModel per button on screen and fill out the values. For example, if you were to create a button to delete a selected customer - you would override isEnabled and return true if there is a customer selected and return false if there is no customer selected. Also you would override clicked and put the delete customer code in there.

Not lets write our custom GtkButton, that takes a model in the constructor!

    public class GtkButton extends Button {
        private GtkButtonModel _model;
        private Button _implementation;

        public GtkButton(GtkButtonModel model) {
            this(new Button(), model);
        }

        public GtkButton(Button button, GtkButtonModel model) {
            super(button.getHandle());
            _model = model;
            _implementation = button;

            buildAll();
            addClickListener();
        }

        private void buildAll() {
            _implementation.setLabel(_model.getText());
            _implementation.setSensitive(_model.isEnabled());
            _implementation.setMinimumSize(_model.getWidth(), _model.getHeight());
        }

        private void addClickListener() {
            _implementation.addListener(new ButtonListener() {
                public void buttonEvent(ButtonEvent event) {
                    if (event.getType().equals(ButtonEvent.Type.CLICK))
                        _model.clicked();
                }
            });
        }

        public void refresh() {
            buildAll();
        }
    }

Notice two interesting things about the code above, the first is that there is a refresh() method which ‘rebuilds’ the buttons properties from the model. So when the data that the model is reflecting changes - calling refresh on the button will cause the button to be updated with the new properties. The second thing to notice is the two constructors, the one we will usually call is the one just passing in the GtkButtonModel, but the second constructor allows us to use this pattern with Glade/Java-Gnome. Use glade to get a handle on the button widget you are interested in and pass in the the button along with your model.

This pattern works really well with my HMVC framework discussed here. For two main reasons. The first being that there is no need now to directly manipulate the domain even from the button’s model for example, you would instead fire a HMVC event and catch it at the MVC model level to operate on the domain. Secondly it allows the easy refreshing of widgets - if something in the domain changes a refesh event can be fired that refreshs all the appropriate widgets.

Further this pattern works really well when dealing with Java Gnomes infamously difficult to use TreeView. I have create a simple GtkTableModel that looks something like the following:

    public abstract class GtkTableModel {
        public abstract String[] getColumnText();

        public abstract TableRowItem[] getTableRowItems();

        public String[] getRendererType() {
            String[] rendererTypes = new String[getColumnText().length];
            for (int i = 0; i < rendererTypes.length; i++)
                rendererTypes[i] = CellRendererMap.TEXT;
            return rendererTypes;
        }

        public final int getColumnCount() {
            return getColumnText().length;
        }

        public boolean isAlternateRowColor() {
            return false;
        }

        public boolean isHeadersVisible() {
            return true;
        }

        public void rowsSelected(Object[] keys) {
        }

        public final boolean isMultipleSelectionMode() {
            return false;
        }

        public void rowDoubleClicked() {
        }
    }

A table row item looks something like the following, where CustomImage is just a wrapper for PixBuf.

    public interface TableRowItem
    {
        public Object getKey();

        public String[] getText();

        public CustomImage[] getImages();
    }

It makes it really easy to get a table up and running; while not having to worry about treestores or treeviewcolumns - or other nastyness like that! If you’re interested in how this approach works when writing a full application or want to delve a little deeper than my quick run over - I recommend taking a look at the source to JBalorsEye - which is written with the HMVC patter and also the widget model pattern where every widget has it’s own model.

Navigation