Freitag, 24. Juni 2011

Eclipse RCP - Databinding, ein einfaches Beispiel

Zuletzt bearbeitet: 04.03.2012, Eclipse Version 3.7 (Indigo)

In diesem Artikel (Tutorial) soll ein einfaches Databinding Beispiel gezeigt und erläutert werden. Die Aufgabe besteht darin den Wert einer Eclipse RCP GUI (SWT) in einem vorgefertigten Datenmodel automatisiert abzulegen und bei Änderungen am Model von dort automatisiert in der GUI wieder darzustellen.
Der Code kann ausprobiert werden, indem eine leere Eclipse Applikation basierend auf dem Template "RCP Application with a View" erstellt wird. Es wird die View Klasse modifiziert und es werden zwei weitere Klassen angelegt.
Rot markierte Codeteile werden benötigt, um das Textfeld und das entsprechende Attribut im Model miteinander zu verbinden.
Grün markierte Codeteile werden für das Databinding nicht zwangsläufig benötigt. Die Codezeilen werden benötigt um auf Änderungen im Model zu reagieren. In der Praxis ist dies oft eine Anforderung und daher für unser Minimalbeispiel notwendig.

Das Model besteht aus einer einzigen Klasse Person mit dem Attribut vorname. Dies vereinfacht das Beispiel und macht die Funktionsweise deutlicher.

Folgende Plugins sollten im plugin.xml unter Dependencies eingetragen werden:
  • org.eclipse.core.databinding
  • org.eclipse.core.databinding.beans
  • org.eclipse.jface.databinding
  • org.eclipse.core.databinding.property
In den Run/Debug Settings des Projekts (Abbildung 1) sollten die oben erwähnten Plugins ebenfalls eingetragen sein (Abbildung 2). Ist dies nicht der Fall, dann tritt unter Umständen ein Fehler auf.

Abbildung 1
Abbildung 2


import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Person {
  private String vorname;
  private PropertyChangeSupport propertyChangeSupport;

  public Person() {
    propertyChangeSupport = new PropertyChangeSupport(this);
  }

  public void addPropertyChangeListener(String propertyName,
      PropertyChangeListener listener) {
    propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    propertyChangeSupport.removePropertyChangeListener(listener);
  }

  public String getVorname() {
    return vorname;
  }

  public void setVorname(String vorname) {
    propertyChangeSupport.firePropertyChange("vorname",
    this.vorname, this.vorname = vorname );
   }

  @Override
  public String toString() {
    return vorname;
  }
}
Die Klasse Person besitzt zwei Klassenattribute: vorname und propertyChangeSupport. Das Attribut vorname enthält den später in der GUI angezeigten Vornamen. Das Attribut propertyChangeSupport vom Typ PropertyChangeSupport und sorgt dafür, dass Änderungen an den Attributen an das Framework propagiert werden.

Mit Hilfe der Klasse PersonPropertyListener wird in der Methode propertyChange auf die Änderungen im Setter setVorname reagiert, indem der Alte und der Neue Wert ausgegeben werden. Beim Aufruf des Setters wird ein Event erstellt auf das alle registrierten PropertyChangeListener zugreifen können. In unserem Beispiel implementiert die Klasse PersonPropertyListener das Interface PropertyChangeListener.
Der PropertyChangeListener sieht wie folg aus:


import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class PersonPropertyListener implements
  PropertyChangeListener {

  @Override
  public void propertyChange(PropertyChangeEvent evt) {
    System.out.println("Vorname wurde von "
      + evt.getOldValue() + " in " + evt.getNewValue()
      + " geändert.");
  }
}


Wenn ein PropertyChangeEvent auftirtt, gibt der Listener den alten und den neuen Wert über die Standardausgabe aus. Dies geschieht bei jedem Aufruf der Setter Methode setVorname.

Als nächstes betrachten wir die View Klasse. Der Inhalt der createPartControl Methode wird ersetzt und die Klasse wird um zwei weitere Methoden erweitert.


public static final String ID = "databinding.view";
private Person person;
private Text vorname;

@Override
public void createPartControl(Composite parent) {

    person = new Person();
    PersonPropertyListener personListener = new PersonPropertyListener();
    person.addPropertyChangeListener("vorname", personListener);

    person.setVorname("Testname");

    GridLayout layout = new GridLayout(2, false);
    layout.marginRight = 5;
    parent.setLayout(layout);

    vorname = createLabelFieldPair(parent, "Vorname:");

    Button button1 = createButton(parent, "Model ausgeben!");
    button1.addSelectionListener(new SelectionAdapter() {

      @Override
      public void widgetSelected(SelectionEvent e) {
        System.out.println("Vorname: " + person.getVorname());
      }
    });

    Button button2 = new Button(parent, SWT.PUSH);
    button2.setText("Model zurücksetzen auf Startwert");
    button2.addSelectionListener(new SelectionAdapter() {

      @Override
      public void widgetSelected(SelectionEvent e) {
        person.setVorname("Testname");
      }
    });
  
    GridData gData = new GridData();
    gData.horizontalSpan = 2;
    button1.setLayoutData(gData);
    gData = new GridData();
    gData.horizontalSpan = 2;
    button2.setLayoutData(gData);
  
    bindData();
}

private void bindData() {
  DataBindingContext bindingContext = new DataBindingContext();
  IObservableValue widgetValue = WidgetProperties
                                     .text(SWT.FocusOut).observe(vorname);
  IObservableValue modelValue = BeanProperties
                                     .value(Person.class, "vorname")
                                     .observe(person);
  bindingContext.bindValue(widgetValue, modelValue);
}

private Text createLabelFieldPair(Composite parent, String labelValue) {
  Label label = new Label(parent, SWT.NONE);
  label.setText(labelValue);
  Text text = new Text(parent, SWT.BORDER);

  GridData gridData = new GridData();
  gridData.horizontalAlignment = SWT.FILL;
  gridData.grabExcessHorizontalSpace = true;
  text.setLayoutData(gridData);
  return text;
}
 
private Button createButton(Composite parent, String label){
  Button button = new Button(parent, SWT.PUSH);
  button.setText(label);
  
  GridData gData = new GridData();
  gData.horizontalSpan = 2;
  button.setLayoutData(gData);
  return button;
}

@Override
 public void setFocus() {
}

Die View besteht aus einem Textfeld und zwei Buttons (siehe Abbildung 3). In das Textfeld wird der Vorname eingetragen, der durch das Databinding im Attribut vorname einer Person Instanz gespeichert wird. Mit dem ersten Button ("Model ausgeben!") wird das Attribut vorname der Person Instanz ausgegeben. Mit dem zweiten Button ("Model zurücksetzen auf Startwert") wird das Attribut vorname in der Person Klasse auf den default Wert "Testname" zurückgesetzt.



Betrachten wir die Codezeilen der createPartControl Methode etwas genauer. 

Es wird zuerst eine neue Person und eine neue PersonPropertyListener Instanz erzeugt. Anschließend wird der PersonPropertyListener für das Attribut vorname registriert. Danach wird das Attribut vorname initialisiert. Die bewirkt das erstmalige feuern des PropertyChangeEvent. Auf diesen reagiert der PersonPropertyListener mit der Methode propertyChange, wodurch die Ausgabe des neuen und alten Wertes auf der Standardkonsole folgt.
In den darauf folgenden Zeilen wird ein GridLayout mit einem Label mit Textfeld und zwei Buttons erstellt. Für das Erzeugen des Label und Text Paars ist die Methode createLabelFieldPair zuständig.
Zu aller letzt wird die bindData Methode aufgerufen. Diese ist für das Binding des Widgets mit der Bean zuständig.
Zu beachten sei, dass es sich bei der Klasse Person um eine Java Bean handelt.

(Dieser Artikel basiert auf dem Artikel JFace Data Binding von Lars Vogel)

Keine Kommentare:

Kommentar veröffentlichen