Tuesday, April 23, 2013

Databinding with radio buttons and contextual binding contexts

The Eclipse databinding framework is a powerful framework that help developers link (bind) their data model to graphical components. It basically provides a way to reflect a user interaction with some internal data (and vice versa) without the use of painful listeners.
It also provide an easy way to validate and convert the data between the graphical interface and the internal java model.

The eclipse databinding framework provides a rich set of classes to bind SWT and JFace graphical components on one side and Java Beans and Pojos on the model side. EMF is also a very good candidate  for data binding as it provides the modification notifications required for the data binding process.

In this post I will not explain the basics of data binding (that has been done perfectly by others here and here for example), but I will talk about a particular use case that some of you may encounter.

The idea is to make wizard with multiple choices to select a way to retrieve data.
Here is the look of the wizard page.


First you need a model to represent those data, there are many ways but here is one.

public class FeatureRepositories {

    public static enum POLICY {
        DEFAULT_REPO,
        REMOTE_REPO,
        LOCAL_FOLDER
    }

    private POLICY policy;

    private String remoteRepoUriStr;

    private String localRepoPathStr;
...

This is a Plain Old Java Object (pojo) with 3 fields and their associated setters and getters (not shown here). This is not a Java Beans meaning I did not implement any property change notifications as I do not require the model to change from outside a user interaction.

Radio button binding

So the first problem is how to bind the policy enum to the radio button selection ? I had trouble finding that information over the net so here to me the most elegant solution using : org.eclipse.core.databinding.observable.value.SelectObservableValue.


//Create the Select Observable for our enum type
SelectObservableValue repoPolicyObservable = new SelectObservableValue(FeatureRepositories.POLICY.class);
//bind the default radion button selection to the right enum value
IObservableValue btnDefaultRemoteSitesObserveSelection = SWTObservables
            .observeSelection(btnDefaultRemoteSites);
repoPolicyObservable.addOption(POLICY.DEFAULT_REPO, btnDefaultRemoteSitesObserveSelection);

// bind remote custom Repo button selection to the right enum value
IObservableValue btnCustomUpdateSiteObserveSelection = SWTObservables
        .observeSelection(btnCustomUpdateSite);
repoPolicyObservable.addOption(POLICY.REMOTE_REPO, btnCustomUpdateSiteObserveSelection);

// bind local folder button selection to the right enum value
IObservableValue btnLocalFolderObserveSelection = SWTObservables.observeSelection(btnLocalFolder);
repoPolicyObservable.addOption(POLICY.LOCAL_FOLDER, btnLocalFolderObserveSelection);

//finally bind the selectable to the model attribute
bc.bindValue(repoPolicyObservable,
            PojoObservables.observeValue(updateWizardModel, "featureRepositories.policy")); //$NON-NLS-1$



The updateWizardModel is an instance of the wizard model that contains a field of the type FeatureRepositories that defines our page model.
public class UpdateWizardModel {

    private FeatureRepositories featureRepositories;
    public FeatureRepositories getFeatureRepositories() {
        return this.featureRepositories;
    }

    public void setFeatureRepositories(FeatureRepositories featureRepositories) {
        this.featureRepositories = featureRepositories;
    }

}
This way you can see how the binding is done using a PojoObservable (only one way binding from GUI to model) and using a nested property "featureRepositories.policy"
Please also note that the code above does not show the binding done to enable text fields and buttons according to the selected radio button, I leave it to the readers hands. 


Error notifications

As mentioned in the introduction, databinding also provides means to validate the data before or after it gets propagated to the model.
So in our example we would like to check that the path is a valid existing folder or the url is a valid one, if not we would like the wizard to say so, like this.


The validation is a basic databinding feature, here is how to do it :

UpdateValueStrategy localFolderValidator = new UpdateValueStrategy().setAfterConvertValidator(new UpdateWizardModel.LocalRepoFolderValidator());
IObservableValue localFolderTextObserveTextObserveWidget = SWTObservables.observeText(localFolderText, SWT.Modify);
bc.bindValue(localFolderTextObserveTextObserveWidget, PojoObservables.observeValue(updateWizardModel, "featureRepositories.localRepoPathStr"), localFolderValidator, null);

where the validator implementation looks like this
static class LocalRepoFolderValidator implements IValidator {
    @Override
    public IStatus validate(Object value) {
        String uriStr = (String) value;
        if (uriStr == null || "".equals(uriStr)) { //$NON-NLS-1$
            return ValidationStatus.info(Messages.getString("UpdateWizardModel.local.folder.required.error")); //$NON-NLS-1$
        }
        File folder = new File(uriStr);
        if (!folder.exists()) {
            return ValidationStatus.error(Messages.getString("UpdateWizardModel.local.folder.must.exist.error")); //$NON-NLS-1$
        }
        if (!folder.isDirectory()) {
            return ValidationStatus.error(Messages.getString("UpdateWizardModel.local.folder.must.be.folder")); //$NON-NLS-1$
        }
        return ValidationStatus.ok();
    }
}


One of many great thing with data binding is support for JFaces components such as Dialogs, Trees, Lists and Wizards. And it provides a way to bind the validation status messages to the Wizard message field with one single line of code
// bind the validation messages to the wizard page
WizardPageSupport.create(this, bc);

Where bc is the data binding context used from the beginning of this post and this is the wizard page.
You'll also notice that the WizardPageSupport handle the page completion that will be enable or disable the next or finish button according to the current binding status.


Issues with a single DataBindindContext 

Now if you implement the 3 radio buttons with all the previous bindings, and validation and error support using a single DataBindingContext  you'll notice that the error message is not reflecting the current radio selection but rather always reflects the first error message in the page.

To get around this expected problem you need to create a new DataBindingContext for each radio button and their associated components. You have to consider each radio button group as a new context for messages, and only this context will be able to represent the error state of the page.

As for the SelectObservableValue that observes the radion button selection to update the model according to the selection, it has to be bound to yet another DataBindingContext.
So here is the final code :

SelectObservableValue featureRepoPolicyObservable = new SelectObservableValue(FeatureRepositories.POLICY.class);
// define default Repo bindings
{
    DataBindingContext defaultRemoteBC = new DataBindingContext();
    IObservableValue btnDefaultRemoteSitesObserveSelection = SWTObservables.observeSelection(btnDefaultRemoteSites);
    featureRepoPolicyObservable.addOption(POLICY.DEFAULT_REPO, btnDefaultRemoteSitesObserveSelection);

    // fake binding to trigger the validation of the defaultRemoteBC to reset the validation message
    defaultRemoteBC.bindValue(btnDefaultRemoteSitesObserveSelection, new WritableValue());
    // bind the validation messages to the wizard page
    WizardPageSupport.create(this, defaultRemoteBC);
}
// define remote custom Repo url bindings
{
    DataBindingContext remoteRepoBC = new DataBindingContext();
    // bind selection to model value
    IObservableValue btnCustomUpdateSiteObserveSelection = SWTObservables.observeSelection(btnCustomUpdateSite);
    featureRepoPolicyObservable.addOption(POLICY.REMOTE_REPO, btnCustomUpdateSiteObserveSelection);

    // bind selection to enable text field
    IObservableValue textObserveEnabled = SWTObservables.observeEnabled(CustomSiteText);
    remoteRepoBC.bindValue(textObserveEnabled, btnCustomUpdateSiteObserveSelection, null, null);
    // bind text modification to model with validation
    UpdateValueStrategy afterConvertRemoteRepoValidator = new UpdateValueStrategy()
            .setAfterConvertValidator(new UpdateWizardModel.RemoteRepoURIValidator());
    IObservableValue customSiteTextObserveText = SWTObservables.observeText(CustomSiteText, SWT.Modify);
    remoteRepoBC.bindValue(customSiteTextObserveText,
            PojoObservables.observeValue(updateWizardModel, "featureRepositories.remoteRepoUriStr"), //$NON-NLS-1$
            afterConvertRemoteRepoValidator, null);
    // bind the validation messages to the wizard page
    WizardPageSupport.create(this, remoteRepoBC);
}
// define local folder Repo bindings
{
    DataBindingContext localRepoBC = new DataBindingContext();
    // bind selection to model
    IObservableValue btnLocalFolderObserveSelection = SWTObservables.observeSelection(btnLocalFolder);
    featureRepoPolicyObservable.addOption(POLICY.LOCAL_FOLDER, btnLocalFolderObserveSelection);

    // bind selection to text fiedl enabled
    IObservableValue localFolderTextObserveEnabled = SWTObservables.observeEnabled(localFolderText);
    localRepoBC.bindValue(localFolderTextObserveEnabled, btnLocalFolderObserveSelection, null, null);
    // bind selection to browse button enabled
    IObservableValue localFolderBrowseButtonObserveEnabled = SWTObservables.observeEnabled(localFolderBrowseButton);
    localRepoBC.bindValue(localFolderBrowseButtonObserveEnabled, btnLocalFolderObserveSelection, null, null);
    // bind text field to model with validation
    UpdateValueStrategy afterConvertLocalFolderValidator = new UpdateValueStrategy()
            .setAfterConvertValidator(new UpdateWizardModel.LocalRepoFolderValidator());
    IObservableValue localFolderTextObserveText = SWTObservables.observeText(localFolderText, SWT.Modify);
    localRepoBC.bindValue(localFolderTextObserveText,
            PojoObservables.observeValue(updateWizardModel, "featureRepositories.localRepoPathStr"), //$NON-NLS-1$
            afterConvertLocalFolderValidator, null);
    //
    // bind the validation messages to the wizard page
    WizardPageSupport.create(this, localRepoBC);
}
DataBindingContext radioBC = new DataBindingContext();
radioBC.bindValue(featureRepoPolicyObservable,
        PojoObservables.observeValue(updateWizardModel, "featureRepositories.policy")); //$NON-NLS-1$

Also notice the fake databinding used to reset the error message on the first button selection because this button does not trigger any validation.

So that is about it for Databinding.
To me this is a great framework that really reduce the use of listeners that are to error prone hence increase the code quality and robustness.

Friday, October 7, 2011

use eclipse EEF as a property sheet for the Graphiti editor


I just dived into the Graphiti project recently because we needed to design a quick and simple graphical editor.
I do not want to get into a war between GMF and Graphiti but that was an opportunity to get to know this framework and GMF has proven having a steep learning curve and I did not have too much time to consume teaching GMF to our Talend colleges in China.
So I decided to make a POC for our own use case with the following requirements :

  • is must be a visual form editor.
  • it has to be very simple
  • allow infinite containment depth
  • you can specify layout to automatically place the components in the containers.
  • be able to edit the selected element properties.
So I decided to create a simple component metamodel using ecore (from the Eclipse Modeling Framework) looking like this.



I then started to follow the Graphiti tutorial available in the Eclipse help. I really invite anyone who wants to start with Graphiti to follow the tutorial, it covers a lot of features provided by Graphiti.

This blog title is about using EEF (Extended Editing Framework) so let's dive into the subject.
Graphiti default editor is able to link with an Eclipse tabbed property sheet that you may declare in your plugin.xml with the org.eclipse.ui.views.properties.tabbed.propertyContributor extension point. 
But you have to it all by yourself. This is where EEF comes into play, why not using the Obeo's contributed set of tools and framework to generate the tabbed property sheets automatically according to our ecore metamodel ? And indeed that is what I did.
here is a screen-cast explaining how I did it.


(here is a youtube link for those who have trouble with screecast-o-matic : http://youtu.be/zbfisZt-FOw)

At 2mn25 there are a couple of files added that make the bridge between EEF and Graphiti.

1) The first file GraphitiEObjectFilter is a section filter to accep displaying the property sheet only when the edited graphiti business object is a EObject :

import org.eclipse.emf.ecore.EObject;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.ui.platform.AbstractPropertySectionFilter;

public class GraphitiEObjectFilter extends AbstractPropertySectionFilter {
 @Override
 protected boolean accept(PictogramElement pe) {
  Object object = Graphiti.getLinkService().getBusinessObjectForLinkedPictogramElement(pe);
  if (object instanceof EObject) {
   return true;
  }
  return false;
 }
}

2) The second file extends an EEF class property sheet section implementation to return the proper EObject from the selection, because Graphiti provides a pictogram element as the selection.

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.eef.runtime.ui.properties.sections.PropertiesEditionSection;
import org.eclipse.gef.EditPart;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.services.Graphiti;

public class GraphitiPropertyEditionSection extends PropertiesEditionSection {
 @Override
 protected EObject resolveSemanticObject(Object object) {
  if (object instanceof PictogramElement) {
   return Graphiti.getLinkService().getBusinessObjectForLinkedPictogramElement((PictogramElement) object);
  }

  EditPart editPart = null;
  if (object instanceof EditPart) {
   editPart = (EditPart) object;
  } else if (object instanceof IAdaptable) {
   editPart = (EditPart) ((IAdaptable) object).getAdapter(EditPart.class);
  }
  if (editPart != null && editPart.getModel() instanceof PictogramElement) {
   return Graphiti.getLinkService().getBusinessObjectForLinkedPictogramElement((PictogramElement) editPart.getModel());
  }
  return super.resolveSemanticObject(object);
 }
}

This last file would not be necessary if Graphiti would somehow provide a way to register some adapters to the pictogram element because EEF if a good citizen and the original resolveSemanticObject looks for an EObject adapter (see this issue).

Cheers.

PS : I'd like to thank Volker Wegert that did a GMF to Graphiti convertion of a complex form editor and gave me good advices to get me started.


Monday, July 27, 2009

Automating Unit tests for RCP applications with the Eclipse Test Framework

The initial article that help me in getting my JUnit test to be run automatically with the Eclipse Test Framework can be found on the RCP quickstart site.

The article, the example as well as the comments where very helpfull but not sufficient enought to answer my needs that is why I wanted to publish some of my findings on this blog.
All the following discussion will be base around this piece of ant script:

<ant target="ui-test" antfile="${library-file}" dir="${eclipse-home}">
  <property name="os" value="${baseos}"/>
  <property name="ws" value="${basews}"/>
  <property name="arch" value="${basearch}"/>
  <property name="data-dir" value="${mhdk.root.for.test} -clean -testApplication com.nds.france.mhdk.mhdkmanager.rcp.application" />
  <property name="plugin-name" value="test.all.test.suite" />
  <property name="classname" value="com.nds.test.AllTests" />
</ant>

This is a cut and paste from Patrick Paulin's example but I removed the emma reference as I am only interested in the unit tests.
What I did, as many of you might have done, was to test the example and I could get it running quickly then I adapted it for my own RCP application.

1) -cleanYou may have noticed that the data-dir property is set to a path and ends with -clean.
   <property name="data-dir" value="${mhdk.root.for.test} -clean" />

Actually this is the only way to pass extra parameter(s) to the eclipse application and I incidently removed it as I did not see the reason for it being here.
Do not do that !!!, I ended up with a java.lang.NoClassDefFoundError: junit/framework/TestListener and spent some time figuring this out.
I have not yet understoud why this is compulsory but this does not work otherwise.

2)RCP application test
The way the script is written does not actually execute your RCP application, but rather launch the org.eclipse.test.coretestapplication application that will load your plugin and run the test class specifyed as plugin-name.You may change the target attribute if you want to perform graphical tests that require a workbench to be created using the value : ui-test, this will launch the org.eclipse.test.uitestapplication.
But what if your application does some specific initialisation when it is started, like mine that uses the workspace (-data) as a specialzed area for accessing data?
If you need your application plugin Activator or your application class to be called before the unit test starts you may then extend the data-dir value with -testApplication such as:

<property name="data-dir" value="${my.workspace} -clean -testApplication com.my.application.id">

3) debug all this
If you want to debug your test while launching them using the ant script or even want to debug the test framework just to see what happens you can always add the following property to the task that launches the unit test.
<property name="extraVMargs" value="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1716">

This will start the eclipse wirtual machine and stop right before the first byte code is executed waiting for a debugger connection to attach to the process.
Go to the Debug dialog box in Eclipse and create a new Remove Java Application configuration with the following parameters.
Connection type : Standard(Socket Attach)
Host : localhost
Port : 1716 (identical to the one in the extraVMargs property)



You may have to configure the source tab if you want the debugger to display the source code of your application or of the Eclipse Test Framework.

4) No space in folders name
Last but not least...
Do NOT use spaces in folder's name, the Eclipse Test Framework and scripts do not like them at all and it took me some time to figure this out.

That is for now, I hope that sharing of my experience will help other people automating their unit test with RCP applications.

Tuesday, April 28, 2009

SWT Visual Editor

This project in the Eclipse foundation has not followed the Eclipse yearly release since Eclipse 3.2 (2006) see here.

This really is a shame as for designers visual editing is a real time saver for those who do not want to fiddle around with layering elements with code, I found that beginner developpers face problems writing hardcoded GUIs.

But it seems that a french compagny called Soyatech did the porting on Eclipse 3.4 and if you carrefully look at their web site there is also a port for Eclipse 3.3 that seems to work quiet well.