Daten und ihre Bindungen

Eclipse DataBinding für die Kommunikation zwischen Modell und GUI

Ludwig Mittermeier

Die Trennung des Kerns einer Applikation, dem Modell, vom Graphical User Interface (GUI) ist eine erstrebenswerte Architektureigenschaft. Dennoch müssen beide Teile miteinander kommunizieren können. Das in Eclipse 3.3 enthaltene Eclipse DataBinding hilft dem Entwickler bei der Erstellung von Applikationen, bei denen in der grafischen Benutzerschnittstelle Daten angezeigt und mit dem Applikationskern synchron gehalten werden müssen.

Ziel der Trennung ist, dass das Modell keine oder möglichst wenige Abhängigkeiten zu einer bestimmten User-Interface-Technologie wie Swing oder SWT hat und sogar auch ohne GUI verwendet werden kann. Eine solche Trennung hat positive Auswirkungen auf Wiederverwendbarkeit, Wartbarkeit, Änderbarkeit und Testbarkeit des Modells. Obwohl also eine Trennung von Modell und GUI das Ziel ist, so müssen diese beiden Teile einer Applikation doch miteinander interagieren: In fast allen Applikationen müssen nämlich bestimmte Parameter der Modellklassen im User Interface angezeigt werden, das User Interface muss also Zugriff zu bestimmten Teilen des Modells erhalten. Entsprechendes gilt für die Gegenrichtung: wird eine Modelleigenschaft in einer editierbaren User-Interface-Komponente angezeigt und ändert der Benutzer diese, so muss diese Änderung natürlich dem Modell mitgeteilt werden. Man spricht hier vom sog. DataBinding: Attribute des Modells werden an User-Interface-Komponenten gebunden, sodass das User Interface das jeweilige Attribut anzeigt, auf Änderungen im Modell reagiert und umgekehrt Änderungen im User Interface auch wieder an das Modell übermittelt werden. Unterstützung hierfür gibt es in der Eclipse-Plattform in Gestalt des Eclipse DataBinding, welches der vorliegende Artikel erklären möchte.

DataBinding bedeutet im Allgemeinen die Synchronisation von Werten zwischen Objekten. Abbildung 1 veranschaulicht dieses Konzept. Die Synchronisation erfolgt oft in beiden Richtungen und schließt häufig auch Datentypkonvertierungen und Werteüberprüfungen mit ein. Oft wird DataBinding dazu verwendet, GUI-Elemente mit Modellattributen zu verknüpfen.

Abb. 1: DataBinding: Synchronisation von Werten zwischen Objekten

Eine technische Ausprägung des allgemeinen DataBinding-Konzepts ist das Eclipse DataBinding. Vom Prinzip her ist Eclipse DataBinding eine Variation des bekannten Modell-View-Controller (MVC)-Entwurfsmusters. Im Unterschied zu diesem hat der Controller beim Eclipse DataBinding jedoch eine zentralere Rolle inne und agiert als Vermittler zwischen Modell und View, indem er beide bezüglich Änderungen beobachtet und aktualisiert (Abb. 2).

Abb. 2: MVC-Entwurfsmuster im Vergleich zu Eclipse DataBinding

Vorteil, dass JFace Content– und LabelProvider eleganter implementiert werden können. Um alle Vorteile von Eclipse DataBinding nutzen zu können, besteht lediglich die Voraussetzung, nicht Swing, sondern das Standard Widget Toolkit (SWT) und JFace für die grafische Benutzeroberfläche zu verwenden. Der Kern von Eclipse DataBinding ist jedoch unabhängig von JFace, und es wäre lediglich ein Adapter für Swing nötig, um Eclipse DataBinding auch für Swing-basierte Java-Anwendungen einsetzen zu können. Anhand eines kleinen Beispiels soll jetzt gezeigt werden, wie die Verknüpfung zwischen Modell und GUI auf herkömmlichem Wege erfolgt. Dann wird in einem weiteren Schritt gezeigt, wie das gleiche Resultat mittels Eclipse DataBinding erzielt werden kann und welche Vorteile und zusätzlichen Möglichkeiten sich daraus ergeben.

[ header = Seite 2: DataBinding mittels EventListener ]

DataBinding mittels EventListener

Als kleines Beispiel soll die Klasse Mitarbeiter dienen, die das Feld name vom Typ String enthält. In der GUI soll der darin gespeicherte Name eines Mitarbeiters in einer editierbaren SWT-Textkomponente angezeigt werden. Sollte sich zur Laufzeit des Programms der Name ändern, so soll dies natürlich umgehend in der GUI angezeigt werden, und wenn der Benutzer Eingaben im SWT-Textfeld macht, so müssen diese natürlich auch der jeweiligen Instanz der Klasse Mitarbeiter mitgeteilt werden. Mit Standard-Java-Mechanismen wird dies üblicherweise mittels Listenern implementiert. Da die Klasse Mitarbeiter keine Abhängigkeiten zur GUI haben soll, bestimmt sie selbst ein Interface, über welches sie Änderungen propagiert. An dieses Interface muss sich jeder halten, der über Änderungen von Instanzen der Klasse Mitarbeiter informiert werden will, in unserem Falle die GUI. Den Quellcode der Klasse Mitarbeiter zeigt Listing 1, der Einfachheit halber belegen wir gleich die Variable name mit einem häufig vorkommenden Wert.

public class Mitarbeiter {
String name = "Huber";
private PropertyChangeSupport propertyChangeSupport =
new PropertyChangeSupport(this);

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

public String getName() {
return name;
}

public void setName(String name) {
propertyChangeSupport.firePropertyChange("name",
this.name, this.name = name);
}

}

Die Klasse PropertyChangeSupport aus dem Paket java.beans vereinfacht die Verwaltung und den Aufruf der Listener. Die Klasse Mitarbeiter stellt unser Applikationsmodell dar, es ist natürlich für dieses Beispiel sehr einfach aufgebaut und konzentriert sich auf die unbedingt nötigen Teile. Als nächstes betrachten wir nun das Graphical User Interface. Damit Änderungen im Modell sich in der GUI zeigen, erfolgt eine Registrierung beim Mitarbeiter als PropertyChangeListener:

// Synchronisation vom Modell zur GUI
mitarbeiter.addPropertyChangeListener("name", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) { nameWidget.setText((String) evt.getNewValue());
}
});

Im Falle einer Namensänderung eines Mitarbeiters wird also die Methode setText des SWT Text Widgets aufgerufen, um die GUI zu aktualisieren und den neuen Namen anzuzeigen. Damit im umgekehrten Falle sich Änderungen in der GUI auf das Modell auswirken, benötigen wir einen ModifyListener beim entsprechenden SWT Widget:

// Synchronisation von der GUI zum Modell
nameWidget.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mitarbeiter.setName(nameWidget.getText());
}
});

Dieser ModifyListener wird bei Änderungen im nameWidget benachrichtigt. Er kann dann das Modell aktualisieren, in unserem Falle bringt er den Namen des Mitarbeiters auf den neuen Stand. Es fällt auf, dass bei dieser Vorgehensweise unser Modell innerhalb von Widget Event Handlern aktualisiert wird. Wenn unsere kleine Beispielklasse Mitarbeiter über mehrere Felder verfügen würde, die jeweils mithilfe eines Widgets in der GUI angezeigt werden sollen, so hätten wir sehr ähnlichen Code in zahlreichen Event-Listener-Methoden. In der Praxis kommt noch hinzu, dass die Daten zwischen GUI und Modell oftmals nicht einfach, wie in unserem Beispiel, eins zu eins übertragen werden können. Meist sind Konvertierungen nötig, und im Falle der Übertragung von der GUI zum Modell auch Validitätsprüfungen. Sehen wir uns daher als Nächstes an, wie sich unser kleines Beispiel mithilfe von Eclipse DataBinding verbessern lässt.

DataBinding im Eclipse-Stil

Wir wollen den Vorteil nutzen, dass das Eclipse DataBinding sehr effektiv mit JavaBeans umgehen kann. Die Klasse Mitarbeiter hält sich bereits an die JavaBeans-Konventionen in Bezug auf setter– und getter-Methoden, wir brauchen diese Klasse daher nicht zu ändern. Lediglich in der GUI benötigen wir ein kleines Refactoring. Die obigen zwei Event-Listener-Methoden werden durch folgenden Quellcode ersetzt:

DataBindingContext bindingContext = new DataBindingContext();

bindingContext.bindValue(SWTObservables.observeText(nameWidget, SWT.Modify),
BeansObservables.observeValue(mitarbeiter, "name"), null, null);

Mit dieser einzigen bindValue-Anweisung sind das SWT Widget nameWidget und die JavaBean mitarbeiter miteinander synchronisiert. Damit ist die Funktionalität unseres zu Anfang dieses Artikels vorgestellten kleinen Beispiels bereits mit Eclipse DataBinding abgedeckt. Es sind folgende Vorteile von Eclipse DataBinding gegenüber dem ersten Ansatz erkennbar:

  • Ein Aspekt (Verknüpfung eines bestimmten SWT Widgets mit einer bestimmten JavaBean) wird im Quellcode durch eine einzige Anweisung ausgedrückt.
  • Wir brauchen in diesem Beispiel keine Widget Event Listener zu implementieren und sparen uns dadurch lästig zu schreibenden, sich häufig in sehr ähnlicher Form wiederholenden Quellcode.
  • Wir benötigen keine explizite Registrierung beim Modell, um auf Änderungen zu reagieren und die GUI zu aktualisieren.
  • In diesem Beispiel machen wir zwar keine Verwendung davon, aber Eclipse DataBinding bietet auch den Vorteil, dass es zahlreiche Standard-Datentypkonvertierungen (z.B. von String zu Integer) automatisch erledigt.

Im vorliegenden Beispiel haben wir eine Instanz der Klasse DataBindingContext erzeugt und davon die Methode bindValue aufgerufen, die Klasse DataBindingContext ist also der Dreh- und Angelpunkt von Eclipse DataBinding. Um seine Arbeit verrichten zu können, erhält ein DataBindingContext in einem bindValue-Aufruf vier Parameter:

  • TargetObservable (z.B. ein SWT Text Widget)
  • ModelObservable (z.B. eine JavaBean des Applikations-Modells)
  • UpdateStrategy Target zu Model (T zu M)
  • UpdateStrategy Model zu Target (T zu M)

Schauen wir uns noch einmal den bindValue-Aufruf unseres Beispiels an:

bindingContext.bindValue(SWTObservables.observeText(nameWidget, SWT.Modify),
BeansObservables.observeValue(mitarbeiter, "name"), null, null);

Wir sehen, dass das TargetObservable im vorliegenden Fall SWTObservables.observeText ist, das ModelObservable erhält den Wert BeansObservables.observeValue und UpdateStrategy wird beide Male mit null belegt. Dies bewirkt, dass eine Standard-UpdateStrategy verwendet wird. Betrachten wir zunächst das SWTObservable. Ein SWT Text Widget bietet die Eigenschaft, einen Text anzuzeigen und zu verändern. Mittels der Methode observeText verwenden wir diesen Text im vorliegenden Beispiel für das Eclipse DataBinding. Wir könnten jedoch genauso gut und genauso einfach andere Eigenschaften, wie zum Beispiel Sichtbarkeit, Auswahl (Selektion) oder Farbe eines SWT Widgets mit einem Modellattribut mittels Eclipse DataBinding verknüpfen. Abbildung 3 zeigt, welche Methoden die Klasse SWTObservables zu diesem Zweck bietet. Als Nächstes wollen wir das Augenmerk auf BeansObservables richten. Durch die Methode observeValue drücken wir aus, dass wir uns für eine bestimmte Property einer konkreten JavaBean interessieren, in unserem Falle die Property name der JavaBean mitarbeiter.

Abb. 3: Die Methoden der Klasse SWTObservables

[ header = Seite 3: Eine Strategie muss her ]

Eine Strategie muss her

Wir haben bisher betrachtet, wie ein DataBindingContext mit einem TargetObservable und einem ModelObservable versorgt wird, welche durch DataBinding miteinander synchronisiert werden sollen. Was aber nun noch fehlt, sind genaue Anweisungen, wie diese Synchronisation vorgenommen werden soll. Diese Anweisungen werden bei Eclipse DataBinding in Gestalt einer UpdateStrategyangegeben, welche aus diesen drei Teilen besteht:

  • Konvertierungsanweisungen
  • Validierungsanweisungen
  • Policy

Wichtig dabei ist, dass ein DataBindingContext zwei unterschiedliche Update-Strategien haben kann, nämlich eine für die Synchronisation von Target zu Modell und die andere für die Gegenrichtung, von Modell zu Target.

Konvertierung

Schauen wir uns nun im Folgenden die Bestandteile einer UpdateStrategy genauer an, beginnend mit den Konvertierungsanweisungen. Diese werden, wenn es darum geht, einen Wert mit einem bestimmten Datentyp zu einem anderen Datentyp zu konvertieren, von Eclipse DataBinding herangezogen. Es ist natürlich möglich, eigene Konvertierungsanweisungen anzugeben, Eclipse DataBinding liefert jedoch bereits eine schöne Auswahl fertiger Datentypkonvertoren (Abb. 4).

Abb. 4: Datentypkonvertoren von Eclipse DataBinding

Validierung

Validierungsanweisungen dienen dazu, eine Synchronisation von Target und Modell nur dann durchzuführen, wenn der zu synchronisierende Wert eine Gültigkeitsprüfung besteht. Man kann sich zum Beispiel vorstellen, dass der Benutzer in einem grafischen Eingabefeld eine Zahl eingeben soll, wobei von der Applikationslogik her diese Zahl niemals 1 000 oder größer sein kann. Wenn nun nicht schon das grafische Eingabefeld auf drei Stellen beschränkt ist, kann bei Eclipse DataBinding als Bestandteil einer UpdateStrategy eine entsprechende Validierungsanweisung angeben werden, sodass der Wert des graphischen Eingabefeldes nur dann zum ApplikationsModell synchronisiert wird, wenn der Benutzer eine Zahl eingegeben hat, die 999 oder kleiner ist. Eine solche Validierungsanweisung kann in Form einer Klasse angegeben werden, die das Interface IValidator implementiert und so aussehen könnte:

private static class ThreeDigitsValidator implements IValidator {

public IStatus validate(Object value) {
Integer price = (Integer) value;
return (price < 1000) ? Status.OK_STATUS :
ValidationStatus.error("the price is too high");
}
}

Policy

Nach der Konvertierungsanweisung und der Validierungsanweisung wollen wir uns nun den dritten Bestandteil einer UpdateStrategy ansehen, nämlich die Policy. Diese steuert im Wesentlichen, wann Eclipse DataBinding eine Synchronisierung durchführt, und kann folgende Werte annehmen:

  • NEVER
  • ON_REQUEST
  • CONVERT
  • UPDATE

NEVER bedeutet, dass Eclipse DataBinding das Quell-Observable nicht beobachtet und niemals das Ziel-Observable aktualisiert. ON_REQUEST bedeutet, dass das Quell-Observable nicht beobachtet wird und Validierung, Konvertierung und Aktualisierung nur dann vorgenommen werden, wenn die entsprechenden Methoden der DataBindingContext-Instanz aufgerufen werden. CONVERT bewirkt, dass das Quell-Observable beobachtet wird und auch die Validierung durchgeführt wird, die Aktualisierung des Ziel-Observables geschieht jedoch nur, wenn dies explizit beimDataBindingContext angefordert wird. UPDATE schließlich ist quasi der Automatik-Modus, bei dem das Quell-Observable beobachtet wird und die Konvertierung, Validierung und Aktualisierung des Ziel-Observables automatisch bei Bedarf vorgenommen werden. Für alle vier Policy-Werte gilt: Das Quell-Observable ist entweder das Target- oder das Modell-Observable, in Abhängigkeit davon, ob es sich um die UpdateStrategy T zu M oder M zu T handelt.

[ header = Seite 4: Änderungsmitteilungen ]

Änderungsmitteilungen

Bei unserem Beispiel sieht der Anwender in der GUI ein SWT-Texteingabefeld. Jedesmal, wenn in diesem Feld eine Änderung gemacht wird (also bei jedem Tastendruck), wird Eclipse DataBinding aktiv und führt Konvertierung, Validierung und Update durch. Ein solches Verhalten ist dann wünschenswert, wenn der Benutzer sofort Feedback zu seiner Eingabe erhalten soll. Zum Beispiel könnte sich das Texteingabefeld rot verfärben, wenn die aktuelle Benutzereingabe nicht den Anforderungen entspricht. Eine derartige Funktionalität lässt sich übrigens sehr schön mit Eclipse DataBinding realisieren. Ein anderes Szenario sieht beispielsweise so aus, dass der Anwender zunächst in Ruhe seine Eingabe machen kann und Überprüfungen, Konvertierungen und Aktualisierungen erst dann erfolgen sollen, wenn der Benutzer mit seiner Eingabe fertig ist und dies explizit ausdrückt, indem er zum Beispiel auf einen Button klickt. In diesem Fall würde unser Beispielquellcode so aussehen:

bindingContext.bindValue(SWTObservables.observeText(nameWidget, SWT.FocusOut),
BeansObservables.observeValue(mitarbeiter, "name"), null, null);

Der einzige Unterschied zu vorher ist, dass sich SWT.Modify zu SWT.FocusOut geändert hat. Durch den Parameter SWT.FocusOut wird Eclipse DataBinding immer dann über eine Änderung im SWT Widget nameWidget informiert, wenn der Fokus auf ein anderes Widget wechselt. Dies passiert etwa dann, wenn der Benutzer auf einen OK-Button klickt.

Stand der Dinge

Eclipse DataBinding ist in Version 1.0 im Europa Release der Eclipse-Plattform enthalten, die Ende Juni 2007 erschienen ist. Tatsächlich ist Eclipse DataBinding schon etwas älter und gereifter, wie der Kasten „Die Historie von Eclipse DataBinding“ zeigt, und es handelt sich bei dieser Technologie um ein gut durchdachtes Konzept. Bisher gibt es allerdings noch wenig Dokumentation zu Eclipse DataBinding. Als Startpunkte kann ich die Wiki-Seite [1] empfehlen sowie einen Blick in das CVS Repository [2]. Dort findet sich der Quellcode zu Eclipse DataBinding, der insgesamt recht gut dokumentiert ist und viele wertvolle Hinweise enthält. Darüber hinaus gibt es im CVS Repository eine ganze Reihe von JUnit-Tests für Eclipse DataBinding. Diese stellen ein hilfreiches Sammelsurium an Beispielen dafür dar, wie man Eclipse DataBinding benutzen kann und was alles damit möglich ist. Abbildung 5 zeigt, wo org.eclipse.core.databinding undorg.eclipse.core.databinding.beans im CVS Repository zu finden sind. Empfehlenswert ist auch ein Blick auf das Projekt org.eclipse.jface.examples.databinding.

Abb. 5: Eclipse DataBinding im CVS-Repository

Eclipse DataBinding hieß früher JFace DataBinding und kam in Gestalt eines einzigen Plug-ins, nämlich org.eclipse.jface.databinding. Für die Version 3.3 der Eclipse-Plattform wurde JFace DataBinding überarbeitet und besteht nun aus einem Kern-Plug-in (org.eclipse.databinding.core) sowie Adaptoren für JavaBeans und JFace (org.eclipse.databinding.beans und org.eclipse.databinding.jface). Die neue Bezeichnung wird noch nicht durchgängig verwendet, oft wird noch der Begriff JFace DataBinding benutzt, auch wenn damit das neuere Eclipse DataBinding gemeint ist.

Fazit

Wir haben gesehen, dass es mit Eclipse DataBinding auf elegante Art und Weise und mit relativ wenig Quellcode gelingt, ein GUI-Element mit einem Modellattribut zu verknüpfen. Es gibt zahlreiche Einstellmöglichkeiten, mit denen beeinflusst werden kann, zu welchen Zeitpunkten Eclipse DataBinding seine Arbeit verrichten soll. Mit diesen Möglichkeiten und in Verbindung mit individuellen Konvertierungs- und Validierungsanweisungen liefert Eclipse DataBinding sehr viel Flexibilität und ein großes Einsatzspektrum. Als Java-Entwickler spart man sich lästig zu schreibenden und sich häufig wiederholenden EventListener-Quellcode sowie unhandliche Datentypkonvertierungen. Probieren Sie es doch einfach mal selbst aus und profitieren Sie von diesen Vorteilen.

Geschrieben von
Ludwig Mittermeier
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.