Galileo: EMF-Databinding – Part 5


Though the main focus of this article is going to be the usage of the properties API to setup a TableViewer there are some importanted I didn’t explain in Part 4 of this series.

Using the properties API in master-detail (continued)

Showing validation states

The first interesting thing is how to present validation errors to user. I’m personally not a friend of ControlDecorations but prefer to display the validation error in the form-header.
Detail Area Error
This means the header presents an aggregation of all binding states and because this a quite common thing Eclipse-Databinding has built in support for this kind of thing.

private void addStatusSupport(ObservablesManager mgr, 
  final DataBindingContext ctx)
{
  AggregateValidationStatus aggregateStatus = 
    new AggregateValidationStatus(
      ctx.getValidationStatusProviders(),
      AggregateValidationStatus.MAX_SEVERITY
  );

  aggregateStatus.addValueChangeListener(
    new IValueChangeListener()
    {
      public void handleValueChange(ValueChangeEvent event)
      {
        handleStateChange(
          (IStatus)event.diff.getNewValue(), 
          ctx
        );
      }
    });
  mgr.addObservable(aggregateStatus);
}

The above aggregates the validation states of all validation status providers. By the default there’s a provider for binding states attached to a databinding context but one can attach also custom ones which opens an interesting opportunity e.g. when used inconjunction with the EMF-Validation framework (probably a topic for another Blog post in the next weeks).
The final task is to display the status to the user through the form header control like this

private void handleStateChange(IStatus currentStatus, 
  DataBindingContext ctx)
{
  if (form.isDisposed() || form.getHead().isDisposed())
  {
    return;
  }

  if (currentStatus != null 
    && currentStatus.getSeverity() != IStatus.OK)
  {
    int type = convertType(currentStatus.getSeverity());

    List<IMessage> list = new ArrayList<IMessage>();
    Iterator< ? > it = ctx.getValidationStatusProviders()
     .iterator()
    ;

    while (it.hasNext())
    {
      ValidationStatusProvider validationStatusProvider = 
        (ValidationStatusProvider)it.next()
      ;
      final IStatus status = (IStatus)
        validationStatusProvider.getValidationStatus()
        .getValue()
      ;

      if (!status.isOK())
      {
        list.add(new IMessage()
        {
          public Control getControl()
          {
            return null;
          }

          public Object getData()
          {
            return null;
          }

          public Object getKey()
          {
            return null;
          }

          public String getPrefix()
          {
            return null;
          }

          public String getMessage()
          {
            return status.getMessage();
          }

          public int getMessageType()
          {
            return convertType(status.getSeverity());
          }
        });
      }
    }
    form.setMessage("Data invalid", 
      type, 
      list.toArray(new IMessage [0])
    );
  }
  else
  {
    form.setMessage(null);
  }
}

Master-Detail, Validation and null
That’s kind of a strange title but and not easy to track down but an undiscovered regression in Eclipse-Databinding since about 2 years and was discovered too late to get fixed in 3.5 timeframe.

In short the problem is that if you are producing an validation error in for a model attribute which initially had the value null the wrong value on the target (=UI-Widget) is not cleared when modifing master. Believe this sounds more complex than it is reality and bug 278301 holds an nice example to reproduce the behaviour.

Nonetheless we need a working solution for now because of the location of the problem we won’t see a fix until 3.6 ships which is 1 year from now. I’m demonstrating in the following code a possible work-around but there are also other solutions. My solution uses the AggregateValidationStatus-support and forces an target update when the master observable is changed and the current validation status is NOT OK.

public class Util
{
  public static void masterDetailFixup(
    final DataBindingContext ctx, 
    IObservableValue master)
  {
    final AggregateValidationStatus s = 
      new AggregateValidationStatus(
        ctx, 
        AggregateValidationStatus.MAX_SEVERITY);

    master.addChangeListener(new IChangeListener()
      {

        public void handleChange(ChangeEvent event)
        {
          IStatus status = (IStatus)s.getValue();
          if (status != null && !status.isOK())
          {
            ctx.updateTargets();
          }
        }
      });
  }
}

Setting up a TableViewer with EMF-Databinding

Let’s now take a look a the next topic for this post where we are going to set up a TableViewer using Eclipse-Databinding for JFace/SWT and EMF. The TableViewer is taking up the complete lower right part of the application
Table Viewer

In part 3 we saw how to setup a TreeViewer, in part 4 we saw how we are setting up a TableViewer without columns and as you’ll see setting up a TableViewer with multiple columns is very similar. Let’s take a look at the code first

private void init(IViewSite site, 
  CTabFolder folder, 
  DataBindingContext ctx, 
  EditingDomain editingDomain, 
  IObservableValue master)
{
  final TableViewer viewer = new TableViewer(
    folder, SWT.FULL_SELECTION
  );

  viewer.getTable().setHeaderVisible(true);
  ObservableListContentProvider cp = 
    new ObservableListContentProvider()
  ;

  {
    IObservableMap[] attributeMap = new IObservableMap [2];
    attributeMap[0] = EMFEditProperties.value(
      editingDomain,
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON, 
        ProjectPackage.Literals.PERSON__LASTNAME
      )
    ).observeDetail(cp.getKnownElements());

    attributeMap[1] = EMFEditProperties.value(
      editingDomain,
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON, 
        ProjectPackage.Literals.PERSON__FIRSTNAME
      )
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("Name");
    column.getColumn().setWidth(150);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0}, {1}", 
        attributeMap
      )
    );
  }

  {
    IObservableMap attributeMap = EMFEditProperties.value(
      editingDomain, 
      ProjectPackage.Literals.COMMITTER_SHIP__START
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("Start");
    column.getColumn().setWidth(100);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0,date,short}", 
        attributeMap
      )
    );
  }

  {
    IObservableMap attributeMap = EMFEditProperties.value(
      editingDomain, 
      ProjectPackage.Literals.COMMITTER_SHIP__END
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("End");
    column.getColumn().setWidth(100);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0,date,short}", 
        attributeMap
      )
    );
  }

  IListProperty prop = EMFEditProperties.list(
    editingDomain, 
    ProjectPackage.Literals.PROJECT__COMMITTERS
  );
  viewer.setContentProvider(cp);
  viewer.setInput(prop.observeDetail(master));

  MenuManager mgr = new MenuManager();
  mgr.add(
    new Action(
      "Hide historic committers", 
      IAction.AS_CHECK_BOX
    )
  {
    @Override
    public void run()
    {
      if (isChecked())
      {
        viewer.addFilter(new ViewerFilterImpl());
      }
      else
      {
        viewer.setFilters(new ViewerFilter [0]);
      }
    }
  });

  viewer.getControl().setMenu(
    mgr.createContextMenu(viewer.getControl())
  );
  site.registerContextMenu(
    Activator.PLUGIN_ID + ".committers", mgr, viewer);
}

The only thing not already explained before is a special type of CellLabelProvider which allows one to use MessageFormat-Syntax to specify how the label is constructed. This label provider is not part of the JFace-Databinding implementation but written by me but it’s quite easy to implement.

public class GenericMapCellLabelProvider 
  extends ObservableMapCellLabelProvider
{
  private IObservableMap[] attributeMaps;
  private String messagePattern;

  /**
   * Create a new label provider
   * @param messagePattern the message pattern
   * @param attributeMaps the values to observe
   */
  public GenericMapCellLabelProvider(
    String messagePattern, 
    IObservableMap... attributeMaps)
  {
    super(attributeMaps);
    this.messagePattern = messagePattern;
    this.attributeMaps = attributeMaps;
  }

  @Override
  public void update(ViewerCell cell)
  {
    Object element = cell.getElement();
    Object[] values = new Object [attributeMaps.length];
    int i = 0;
    for (IObservableMap m : attributeMaps)
    {
      values[i++] = m.get(element);
      if (values[i - 1] == null)
      {
        cell.setText("");
        return;
      }
    }
    cell.setText(
      MessageFormat.format(messagePattern, values)
    );
  }
}

That’s all we need to have a working TableViewer though there’s even more one can do with the Databinding-API e.g. Inline-Editing is supported and there are helpers to setup viewers in even less lines of code.

This entry was posted in EMF, Tutorials. Bookmark the permalink.

20 Responses to Galileo: EMF-Databinding – Part 5

  1. Pingback: Galileo: Improved EMF-Databinding-Support « Tomsondev Blog

  2. Thomas B. says:

    Hello Tom,

    I tried to use the EMFDatabinding Properties API (Eclipse 3.5) for a TableViewer together with TENEO Framework. I found an old bug 245183 saying that equals was implemented not correctly in the past. When I change my code to using “old” styled databinding IObservableList instead of IEMFListProperty everything works fine. Have you checked the Properties API & Teneo Framework against this old bug?

    Any reply is highly welcome.

    Best regards
    Thomas

  3. Wayne says:

    Hey Tom. Just FYI, I’ve added this series to the eclipse.org /resources page. Nice work.

  4. Andy says:

    Tom,

    Thanks for the information. Do you know where to find any more info on creating custom ValidationStatusProviders and attaching them to the databinding context?

  5. Jyothi Gudavalli says:

    Hi,
    I was trying to bind the table to backend emf model. My tableviewer is inside an editor. I am able to change the values of the table and see them in the viewer. But my editor doesn’t become dirty when I change an existing value in the table. In other words, the save button is not enabled in the swt editor.
    do I need to write new listners for this?
    Any help is appreciated

    Regards,
    Jyothi

    • Tom Schindl says:

      Well you need to inform the editor that your Resources is dirty. The easiest solution is is to use an EditingDomain and attach a listener to the command stack and whenever there are undo-able commands on the stack it means your editor is dirty.

  6. Phillip says:

    I have been rewriting a lot of code to take advantage of EMF binding, but I have run into a small problem that seems like it *should* be easily solvable.

    Basically, I want to display information in a Text widget, but the property to observe is a list. In some cases, for example, I would like to observe a list, but only display the first element. In other cases, I would like to observe a list and create a string representing that list.

    I tried to use a converter to accept the list as a IObservableValue, but that did not seem to work. The only other approach i can come up with is to create an intermediate pojo which would observe changes to the list and modify a single value which can be observed by the Text widget. While this would work, it would be surprising if this were the best solution.

    So, is there not a cleaner alternative?

    • Tom Schindl says:

      * The List to String can be done using a ComputedValue or is a there a back mapping needed too?
      * The entry in List can be done by creating an intermediate WritableValue and observe the parent list and update the writable value accordingly

      • Tom Schindl says:

        I think I have an idea how to solve the observe a special list entry problem. Would you be so kind to file a bug against EMF and CC or post the bug id here. I can’t guarantee that this will be solved with in the next 2.7 release unless you or your company funds the work from me on it (I’ll nevertheless will give it a try).

      • Phillip says:

        Thanks for the ideas and, yes, I’ll file a bug for this issue. I doubt I would be able to secure funding for you to accelerate the development (unfortunately), but I think my company would be willing to allow me to assist with the development as part of my work.

      • Tom Schindl says:

        Hi. Great! Just to make sure that my comment about funding is not mistaken: I’m naturally considering to work on this problem but because of my current focus on Eclipse 4.1 I’m not able to guarantee that I’ll be able to implement the solution I already outlined in my previous comment. Like outlined I already have an idea how to do it but it takes time to turn this idea into working code and API people can use to solve this not really uncommon requirement.

        BTW if you are using EMF you could get away using a derived, volatile feature to get around the problems you are describing here because they really provide you the indirection you are requesting (at least most of it).

  7. Hi. Im having an issue with the GenericMapCellLabelProvider. I am still working on Eclipse Helios and implemented your GenericmalCellLabelProvider. The Problem is, the IObservableMap attributeMaps, returned from observeDetail(cp.getKnownElements()) is always empty, because the ContentProvider gets the input like in your example almost at the end of the method. Did i missed something?

    • Tom Schindl says:

      What do you mean with empty? If the cp.getKnownElements() is empty at start this is expected because it can be populated only you’ve set an input

  8. I mean something like that
    067 IObservableMap attributeMap = EMFEditProperties.value(
    068 editingDomain,
    069 ProjectPackage.Literals.COMMITTER_SHIP__END
    070 ).observeDetail(cp.getKnownElements()); <– getKnownElement is empty

    077 column.setLabelProvider(
    078 new GenericMapCellLabelProvider(
    079 "{0,date,short}",
    080 attributeMap

    089 viewer.setContentProvider(cp);
    090 viewer.setInput(prop.observeDetail(master)); <– here comes the input

    When im trying to do that, my IObserableMap is empty. But i have the feeling that i dont see something important :D.

    • Tom Schindl says:

      It’s perfectly ok for getKnownElement() to be empty at the given position so this can not be the problem – once the input is set you’ll see that getKnownElements()-Set contains values that’s the whole idea

  9. Ok, i think my problem comes from a lack of Knowledge how viewers and databinding are working together. I worked around my problem, but i will watch into it next week. Thanks for your help or at least that you tried :).

  10. Stein Erik says:

    Thanks for some excellent tutorials and work.
    Being a rookie on the Eclipse Platform, including both utilizing the community resources and the tech itself, I still hope you have time to answer a quick question.

    I sense from various docs that this EMF binding implementation could be altered/moved, or perhaps as an outermost consequence not supported, in the future. Maybe I am wrong here. Anyway, the question is: Do you consider this EMF binding implementation so stable and robust, both now and further down the line, that it is worthwhile developing a commercial application relying on it?

    Of course, the reason I am asking is that developing the software as an EMF centric application is very appealing. At the same time I do not understand all the implications of that and is trying to explore and clarify in this phase.

    • Tom Schindl says:

      The API is marked as provisional but the only reason is that I’ve always waited to long before remove the provisional warning. The API is considered stable, we are actively maintaining the library and it has been used in many commercial projects already.

      • Stein Erik says:

        Thanks Tom for your rapid answer. I hope I can contribute and test somehow. Not in the near future though, I am almost totally “cold” on the Eclipse platform at this point in time.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.