Skip to main content
Jump to: navigation, search

Data Binding HOWTO

Revision as of 12:40, 22 March 2006 by Bbokowski (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Getting started

By far, the largest body of publicly-available examples for using JFace Data Binding is in the test suite. This test suite is based off of the scenarios document, which is a list of use-cases that we expect data binding to cover, so it turns out to be a very good starting place for people wanting to learn the library quickly. The Scenarios document is linked from the bottom of the (currently spare and frugal) main documentation page on the Eclipse Wiki:

http://wiki.eclipse.org/index.php/JFace_Data_Binding

The direct link to the Scenarios document is:

http://wiki.eclipse.org/index.php/JFace_Data_Binding_Scenarios

Since the test suite isn’t included in the integration or nightly builds, but rather is in CVS, I suggest that you download the code (including the tests) from CVS. Fortunately, Eclipse makes that easy.

Downloading the code

If you haven’t already done so, add a new CVS repository, pointing to the Eclipse Platform project. From the CVS Repository Exploring perspective, right-click in the “CVS Repositories” view and choose “New | Repository location…”. In the resulting dialog, enter:

Host: dev.eclipse.org
Repository path: /cvsroot/eclipse
User: anonymous
Password: (leave blank)
Connection type: pserver

Expand the project, then expand HEAD. Then control-click the “org.eclipse.jface.databinding”, “org.eclipse.jface.examples.databinding”, and “org.eclipse.jface.tests.databinding” projects, right-click, and select “Check out” from the context menu to download the code.

Running the tests

Now that we have the data binding code and the tests, let’s run them to make sure we have everything configured correctly.

  1. Expand org.eclipse.jface.tests.databinding
  2. Expand the org.eclipse.jface.tests.databinding package
  3. Right-click “BindingTestSuite.java” and choose “Run as | SWT Application”

If everything went well, the console view should open, you should see a dot printed out for each test case executed, then a note about how long the tests took and how many tests were run.

So far we’ve gotten the code and verified that we have installed it correctly on our machine. Now we’d like to know what to do with it.

Examining the scenarios

The test cases are organized along the same functional lines as the different scenarios in the scenarios document. Again, the direct URL for that document (as of this writing is):

http://wiki.eclipse.org/index.php/JFace_Data_Binding_Scenarios

Let’s open that document and see how to learn about data binding.

First, notice that the scenario document has the following main headers:

  • Property binding
  • Custom binding
  • Binding to a read-only Combo or List
  • Master-detail
  • … and so on

Each of these main headers roughly corresponds with a source file in the tests. So if you want to get started with data binding, you might first want to open the PropertyScenarios.java file and read how to bind controls to simple properties. If you’re interested in more complex master-detail binding, MasterDetailScenarios.java might be a promising place to look. <img src="icon_smile.gif" alt=":-)" class="wp-smiley">

Let’s look at a simple example of data binding that will provide all of the background you will need in order to understand the test scenarios.

Brief Introduction to JFace Data Binding

This section is a brief conceptual description of data binding. If you just want the code, you can skip to the next section.

JFace Data Binding is really a generic data synchronization service.

It doesn’t even assume a user interface. It can be used in simple SWT applications, in full-blown RCP applications, and certainly those are the main places we expect it to be applied. But it could also be used inside the model tier (without any user interface at all) to keep sections of the data model in sync.

For our purposes, let’s write a simple RCP application to demonstrate the most common use-case: binding properties of a POJO to some SWT controls that will be used to edit them.

Property binding example

We will create our RCP application using one of the RCP application templates. Please do each of the following steps completely, and in this order, in order to get started.

  1. File | New | Project…
  2. Choose “Plug-in Project”, then click “Next”.
  3. Type a valid plug-in project name. I typed “com.coconut-palm-software.databinding”. Click “Next”.
  4. Next to “Would you like to create a rich client application?”, click “Yes”, then click “Next”.
  5. Choose “RCP application with a view”, and click “Finish”.
  6. If Eclipse asks you if you want to switch to the Plug-in development perspective, say “No”, and make sure you’re in the “Java” perspective.
  7. In the plug-in manifest editor, switch to the “Dependencies” page.
  8. Under “Required plug-ins”, choose “Add”, and type “org.eclipse.jface.databinding” and click “OK”.
  9. Switch back to the “Overview” tab and click “Launch an Eclipse Application”.

You should now see the default RCP application with a view, which you can safely close.

We have now created a new Rich Client Application, configured it to use data binding, created a launch configuration for it, and run it. Now we need to add some data binding to it.

Creating a model to bind

Of course, before we can bind our user interface, we need a model to edit using our user interface. Let’s create a really simple model. Create the following class in the package with your plug-in’s Application class:

package com.coconut_palm_software.databinding;

public class Person {
    private String firstName = "John";
    private String lastName = "Doe";
    private int timeWithCompany = 42;

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public int getTimeWithCompany() {
        return timeWithCompany;
    }
    public void setTimeWithCompany(int timeWithCompany) {
        this.timeWithCompany = timeWithCompany;
    }
}

You will recognize this as just a POJO with two String properties and one int property.

Next, we will need to create a user interface that can edit this model.

Building a user interface

Open up the View class, and change it to read:

package com.coconut_palm_software.databinding;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

public class View extends ViewPart {
    public static final String ID = "com.coconut_palm_software.databinding.view";
    private Composite top = null;
    private Text firstName = null;
    private Text lastName = null;
    private Text timeWithCompany = null;
    private Label label = null;

    /**
     * This is a callback that will allow us to create the viewer and initialize
     * it.
     */
    public void createPartControl(Composite parent) {
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 2;
        top = new Composite(parent, SWT.NONE);
        top.setLayout(gridLayout);
        label = new Label(top, SWT.NONE);
        label.setText("First name:");
        firstName = new Text(top, SWT.BORDER);
        firstName.setLayoutData(getGridData());
        label = new Label(top, SWT.NONE);
        label.setText("Last name:");
        lastName = new Text(top, SWT.BORDER);
        lastName.setLayoutData(getGridData());
        label = new Label(top, SWT.NONE);
        label.setText("Time with company:");
        timeWithCompany = new Text(top, SWT.BORDER);
        timeWithCompany.setLayoutData(getGridData());
    }

    private GridData getGridData() {
        GridData gd = new org.eclipse.swt.layout.GridData();
        gd.horizontalAlignment = org.eclipse.swt.layout.GridData.FILL;
        gd.grabExcessHorizontalSpace = true;
        gd.verticalAlignment = org.eclipse.swt.layout.GridData.CENTER;
        return gd;
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    public void setFocus() {
    }
}

If you re-run the RCP application, you now should see:

<img src="rcpapplicationunbound.png" alt="Unbound RCP application">

Binding the user interface

Now we need to create an instance of our model object and bind it to the user interface. For our purposes, we will create our model object right inside the View. Add the following line to the View class:

Person person = new Person();

Now we need to bind our user interface to this new Person object so that we can edit it. We will use a separate method called “bind” to do this. Add the following line to the end of the createPartControl() method:

bind();

Now we will actually create the bind() method. Add the following code to the View class:

private void bind() {
    DataBindingContext dbc = createContext(top);
    dbc.bind(firstName, new Property(person, "firstName"), null);
    dbc.bind(lastName, new Property(person, "lastName"), null);
    dbc.bind(timeWithCompany, new Property(person, "timeWithCompany"), null);
}

Let’s look at what’s going on here:

  • The first line calls a method that we have not written yet for creating a data binding context. We will do that in a moment. Just remember that we are passing “top”, the top-level Composite that holds everything.
  • The next three lines bind the appropriate Text control’s default property (the “text” property) to the firstName, lastName, and timeWithCompany properties of the Person object respectively.

The method createContext() is implemented as follows. Basically, we are configuring the data binding context with a number of factories, and we are attaching a dispose listener to the top-level composite so that the data binding context can be disposed of when the composite is disposed. Disposing of the data binding context will automatically remove event listeners registered with the SWT controls and JavaBeans objects.

private void createContext(Composite parent) {
    DataBindingContext dbc = new DataBindingContext();
    dbc.addObservableFactory(new BeanObservableFactory(dbc, null, null)); // binding to beans
    dbc.addObservableFactory(new SWTObservableFactory()); // binding to SWT controls
    dbc.addBindSupportFactory(new DefaultBindSupportFactory()); // default converters
    dbc.addBindingFactory(new DefaultBindingFactory()); // default binding behaviour
    parent.addDisposeListener(new DisposeListener() {
        public void widgetDisposed(DisposeEvent e) {
            dbc.dispose();
        }
    });
    return dbc;
}

Let’s run the application and play with it a bit. At first, it should look like the following:

<img src="rcpapplicationbound1.png" alt="Bound RCP application">

So far, everything looks perfect. The data binding framework even noticed that our timeWithCompany property is an integer and automatically supplied an int to String conversion for us.

But look at what happens if we try to type some data into this field:

<img src="rcpapplicationbound2.png" alt="Bound RCP application with incorrect characters in int field">

Oooops!

Although this will be addressed in future builds, Data Binding currently doesn’t automatically validate keystrokes based on the current data type conversion that is being used.

However, we can supply this feature manually. This also illustrates how to manually override the default validation in cases when the default validation (once we have finished implementing default validation) cannot possibly be corrected by itself, such as when an integer field needs to be limited to a specific range.

Change the call that binds timeWithCompany (the last one) to read:

dbc.bind(timeWithCompany, new Property(person, "timeWithCompany"), 
        new BindSpec().setValidator(new RegexStringValidator(
                "^[0-9]*$", "^[0-9]+$", "Please enter a number")));

The third parameter to dbc.bind() is an optional BindSpec object. The BindSpec object can optionally override the default data type conversion behaviour or it can optionally override the default validation behaviour.

Here we call setValidator() and pass a regular expression string validator instance. The setter returns the bind spec object, which allows us to call it inline, and to chain several setters if we wanted to specify more than the validator. The regular expression string validator is one of several predefined IValidator implementations. Its constructor parameters are the following:

  • Regular expression that must match in order to accept a keystroke. In this case, we accept 0 or more digits.
  • Regular expression that must match in order to accept the entire field. In this case, we require 1 or more digits.
  • A help string that will be passed to an event on the data binding context whenever validation fails.

Now re-run the application. It should now behave as you would expect.

Revisiting the test scenarios

Now we know enough to be able to understand the test scenario source code. Let’s quickly look at one of the test scenarios and you’ll see what I mean.

Open PropertyScenarios.java. The third method in the file (as of this writing) is testScenario01(), which reads:

public void testScenario01() {
	Text text = new Text(getComposite(), SWT.BORDER);
	getDbc().bind(text, new Property(adventure, "name"), null);
	// uncomment the following line to see what's happening
	// spinEventLoop(1);
	assertEquals(adventure.getName(), text.getText());
	enterText(text, "foobar");
	// uncomment the following line to see what's happening
	// spinEventLoop(1);
	assertEquals("foobar", adventure.getName());
	adventure.setName("barfoo");
	// uncomment the following line to see what's happening
	// spinEventLoop(1);
	assertEquals("barfoo", text.getText());
}

The only thing that is different in here from the example we just built is that the test uses the helper method getDbc() to get the data binding context and it has “scripted” SWT using the enterText() method.

From here you should be able to play with the various test scenarios and quickly understand how to use JFace Data Binding.

Conclusion

We have seen:

  1. How to get the JFace Data Binding source code and tests.
  2. How to create and run an RCP application using JFace Data Binding.
  3. A simple example illustrating all of the basics of using JFace Data Binding.
  4. How to understand the JFace Data Binding unit tests’ organization.

Back to the top