Eclipse Stuff

Just another WordPress.com weblog

Archiv für die Kategorie ‘cool eclipse’

W-JAX am Dienstag 10.11.

Verfasst von splitshade am November 10, 2009

Resumée W-JAX vom 10.11

Da dies mein erster Besuch auf der W-JAX war, war ich natürlich ordentlich gespannt, wie die einzelnen Sessions wohl ablaufen werden.

Eröffnet wurde der Tag mit einer Keynote von Ted Newark, der etwas provokative Titel „Why the next five years will be about languages“ liess schon einiges Versprechen.

Ich sage mal, ich habe selten einen so kompetenten Speaker wie Ted erlebt, mit Wortwitz und Eloquenz konnte er mich durchaus überzeugen.

Idee hinter der Keynote war hauptsächlich, herauszuarbeiten wieso das Thema Languages die letzten paar Jahre (Jahrzehnte?) nicht wirklich relevant für das tägliche Business war.

Die Essenz der Aussage von Ted hierauf war, das Sprachen typischerweise im akademischen Bereich entwickelt werden. Der Fokus einer akademischen Arbeit ist üblicherweise aber der, einer „akademisches“ Problem zu lösen. Ist dieses akademische Problem gelöst, verliert sich das Interesse an der Weiterentwicklung dieser Sprache in den meisten Fällen leider sehr schnell.

Versuchte man also früher, als Entwickler eine „neue“ Sprache zu verwenden, stiess man sehr schnell an die Problematik, dass die Funktionalität einer Sprache über diese spezifische Problemstellung hinaus dürftig, wenn überhaupt vorhanden war (humoristisches, leicht übertriebenes Beispiel von Ted war die Frage, wie greife ich in einer hypotetischen Sprache auf eine relationale Datenbank zu? Die Antwort hierauf, „Relational Databases? I never heard of this new Thing??“. Es herrscht wohl die Meinung vor, dass Sprachen üblicherweise von Professoren entwickelt werden, die mindestens ein Jahrzehnt nicht mehr draussen in Projekten waren;-).

Heute scheint es möglich zu sein, seine eigene „einfache“ Sprache innerhalb von einem Tag zu entwickeln, und zwar nicht von Akademikern, sondern vom Standardentwickler wie auch ich einer bin. Eingie Beispiele zu neuen Sprachen, die natürlich zur Sprache kamen waren Groovy, Ruby, Scala etc. Insgesamt stimmte die Keynote auf einen erfolgreichen Tag ein.

Die zweite Session die ich besucht habe war geleitet von … Hierbei ging es darum, die Neuigkeiten in JEE6 gegenüber JEE5 und sogar J2ee herauszuarbeiten. Der Vortragsstil des Referenten ist als durchaus „nüchtern“ zu beschreiben, da wünsche ich mir doch ein wenig mehr Enthusiasmus, nichts desto trotz war es für mich interessant, mal zu hören, was denn alles so zu kommen scheint. (Ich habe in meiner täglichen Arbeit eher weniger mit EJBs etc.. zu tun, deswegen muss ich leider eingestehen dass ich hier nicht mehr so ganz auf dem laufenden bin).

Der Referent berichtete aus seinem täglichen Projektgeschäft und speziell von einem Projekt zur Erstellung eines FTS ( Führerlosen Transportsystems auf Basis von EJB und Java, als Frontend kam ein Swing Client zum Einsatz).

Meiner Ansicht nach interessante Änderungen die mit EJB 3.1 komme sind:

  1. @Singleton-Annotation : hierbei bietet die Spec die Möglichkeit, eine EJB wirklich nur einmal (pro JVM) zur Verfügung zu stellen. Könnte sich durchaus eigenen, um Applicationsettings einmalig global zu setzen. Hieraus ergeben sich natürlich recht interessante Multithreadingproblematiken, was ist zum Beispiel, wenn mehrere Clients gleichzeit auf die Singleton Instanz zugreifen? Hier bietet die Spec einigre interessante Annotations, mit denen das Locking geregelt werden kann (@Lock(READ oder WRITE) oder @AccessTimeout.
  2. Endlich, standardisierte JNDI-Namen für EJBs , und zwar Herstellerunabhängig nach dem Pattern java:global/<app>/<module>/beanImpl:Interface, keine Ahnung wieso das nicht schon früher kam.
  3. Timerservice (@Timeout, @Schedule etc., müsste man sich mal im Detail anschauen)
  4. @Asynchronous, wir haben jetzt die Möglichkeit, eine Session-Bean-Methode asynchron auszuführen, d.h. Die Methode kehrt sofort zurück und liefert bei Bedarf ein Future (JDK 1.5), mit dem das Ergebnis abgefragt werden kann.
  5. Die Möglichkeit, einen EJBContainer lokal programmatisch durchzustarten (für beispielsweise UnitTests : EJBContainer.createEJBContainer(), über diesen bekommt man beispielsweise einen Context, mit dem alle EJBs abgefragt werden können). Extrem cooles Feature!!
  6. Man braucht keine Interfaces mehr (@LocalBean)

Container die EJB3.1 aktuell schon unterstützen sind Glassfish V3, Jboss 5.2 und OpenEJB.

Die dritte Session des Tages trug den verheissungsvollen Titel „EJB in the Large“. Die Referentin war direkt aus Brasilien eingeflogen, um aus Ihrer täglichen Arbeit zu berichten. Das Projekt das hier beschrieben wurde ist als durchaus eindrucksvoll zu bewerten. Es wurde beschrieben, wie ein „Health Care System“ für die Stadt „Sao Paolo“ in sage und schreibe 9 Monaten entwickelt wurde, und zwar auf Basis von EJB 2.1, Entity Beans, Xdoclet, Drools, Luntbuild und CrossDB. Das System konnte täglich ca. 1 Mio Anfragen bearbeiten, was zusammen mit der eingesetzten Technologie schon beinahe Sagenhaft ist.

Auch die Ansätze die die Referentin (gleichzeitig Projektleiterin) gewählt hat, waren sehr interessant. Insgesamt waren in dem Projekt ca. 70 Entwickler beschäftigt aufgeteilt in 3 Teams, teilweise EJB Experten, teilweise aber auch nicht. Am Ende bestand das Projekt aus 600!! EJB-Instanzen (für jeden Use-Case wurde eine eigene SessionFacade erstellt). Der Persistenzlayer wurde mit EntityBeans realisiert. (Aber nicht komplett, denn beispielsweise wurde aus Performancegründen komplett auf das Queriying mittels EntityBeans verzichtet. Dies wurde separat mit einem SQL-Framework CrossDB erstellt).

Der Vortrag an sich konnte leider wenig beeindrucken. Abgerundet wurde das Ganze von einem „Film“ gedreht von Sun, der ca. 10 Minuten dauerte. Ich habe derweil versucht, meine Eindrücke ordentlich zusammenzuschreiben;-).

Der Vortrag bestand eigentlich aus zwei Teilen. Im zweiten Teil sollte die Migration auf EJB 3 beschrieben werden, aufgrund des eher dürftigen ersten Teils habe ich mich aber entschlossen, den zweiten Teil auszulassen.

Als nächstes kam die Keynote von Adam Bien („The Future of Enterprise Java“). Gewohnt souverän versuchte hier Adam Bien die rosige Zukunft von Enterprise Java zu bestätigen, und das prognostizierte Aussterben von EJB zu widerlegen;-).

Das Fazit, dem ich mich persönlich durchaus anschliesse, EJB wird in nächster Zeit nicht aussterben! Good news!

Der nächste Workshop hatte den Titel „JPA under the Hood“ und wurde gehalten von Alois Reitbauer.

Kern des Workshops sollte es sein, herauszuarbeiten, wie sich verschiedene Persistenzframeworks für bestimmte Use-Cases verhalten (untersucht hat der Speaker Hibernate, EclipseLink und OpenJPA). Hierbei wurden u.a. die UseCases ObjectCache und QueryCache betrachtet.

Sehr interessant war für mich hier die vom Speaker eingesetzte Software. Die zentrale Frage hierbei war, kenne ich mein System wirklich, wenn ich nur die Interfaces kenne? (JPA? Hallo?).

Erste Frage: Das Query „select User u where u.id=1“

Essenz: Niemals feste Parameter in Queries kodieren, denn diese Queries können nicht im Querye-Cache landen und werden vom ORM-Framework immer und immer wieder ausgeführt.

Interessanterweise checkt sowohl OpenJPA als auch EclipseLink, dass es den Parameter hierzu ersetzen muss (gespeichert wird also die Query „select User u where u.id=?“.

Hibernate hingegen setzt tatsächlich die nicht parametrisierte Query ab.

Zweite Frage:

Was passiert, wenn der User geladen wird, eine Änderung in der Datenbank gemacht wird und dann diesselbe Query erneut abgesetzt wird?

Antwort: Die Änderungen aus der Datenbank werden nicht geladen, weil die Persistenzframeworks auf Objektidentität prüfen und bemerken, das der User mit der bestimmten ID bereits im Cache vorhanden ist. Durchaus überrsaschend, wenn auch logisch.

Dritte Frage:

Ist „getReference“ tatsächlich sinnvoll? GetReference() bietet die Möglichkeit, eine Referenz aus einer Klasse zu laden, ohne das die eigentliche Klasse wirklich geladen werden muss. (Beispielsweise kann man eine AdressEntity eines Users laden, ohne das der User komplett geladen werden muss).

Ergebnis: Ja, das funktioniert sowohl in Hibernate als auf OpenJPA. EclipseLink lädt leider standardmässig trotzdem alles aus der Datenbank, vorsichtshalber.

Vierte Frage:

Wieviele Connections werden erzeugt, wenn man in einem Loop ständig neue Sessions anfordert?

Antwort: Ohne Transaktionen, eine. Mit Transaktionen erzeugt Hibernate für jede angeforderte Session eine eigene Connection (autsch!).

Eergebnis: Es wäre schön, wenn alle Persistenzframeworks einen gemeinsamen Defaultstandard hätten. Ist aber nicht der Fall…

Im nächsten Workshop ging es um die Specs JSR-299, 330 und 340. Der Referent war Werner Keil, leider habe ich bisher keinen so schlechten Vortrag gehört, deswegen habe ich die gesamte Session versucht, mein Wlan ans Laufen zu bekommen;-).

Die Letzte Session war definitv das Highlight des Tages : Live on Stage Jave EE 6 Hacking mit Adam Bien.

Unter anderem wurden einige SessionBeans erstellt, Restful-Zugriff, JSF 2.0 Seiten, JPA Entities etc.. immer wieder ein Spass, Adam zuzuschauen.

Bin schon gespannt, was morgen kommt.

Veröffentlicht in cool eclipse | Kommentar schreiben »

W-JAX in München

Verfasst von splitshade am November 10, 2009

Heute und morgen bin ich glücklicher Inhaber einer Eintrittkarte für die W-JAX 2009 in München.
Es dürften zwei vielversprechende Tage werden, ich werde hier natürlich berichten.

Veröffentlicht in cool eclipse | Verschlagwortet mit : , | Kommentar schreiben »

Exzellentes P2-Tutorial

Verfasst von splitshade am Oktober 25, 2009

Ein wirklich exzellentes Tutorials über P2 gibt hier.

VIelen Dank an Ralf Ebert für diesen Beitrag.

Veröffentlicht in cool eclipse | Kommentar schreiben »

Toolbar in einer Form mit Commands bevölkern

Verfasst von splitshade am Oktober 12, 2009

Wieder 2 Stunden meines Lebens verschwendet, weil ich eine einzige Zeile vergessen habe,

es ist zum ausrasten, damit mir das nicht nochmal passiert, soll das Ganze hier dokumentiert werden. Was wir machen möchten ist die Bevölkerung einer FormToolbar mit einem deklarativ beschriebenen Menu.

Das Ganze soll also über Commands funktionieren.

toolbar

Hierfür definieren wir zunächst 2 Commands nebst irgendwelchen Handlern:

<command commandId=“playmp3″ icon=“icons/Button-Play-32×32.png“ label=“Play“ style=“push“>
<command commandId=“stopmp3″ icon=“icons/Button-Stop-32×32.png“ label=“Stop“ style=“push“/>

Dann definieren wir die Toolbar-Menucontribution, die in der FormToolbar angezeigt werden soll:

<menuContribution locationURI=“popup:myMp3ToolBar“>
<command commandId=“playmp3″ icon=“icons/Button-Play-32×32.png“ label=“Play“ style=“push“>
</command>
<command commandId=“stopmp3″ icon=“icons/Button-Stop-32×32.png“ label=“Stop“ style=“push“>
</command>

</menuContribution>

Um dieses deklarativ beschrieben Menu nun in einer FormToolbar anzuzeigen, braucht mal folgendes Codefragment:

ToolBarManager manager = (ToolBarManager) sForm.getToolBarManager();
toolkit.decorateFormHeading(sForm.getForm());
IMenuService menuService = (IMenuService)getSite().getService(IMenuService.class);
menuService.populateContributionManager(manager, „toolbar:myMp3Toolbar„);
manager.update(true);
Composite container = sForm.getBody();

Die in fetten Lettern  beschriebene ID ist die genau die ID die wie zuvor definiert haben in der plugin.xml.

Achtung, der Grund für diesen Blogeintrag, vergisst man die zweite fette Zeile, passiert nichts! keine Fehlermeldung kein gar nichts, einfach nichts! Manchmal hasse ich Eclipse.

Veröffentlicht in cool eclipse | 1 Kommentar »

Glassfish V3 on OSGi

Verfasst von splitshade am Juli 10, 2009

Zu Beginn einige Glassfish-Facts, die nicht jeder kennt (zumindest ich nicht;))

Glassfish kommt mit einer Embedded-Datenbank (JavaDB) – kann gestartet werden mit

glassfish/bin/asadmin start-database

Hochinteressant, mittlerweile dürfte wohl jeder mitbekommen haben, das Glassfish in der V3 auf einem OSGi-Container (Felix) aufbaut.

Mir war bisher zwar klar, das sich hierdurch für Glassfish einige Vorteile ergeben (Management, Dynamik), aber ich hatte keine Ahnung, inwiefern das für mich als Entwickler interessant sein kann.

Zunächst mal hier einige Infos die hier weiterhelfen:

Die OSGi-Konsole bietet einen Remote-Schnittstelle an, wenn der Glassfish gestartet wurde, kann mit „telnet localhost 6666″ eine Verbindung zur Konsole aufgebaut werden

Unbenannt-2

Das allein ist zwar definitiv interessant, reicht aber nicht.

Ein Entwickler von Glassfish betreibt hierzu einen wirklich hochinteressanten Blog, in welchem beispielsweise ein Eintrag vorhanden ist, der sich damit beschäftigt, ein OSGi-Bundle als WAR File zu deployen. Denkt man sichjetzt noch die Möglichkeit, OSGi-Services zu verwenden um Funktionalitäten Horizontal zwischen Applikationen anzubieten, dann tun sich hier schöne neue Welten auf.

Die Blogeinträge hierzu findet man hier:

http://weblogs.java.net/blog/ss141213/archive/2009/06/osgi_enabled_we.html

http://weblogs.java.net/blog/ss141213/archive/2009/06/developing_hybr.html

http://weblogs.java.net/blog/ss141213/archive/2009/05/using_felix_web.html

Ein Blick lohnt sich auf jedenfall, wenn jemand hier bereits interessante Erfahrungen gemacht hat würde ich mich über Kommentare freuen.

Veröffentlicht in cool eclipse | Kommentar schreiben »

RCP UI Testing mal ganz ohne Framework

Verfasst von splitshade am Mai 19, 2009

Das Testen von UI Funktionalitäten ist oftmals eine nicht ganz einfache Aufgabe.

Ein Framework, dass die Arbeit hier sehr erleichtert ist beispielsweise SWTBot.

Manchmal lässt die Projektsituation aber einfach nicht zu, ein weiteres Framework in die Systemarchitektur zu integrieren.

Jetzt hat man prinzipiell zwei Möglichkeiten

  1. Wir verzichten auf UI-Tests
  2. Man testet ohne Framework

Dass die erste Option nicht unbedingt zu empfehlen ist, sollte jedem Entwickler klar sein, ebenso aber auch dass die Umsetzung der zweiten Option nicht ganz trivial ist. Hier führen mit Sicherheit mehrere Wege nach Rom, einen davon (nicht zwangsläufig den besten oder kürzesten) soll dieser Artikel beleuchten.

Bevor sich ein Wandersmann auf den Weg macht, sollte die Ausrüstung überprüft werden. Statt gutem Schuhwerk und wasserfester Kleidung benötigen wir jedoch folgendes:

  • Databinding Framework
  • Eclipse Adapter Framework
  • Motivation

Machen wir uns also auf den Weg.

Die ersten Schritte sind meist die schwersten (bzw. die am wenigsten spannenden). Für einen Artikel ist dies meist die Umsetzung einer mehr oder minder spannenden Demoapplikation. Für unsere Zwecke brauchen wir also eine Anwendung mit Benutzeroberfläche, die es zu testen gilt.

Was sind aber testbare UI-Funktionalitäten?

  • Validierungslogik
  • Konvertierungslogik
  • Enablement / Disablement von Widgets
  • Visibility / Invisibility von Widgets

Diese Liste könnte wahrscheinlich endlos weitergeführt werden, aber das soll erstmal reichen.

Wir verwenden hierfür eine sehr einfache Demoanwendung. Das zugrundeliegende Modell besteht nur aus einer Klasse „Person“,

die in etwa so aussieht:

public class Person {

private String name;
private String surname;
private String email;
private String title;

private PropertyChangeSupport support = new PropertyChangeSupport(this);

/**
* Constructor
* */
public Person(String name, String email, String title) {
super();
this.name = name;
this.email = email;
this.title = title;
}

public void addPropertyChangeListener(String attribute,
PropertyChangeListener listener) {
support.addPropertyChangeListener(attribute, listener);
}

public void removePropertyChangeListener(String attribute,
PropertyChangeListener listener) {
support.removePropertyChangeListener(attribute, listener);
}

public String getName() {
return name;
}

public void setName(String name) {
String oldName = this.name;
this.name = name;
support.firePropertyChange(„name“, oldName, name);
}

public String getSurname() {
return surname;
}

public void setSurname(String surname) {
String oldSurname = this.surname;
this.surname = surname;
support.firePropertyChange(„surname“, oldSurname, surname);
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
String oldEmail = this.email;
this.email = email;
support.firePropertyChange(„email“, oldEmail, email);
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
String oldTitle = this.title;
this.title = title;
support.firePropertyChange(„title“, oldTitle, title);
}

}

Damit das Modellobjekt korrekt mit dem Databinding-Framework zusammenarbeitet, implementieren wir den PropertyChangeSupport derart, dass jede Setter-Methode ein PropertyChangeEvent feuert. Hierdurch ist sichergestellt, dass wann immer sich ein Modellattribut ändert, die Änderung an die UI propagiert wird (Ist diese Anforderung nicht zwingend kann auf die Implementierung des PropertyChangeSupport verzichtet werden).

Im Folgenden wird auf die Erläuterung der Standardfunktionalitäten verzichtet, diese sollte man entweder kennen oder nachschlagen.Erstellen wir eine passende Benutzeroberfläche hierfür. Wir erzeugen ein einfaches Demoprojekt als RCP Anwendung.

Zunächst erzeugen wir einen Editor mit einem passenden EditorInput.

Der Code des EditorInputs könnte in etwa so aussehen:

public class PersonEditorInput implements IEditorInput {

private Person model;

public PersonEditorInput(Person person) {
this.model = person;
}

@Override
public boolean exists() {
// TODO Auto-generated method stub
return false;
}

@Override
public ImageDescriptor getImageDescriptor() {
// TODO Auto-generated method stub
return null;
}

@Override
public String getName() {
return „PersonEditor“;
}

@Override
public IPersistableElement getPersistable() {
// TODO Auto-generated method stub
return null;
}

@Override
public String getToolTipText() {
return getName();
}

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

public Person getInput() {
return model;
}

}

Die einzig interessante Codezeile ist folgende:

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

Darauf kommen wir später noch zu sprechen.

Startet man die Anwendung, könnte das in etwa so aussehen:

pluginwithaview

Hier erstellen wir uns jetzt eine passende Benutzeroberfläche (auf die Erstellung gehe ich nicht näher ein).

view_mit_widgets

Das sieht doch schon gar nicht mal schlecht aus. Zunächst definieren wir Logik, die in diesem Zusammenhang getestet werden könnte (ohne bestimmte Reihenfolge).

  • Der Save-Button sollte nur enabled sein, wenn das Emailfeld korrekt befüllt ist oder die Checkbox deselektiert
  • Das Emailfeld sollte nur enabled sein, wenn die Checkbox selektiert ist
  • Wir die Checkbox abgewählt, sollte eine evtl. zuvor eingegebene Emailadresse gelöscht werden

Derartige Funktionalitäten zu testen ist schwer, da wir nicht einfach eine View für einen JUnit-Test instantiieren können, und selbst wenn das möglich wäre, müssten die einzelnen Felder (jetzt als private deklarierten Felder) irgendwie zugänglich gemacht werden, damit wir sie in einem JUnit-Test abdecken können (sieht man mal von der wenig schönen Möglichkeit ab, das Ganze über die Reflection-API zu lösen).

Gehen wir´s an!

Es gibt einen wunderschönen Design-Pattern, der uns die Testbarkeit zurückgibt, die uns im Verlauf des Weges abhanden gekommen ist, das Presentation Model, ursprünglich definiert von Martin Fowler, hunderte Male adaptiert und jetzt schliesslich hier gelandet. Auf die Philosophie des Design-Patterns, als auch auf mögliche Vor- und Nachteile werde ich nicht näher eingehen und verweise auf entsprechende Fachliteratur oder Online-Artikel. Uns interessiert an dieser Stelle primär die Implementierung.

Ein Presentation-Modell kann als eine Art Abstraktion für unsere View gesehen werden. Das klingt jetzt vielleicht schwierig, ist aber eigentlich sehr einfach.

Gibt es beispielsweise in der View eine Checkbox, dann bietet das PresentationModell hierfür Methoden wie

  • isCheckBoxSelected()
  • isCheckBoxEnabled()
  • isCheckBoxVisible()

Für ein Textfeld könnte das PresentationModell u.a. folgende Methoden bieten:

  • isTextFieldVisible()
  • isTextFieldEnabled()
  • isTextFieldEditable

Im Prinzip machen wir also bestimmte Eigenschaften unsere (natürlich privaten) Widgets nach aussen hin zugänglich, aber nur indirekt gekapselt durch das PresentationModel.Würde man sich das Ganze in einem Klassendiagramm visualisieren, würde das so aussehen:

pm_klassendiagramm

D.h. der View kennt das PresentationModell, und das PresentationModell kennt das Modellobjekt, das wars.

Da wir aber RCP verwenden, wollen wir das ganze noch ein wenig „loser“ koppeln.

Jetzt definieren wir uns das PresentationModel, gemäss den obigen Testanforderungen könnte eine Implementierung wie folgt aussehen, der Einfachheit halber beschränken wir uns auf das Testen des Emailfeldes und der damit zusammenhängenden Funktionalitäten, mögliche Validierungen für die anderen Felder überlasse ich dem Leser.

Ein Interface für das PresentationModel könnte so aussehen:

public interface IPersonPresentationModel {

public IObservableValue isEmailFieldVAlid();

public IObservableValue isEmailButtonChecked();

public IObservableValue isEmailFieldEnabled();

public IObservableValue isSaveButtonEnabled();

public IObservableValue getEmailTextValue();

public IObservableValue getNameValue();

public IObservableValue getTitleValue();

}

Wie man sieht, gibt es eine Methode die den Status der Checkbox überwacht, eine Methode für das Enablement des Emailfeldes (dies hängt von der Selektion der Checkbox ab), sowie eine Methode die das Enablement des Save-Buttons überwacht.Zusätzlich gibt es Getter für alle Observables zur Überwachung der Inhalte, also beispielsweise getNameValue() zur Überwachung des Textes in einem Textfeld.

Da der Editor, den wir hier überwachen, von der Eclipse-Plattform zur Laufzeit instantiiert wird, brauchen wir eine einfache Möglichkeit, das PresentationModel dem Editor bekannt zu machen.

Wir verwenden das Adapter-Framework von Eclipse. Adaptiert wird das Model-Objekt direkt, und zwar aus dem EditorInput. Hier kommen wir wieder zurück auf die zuvor definierte Zeile.

@Override
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(model, adapter);
}

Wir definieren hierzu eine Extension für den Extension-Point „org.eclipse.core.runtime.adapters“ wie folgt:

<extension
point=“org.eclipse.core.runtime.adapters“>
<factory
adaptableType=“de.pentasys.uitesting.demo.model.Person“
class=“de.pentasys.uitesting.demo.adapters.PersonViewAdapterFactory“>
<adapter
type=“de.pentasys.uitesting.demo.pm.IPersonPresentationModel“>
</adapter>
</factory>
</extension>

Die entsprechende Implementierung der Adapterfactory ist sehr einfach und sieht so aus:

public class PersonViewAdapterFactory implements IAdapterFactory {

public static final Class<?>[] ADAPTERS = new Class<?>[] { Person.class};

@Override
@SuppressWarnings(„unchecked“)
public Object getAdapter(Object adaptableObject, Class adapterType) {

return new PersonViewPresentationModel((Person)adaptableObject);

}

@Override
public Class<?>[] getAdapterList() {
return ADAPTERS;
}

}

Die Klasse adaptiert also die Klasse Person, und liefert ein hierfür passenden PresentationModel zurück.

Der nächste Schritt besteht jetzt darin, sowohl die Widgets in der UI an die Observables aus dem PresentationModell als auch die Observables des PresentationModels an die Attribute des Models zu binden. Im Editor könnte das so aussehen:

private void initBindings() {
DataBindingContext context = new DataBindingContext();
context.bindValue(SWTObservables.observeText(emailText, SWT.FocusOut),
pm.getEmailTextValue(), null, null);

context.bindValue(SWTObservables.observeText(nameText, SWT.FocusOut),
pm.getNameValue(), null, null);

context.bindValue(ViewersObservables
.observeSingleSelection(titleViewer), pm.getTitleValue(), null,
null);

context.bindValue(SWTObservables.observeSelection(emailButton), pm
.isEmailButtonChecked(), null, null);

context.bindValue(SWTObservables.observeEnabled(emailText), pm
.isEmailFieldEnabled(), new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

context.bindValue(SWTObservables.observeEnabled(saveButton), pm
.isSaveButtonEnabled(), new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

}

Im PresentationModel könnte das Ganze so aussehen:

context.bindValue(emailFieldValue, BeansObservables.observeValue(
person, „email“), null, null);

context.bindValue(titleValue, BeansObservables.observeValue(person,
„title“), null, null);

context.bindValue(nameValue, BeansObservables.observeValue(person,
„name“), null, null);

Startet man das Ganze jetzt mit einer Demoperson, kann man erkennen, dass die Daten bereits an die Widgets gebunden sind. Bisher ist nichts gewonnen, definieren wir also die restlichen Bindings.

Schauen wir uns einige interessantere Bindings im Presentation-Model an:

context
.bindValue(emailFieldEnabledValue, emailButtonCheckedValue,
new UpdateValueStrategy(
UpdateValueStrategy.POLICY_NEVER), null);

Hier binden wir das emailFieldEnabledValue an den EmailButton, daraus ergibt sich, dass das Email-Feld nur enabled ist, wenn die Checkbox aktiviert ist.

Der Save Button soll nur Enabled sein, wenn eine gültige Email oder keine Email eingegeben wurde.

context.bindValue(saveButtonEnabledValue, emailFieldValue,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER),
new UpdateValueStrategy().setConverter(new Converter(
String.class, Boolean.class) {
@Override
public Object convert(Object fromObject) {
String str = fromObject.toString();
if („“.equals(str) || str
.matches(„[a-zA-Z0-9\\.]+@[a-zA-Z0-9]+\\.[a-z]{2,3}“)) {
return true;
}
return false;
}
}));

Wir verwenden hier einen Converter und eine Regexp zur Validierung der Email-Adresse.

Hier haben wir 2 Funktionlitäten, die es wert sind, getestet zu werden.

  1. Ist der SaveButton aktiviert, wenn keine Emailadresse eingegeben wurde?
  2. Ist der SaveButton aktiviert, wenn eine gültige Emailadresse eingegeben wurde?
  3. Ist der SaveButton deaktiviert, wenn eine ungültige Emailadresse eingegeben wurde?
  4. Ist das Emailfeld aktiviert, wenn die Checkbox aktiviert wurde?

Das schöne dabei ist, dass wir nur noch die Inhalte der entsprechenden Observables testen müssen, da der View (also unser Editor) im Prinzip

komplett dumm ist, und keine Logik enthält, sondern stattdessen seine Widgets nur gegen die Observables im PresentationModel bindet.

Definieren wir also ein Fragment, welches die Tests für unser Plugin definiert.

Die oben festgelegten Funktionalitäten lassen sich nun sehr einfach testen, ein Testfall könnte in etwa so aussehen:

public class PresentationModelTest {

private IPersonPresentationModel model;

private Person person;

@Before
public void setUp() {
person = new Person(null,null,null);

model = new PersonViewPresentationModel(person);

}

@After
public void tearDown() {
person = null;

model = null;
}

@Test
public void testSaveInitialEnabled(){
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}

@Test
public void testSaveEnabledLegalEmail(){
model.isSaveButtonEnabled().setValue(false);
person.setEmail(„test@test.de“);
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testSaveEnabledEmptyEmail(){
model.isSaveButtonEnabled().setValue(false);
person.setEmail(„“);
assertTrue((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testSaveDisabledIllegalEmail(){
person.setEmail(„test@de“);
assertFalse((Boolean)model.isSaveButtonEnabled().getValue());
}
@Test
public void testEmailFieldEnabledCheckBox(){
model.isSaveButtonEnabled().setValue(false);
assertFalse((Boolean)model.isEmailButtonChecked().getValue());
assertFalse((Boolean)model.isEmailFieldEnabled().getValue());

model.isEmailButtonChecked().setValue(true);
assertTrue((Boolean)model.isEmailFieldEnabled().getValue());
}
}

Lässt man die Tests laufen, sieht man sofort:

unittests

Ah, grün!! wunderbar, bauen wir doch mal einen Bug ein, um das Ganze in Aktino zu sehen, nehmen wir an,

es schleicht sich ein Fehler im Validator ein, so dass dieser immer true liefert, egal was man für eine Emailadresse eingibt.

context.bindValue(saveButtonEnabledValue, emailFieldValue,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER),
new UpdateValueStrategy().setConverter(new Converter(
String.class, Boolean.class) {
@Override
public Object convert(Object fromObject) {
if(true)
return true;

String str = fromObject.toString();
if („“.equals(str) || str
.matches(„[a-zA-Z0-9\\.]+@[a-zA-Z0-9]+\\.[a-z]{2,3}“)) {
return true;
}
return false;
}
}));

Jetzt lassen wir die Tests nochmals laufen, und sehen wie erwartet:

unittests_fail

Dieser Artikel liefert mit Sicherheit keine Brandneuen Erkenntnisse, noch sind die Themen die er behandelet neu. Die Herangehensweise ist

aber dennoch interessant und düfrte für so manches Projekt von Belang sein. Ich hoffe, ich konnte euch ein wenig für das Thema begeistern, viel Spass damit, über Feedback würde ich mich freuen.

Den Source-Code zu der Demoanwendung findet man übrigens hier.

Veröffentlicht in cool eclipse, gui patterns | 1 Kommentar »

Databinding in Galileo

Verfasst von splitshade am April 10, 2009

Die neue Properties API.

Ich sitze hier gerade in einem Regionalzug auf dem Weg ins Allgäu und habe mir gedacht, ich nutze die Zeit (3 Stunden fahrzeit von München aus), um mir
einige neue Features von Galileo anzuschauen.

Das Thema Databinding interessiert mich sehr, zum Einen weil ich im aktuellen Projekt sehr viel damit zu tun habe,
zum Anderen weil ich es als essentiellen Besandteil einer RCP-Anwendung betrachte
Ich habe mir aus diesem Grund kürzlich die neue Properties-API in Galileo zu
Gemüte geführt.

Was ändert sich für den gemeinen Entwickler?

Im Prinzip erst mal nichts, alles wird einfacher, schneller , besser.

Schauen wir uns hierzu einige Beispiele an:

1. Das Binding eines Textfeldes an eine ModelProperty.

Bisher:
Text text = new Text(parent, SWT.BORDER);
Person person = new Person(„Mueller“, „Hans“, „Bertholdweg 7″, „88279″,
„Amtzell“);
DataBindingContext ctx = new DataBindingContext();
ctx.bindValue(SWTObservables.observeText(text, SWT.Modify),

Zunächst mal fällt der längst überfällige Schritt auf, dass man auch ohne Übergabe einer UpdateValueStrategy
ein Binding anlegen kann (man beachte das bisher in 3.4 immer notwendige null,null **gähn**)

Das anlegen von Bindings über die Properties-API würde so aussehn (Die Properties-API wird übrigens
mittlerweile auch von allen Klassen wie BeanObservables, SWTOBservables etc. intern verwendet).

ctx.bindValue(WidgetProperties.text(SWT.Modify).observe(text),
BeanProperties.value(„vorname“).observe(person));

Zunächst mal scheint es so, als wäre nicht viel gewonnen, der zu schreibende Code ist im Prinzip erst
mal fast gleich.

Ganz ähnlich sieht die Sache aus, wenn man Viewer verwendet:
ComboViewer viewer = new ComboViewer(parent, SWT.NONE);
viewer.setInput(new String[] { „Herr“, „Frau“, „Dr.“, „Prof.“ });
viewer.setLabelProvider(new LabelProvider());
viewer.setContentProvider(new ArrayContentProvider());

ctx.bindValue(ViewerProperties.singleSelection().observe(viewer),
BeanProperties.value(„anrede“).observe(model));

Machen wir das ganze mal etwas dynamisch, die Klasse Person kriegt wie eigentlich in allen meinen Beispielen
Child-Objekte :-)
Was wir jetzt machen möchten ist, die ChildObjekte in einer Liste anzeigen, und zwar jeweils mit dem Vornamen.
Bisher würde man das in etwa so realisieren:
ListViewer viewer = new ListViewer(parent);
ObservableListContentProvider cp = new ObservableListContentProvider();
viewer.setContentProvider(cp);
ObservableMapLabelProvider lp = new ObservableMapLabelProvider(
BeansObservables.observeMap(cp.getKnownElements(), „vorname“));
viewer.setLabelProvider(lp);
viewer.setInput(BeansObservables.observeList(model, „children“));

mit der Properties-API sieht das in ungefähr so aus:
ctx.bindValue(ViewerProperties.input().observe(viewer), new WritableValue(BeanProperties
.list(„children“).values(„vorname“).observe(model),IObservableList.class));

Was hierbei richtig cool ist ist die Kaskadierung der Properties:
BeanProperties.list(„children“).values(„vorname“).observe(model)

Hier wird im Prinzip zuerst eine ObservableListProperty aus der Children-Liste generiert, und anschliessend direkt
auf dieser ListProperty eine weitere ValueProperty erzeugt, die den Vornamen observiert.
Das kann man theoretisch endlos weitertreiben. Einfacher gehts nicht, intern wird ein Master-Detail Binding erzeugt,
welches die observierte Liste (children) mit den Values verknüpft.
Es würde jetzt also, wenn sich ein Vorname eines Childs ändert, diese Änderung direkt in dem
Viewer widergespiegelt werden.

Da bleibt mir nur, viel Spass mit Databinding, ich seh hier goldene Zeiten auf uns zukommen…

Veröffentlicht in cool eclipse | Kommentar schreiben »

The Mysterious Table Viewer

Verfasst von splitshade am Februar 18, 2009

Heute soll es um meinen speziellen Freund, den „TableViewer“ gehen, speziell darum, wie man einen TableViewer implementiert, der unter Umständen etwas komplexer ist (mit Comboboxen etc…).

Witzigerweise ist es nämlich wie mit beinahe allem, es gibt hierzu keine brauchbaren Tutorials, also muss man sich selbst helfen und die try-and-error-Variante fahren.

Da ich mich eine Zeitlang wirklich enorm über dieses Thema geärgert habe, habe ich beschlossen, dieses Thema in einem kleinen Eintrag zu erläutern (nicht zuletzt für mich selbst, damit ich beim nächsten Mal weiß wo ich nachzuschauen habe).

Als Beispiel implementieren wir einfach mal einen TableViewer, der einer Liste einer bestimmten Person aus einer Liste eine bestimmte Rolle zuordnen kann.

Hierzu bauen wir uns zuerst wieder das typische Personen-Modell zusammen:

Klasse Person:

public class Person {

private String name;

private Rolle rolle;

public Person(String name, Rolle rolle) {

super();

this.name = name;

this.rolle = rolle;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Rolle getRolle() {

return rolle;

}

public void setRolle(Rolle rolle) {

this.rolle = rolle;

}

public String toString() {

return getName() + getRolle().toString();

}}

Enum Rolle:

public enum Rolle {

ADMIN, CUSTOMER, USER

}

Klasse PersonService:

public class PersonService implements IPersonService {

private static PersonService service = new PersonService();

private IObservableList persons = new WritableList();

public PersonService() {

persons.add(new Person(„Hans“, Rolle.ADMIN));

persons.add(new Person(„Fritz“, Rolle.CUSTOMER));

persons.add(new Person(„Gerhard“, Rolle.USER));

persons.add(new Person(„Harald“, Rolle.ADMIN));

}

@SuppressWarnings(„unchecked“)

public List<Person> getPersons() {

return (List<Person>) Collections.checkedList(persons, Person.class);

}

public IObservableList observePersons() {

return persons;

}

public void addPerson(Person person) {

persons.add(person);

}

public static PersonService getInstance() {

return service;

}

public List<Rolle> getRollen() {

return Arrays.asList(Rolle.values());

}

}

Zusätzlich bauen wir einen View, der eine Tabelle bereitstellt, die:

  1. Alle User anzeigt
  2. Die Möglichkeit bietet, die Rolle eines Users zu ändern
  3. Die Möglichkeit bietet, neue User anzulegen
  4. Mit Databinding arbeitet

Zunächst mal bearbeiten wir die createPartControl-Methode des Views, und legen einen TAbleViewer an:

final TableViewer viewer = new TableViewer(container);

viewer.getTable().setHeaderVisible(true);

viewer.getTable().setLinesVisible(true);

Der TableViewer soll 2 Columns haben (Person und Rolle):

TableViewerColumn person = new TableViewerColumn(viewer, SWT.BORDER);

person.getColumn().setText(„Person“);

person.getColumn().setWidth(100);

TableViewerColumn rolle = new TableViewerColumn(viewer, SWT.BORDER);

rolle.getColumn().setText(„Rolle“);

rolle.getColumn().setWidth(100);

Nun setzen wir den Label- und ContentProvider. Da wir mit DataBinding arbeiten, bietet sich der ObservableListContenentProvider an (der übrigens ausgezeichnet funktioniert!!)

Damit das jedoch richtig funktioniert, passen wir den PersonService folgendermaßen an:

public class PersonService implements IPersonService {

private IObservableList persons = new WritableList();

public PersonService() {

persons.add(new Person(„Hans“, Rolle.ADMIN));

persons.add(new Person(„Fritz“, Rolle.CUSTOMER));

persons.add(new Person(„Gerhard“, Rolle.USER));

persons.add(new Person(„Harald“, Rolle.ADMIN));

}

@SuppressWarnings(„unchecked“)

public List<Person> getPersons() {

return (List<Person>)Collections.checkedList(persons, Person.class);

}

public IObservableList observePersons(){

return persons;

}

public void addPerson(Person person) {

persons.add(person);

}

public static PersonService getInstance() {

return new PersonService();

}

}

Jetzt können wir die ObservableList aus dem Service direkt als Input in den TableViewer verwenden, und alle Änderungen an der PersonenListe werden sofort in der Tabelle widergespiegelt.

viewer.setLabelProvider(new LabelProvider());

viewer.setContentProvider(new ObservableListContentProvider());

viewer.setInput(PersonService.getInstance().observePersons());

Startet man die Anwendung jetzt, sieht das ungefähr so aus:

tableviewer

Zumindest ist es eine Tabelle…

Ok, wir müssen natürlich definieren, in welche Spalte welche Werte angezeigt werden sollen, das erledigt der LabelProvider, also ändern wir das folgendermaßen ab:

viewer.setLabelProvider(new ITableLabelProvider() {

@Override

public Image getColumnImage(Object element, int columnIndex) {

return null;

}

@Override

public String getColumnText(Object element, int columnIndex) {

switch (columnIndex) {

case 0:

return element.toString();

case 1:

return ((Person) element).getRolle().toString();

default:

return „“;

}

}

@Override

public void addListener(ILabelProviderListener listener) {

// TODO Auto-generated method stub

}

@Override

public void dispose() {

// TODO Auto-generated method stub

}

@Override

public boolean isLabelProperty(Object element, String property) {

// TODO Auto-generated method stub

return false;

}

@Override

public void removeListener(ILabelProviderListener listener) {

// TODO Auto-generated method stub

}

});

Noch eleganter wäre, nicht dem TableViewer an sich, sondern jeder Column einen eigenen LabelProvider zur Verfügung zu stellen.

column.setLabelProvider(new ColumnLabelProvider(){

}

Wie dem auch sei, Neuer Versuch:

tableviewer_21

Ok, was wir jetzt aber wollen, ist zum Einen, dass man den Namen der Person editieren kann (durch einfache Eingabe) und zum Anderen wollen wir die Rolle über eine ComboBox auswählen.

Jetzt wirds spannend, weil hier die Dokumentation im Web mehr als spärlich wird.

Eclipse bietet hierzu die Klasse „EditingSupport“, der JavaDoc Kommentar ist hierzu mehr als sprechend:

„EditingSupport is the abstract superclass of the support for cell editing.“

Spitze! und so zieht sich das durch, man findet kein!! ich wiederhole : KEIN wirklich brauchbares Beispiel im Web, bis heute!!

Ok, zurück zum Thema:

Was das Editieren von Columns in einer Tabelle ermöglicht sind sogenannte CellEditoren. Diese Editoren können Text-, COmbo-,Checkbox etc.. sein.

Was uns hier interessiert ist zum Einen der Texteditor zum Eingeben des Personennamens und ein Comboeditor zum Eingeben der Rolle.

Zunächst die Namenseingabe:

person

.setEditingSupport(new ObservableValueEditingSupport(viewer,

ctx) {

private TextCellEditor textEditor;

@Override

protected IObservableValue doCreateCellEditorObservable(

CellEditor cellEditor) {

return SWTObservables.observeText((Text) cellEditor

.getControl(), SWT.Modify);

}

@Override

protected IObservableValue doCreateElementObservable(

Object element, ViewerCell cell) {

return BeansObservables.observeValue(element, „name“);

}

@Override

protected CellEditor getCellEditor(Object element) {

if (textEditor == null) {

textEditor = new TextCellEditor((Composite) viewer

.getControl());

}

return textEditor;

}

});

Man sieht, im Prinzip muss man nur 2 Methoden implementieren, die jeweils ein ObservableValue für den CellEditor (also entweder ein Text oder eine Combo) bereitstellt, und eine Methode, die ein ObservableValue für das beabachtete Objekt (in diesem Fall ein Person-Objekt bereitstellt).

Klickt man jetzt auf ein Feld in der Tabelle ändert sich dieses in ein Text-Feld und man kann den Wert editieren!

tableviewer_3

Problem ist, drückt man jetzt auf Enter wird zwar der Wert im Modell geändert, nicht jedoch automatisch der Viewer refreshed, d.h. dass weiterhin der alte Wert angezeigt wird, wenn man sich nicht im Edit-Modus befindet.

Hierzu kann man folgenden Listener registrieren, der das bewerkstelligt.

viewer.getColumnViewerEditor().addEditorActivationListener(

new ColumnViewerEditorActivationListener() {

@Override

public void afterEditorActivated(

ColumnViewerEditorActivationEvent event) {

// TODO Auto-generated method stub

}

@Override

public void afterEditorDeactivated(

ColumnViewerEditorDeactivationEvent event) {

viewer.refresh();

}

@Override

public void beforeEditorActivated(

ColumnViewerEditorActivationEvent event) {

// TODO Auto-generated method stub

}

@Override

public void beforeEditorDeactivated(

ColumnViewerEditorDeactivationEvent event) {

// TODO Auto-generated method stub

}

});

So, weiter gehts mit der Rollen-Column, hier haben wir im Prinzip nochmals das Gleiche Prozedere, nur dass wir hier einen ComboViewer in einem TableViewer verbauen wollen.

rolle.setEditingSupport(new ObservableValueEditingSupport(viewer, ctx) {

private ComboBoxViewerCellEditor cellEditor;

@Override

protected IObservableValue doCreateCellEditorObservable(

CellEditor cellEditor) {

return ViewersObservables

.observeSingleSelection(((ComboBoxViewerCellEditor) cellEditor)

.getViewer());

}

@Override

protected IObservableValue doCreateElementObservable(

Object element, ViewerCell cell) {

return BeansObservables.observeValue(element, „rolle“);

}

@Override

protected CellEditor getCellEditor(Object element) {

if (cellEditor == null) {

cellEditor = new ComboBoxViewerCellEditor(

(Composite) viewer.getControl());

cellEditor.setContenProvider(new ArrayContentProvider());

cellEditor.setLabelProvider(new LabelProvider());

cellEditor.setInput(Rolle.values());

}

return cellEditor;

}

});

tb4

Ok, zuletzt brauchen wir noch die FUnktionalität, eine neue Person anzulegen.

Hierzu hätte ich gerne ein Contextmenü. DAs ist ganz einfach realisiert:

private void initContextMenu() {

MenuManager manager = new MenuManager();

viewer.getControl().setMenu(

manager.createContextMenu(viewer.getControl()));

manager.add(new Action(„Person hinzufügen“) {

@Override

public void run() {

PersonService.getInstance().addPerson(

new Person(„“, Rolle.USER));

viewer.refresh();

}

});

}

tb51

By the way, es hindert uns hier auch nichts daran, dieses Context Menü über das Menüframework zu erweitern.

Hierzu müssen wir lediglich das Menü über die ViewSite registrieren, damit wir global darauf zugreifen können.

getSite().registerContextMenu(mgr,null);

Zu beachten ist hier lediglich, dass innerhalb eines Parts jedes ContextMenu unter der ID des Parts registriert wird.

Um das Context Menü deklarativ zu bevölkern braucht man jetzt nur noch etwas wie:

<menuContribution
locationURI=“popup:de.md.commands.menuview“>
<command
commandId=“de.md.commands.helloworld“
label=“Hallo Popup“
style=“push“>
</command>
</menuContribution>


Mit einem Viewer, der den EditorSupport verwendeet, hat man das Problem, dass dieser voraussetzt, dass
der TableViewer mit dem Flag „SWT.FULL_SELECTION“ erzeugt wurde.
Oft ist es aber nicht erwünscht, dass man die ganze Zeile selektiert, sondern oft möchte man nur eine einzelne
Zelle selektieren (am besten noch so wenig wie möglich mit der Maus sondern per Tastatur).
Hier stösst man schnell an die Grenzen des mit dem normalen Tableviewer machbaren.

Doch wie so oft gibt es hier einige mehr als nützliche Klassen, die ich im folgenden mal kurz dokumentieren möchte.

final TableViewerFocusCellManager focusCellMgr = new TableViewerFocusCellManager(
viewer, new FocusCellOwnerDrawHighlighter(viewer));

Der TableViewerFocusCellManager bietet die Möglichkeit, das Highlighting der Zellen anzupassen, damit nicht immer
die gesamte Zeile markiert ist, sondern nur diejenige, die wir gerade auch wirklich selektiert haben. Das macht der
FocusCellOwnerDrawHighlighter für uns. Die genauere Funktionsweise dahinter braucht uns zunächst nicht zu interessieren,
da diese Zeile schon alles macht, was wir brauchen.

Weiter gehts:
ColumnViewerEditorActivationStrategy actStrategy = new ColumnViewerEditorActivationStrategy(
viewer) {
protected boolean isEditorActivationEvent(
ColumnViewerEditorActivationEvent event) {
return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION
|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION
|| (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED);
}

};

Hier wirds dann interessant, denn oft hat man von Fachbereichen die Anforderung, ich möchte aber, dass wenn ich hier auf Enter drücke (oder hoch, oder runter, oder was ganz anderes), dass dann die Combobox aufgeht….
Hierfür gibts so etwas wie eine ColumnViewerEditorActivationStrategy.
Die hat eine CallbackMethode isEditorActivationEvent, die im Prinzip bei jeder User-Interaktion überprüft, ist das Event gewünscht um den Viewer
zu triggern? Wenn ja, CellEditor aktivieren, wenn nein… weiter.
Im obigen Beispiel verwenden wir das Durchlaufen mit Tab (Traversal), MouseEvents und KeyEvents als Activation Events für den Celleditor.

Das ganze binden wir jetzt noch an unseren TableViewer

TableViewerEditor.create(viewer, focusCellMgr, actStrategy,
ColumnViewerEditor.TABBING_HORIZONTAL
| ColumnViewerEditorActivationEvent.TRAVERSAL
| ColumnViewerEditor.KEYBOARD_ACTIVATION);

Hier hat man zusätzlich noch die Möglichkeit, über Flags zu konfigurieren, wie die einzelnen Zellen durchlaufen werden sollen.

ColumnViewerEditor.TABBING_HORIZONTAL
Bei durchlaufen mit Tab soll immer eine Zelle horizontal weitergesprungen werden.

ColumnViewerEditor.KEYBOARD_ACTIVATION
KeyboardActivation einschalten

ColumnViewerEditorActivationEvent.TRAVERSAL
CellAktivierung beim Durchlaufen einschalten.

Das ist so einfach!! und funktioniert wunderbar, nur ein Problem gibts damit. Wenn man das Horizontale Tabbing einschaltet, dann kommt man
mit Tab nie wieder aus dem TableEditor heraus. Wenn jemand hierfür eine brauchbare Lösung hat, bitte immer her damit!!

Veröffentlicht in allgemein, cool eclipse | 11 Kommentare »

Commands, Handlers und Parameter…

Verfasst von splitshade am Januar 7, 2009

Hallo zusammen,

nachdem ich mich selbst nun schon eine ganze Weile damit herumgeärgert habe, die Dokumentation wie üblich spärlich ist, und in den diversen Beiträgen hier im Prinzip schon alle „Fragmente“ einmal zur Sprache kamen, soll hier mal ein funktionierendes Beispiel zusammengebaut werden, und zwar soll es darum gehen,wie man Commands + Handler mit einer Parameterübergabe implementieren kann.

Die einzelnen Bausteine, die wir hierfür benötigen sind:

  • Commands
  • Handler
  • ISourceProviders

Wir machen hier 2 Beispiele, zum Einen den einfachen Fall, und zwar, das ein String als Parameter übergeben wird.

Und den etwas komplizierteren Fall, und zwar indem wir einen beliebigen Parameter übergeben, nennen wir ihn Object;-).

Aber eins nach dem anderen, denken wir uns also ein Beispiel aus.. .. .. .. .. ..

Aha! wir implementieren ein OpenViewCommand, das eine ViewID als String-Parameter bekommt und diesen VIew anschliessend auch öffnet.

Was brauchen wir hierzu:

Command +  Handler

Textfeld für die Eingabe der View-ID

Button zum Auslösen der Command

2 Views, zur Unterscheidung.

Ok, frisch ans Werk:

Wir erstellen also ein einfaches Plug-In Project, natürlich als RCP-Anwendung und nehmen folgendes Template her:

rcp1

Anschliessend implementieren wir einen ersten View der als Container für unsere Widgets dienen soll, die Implementierung ist derart trivial, das ich hier nicht darauf eingehen werden, das ganze sieht anschliessend so aus:

view1

Auf die Implementierung des BusinessLogik für den Button kommen wir später noch.

Jetzt definieren wir eine Command mit der ID de.md.example.commandparameter.openview,der wir auch direkt einen DefaultHandler zuordnen.

<extension
point=“org.eclipse.ui.commands“>
<command
id=“de.md.example.commandparameter.openview“
name=“name“>
</command>
</extension>
<extension
point=“org.eclipse.ui.handlers“>
<handler
class=“de.md.example.commandparameter.commands.OpenViewHandler“
commandId=“de.md.example.commandparameter.openview“>
</handler>
</extension>

Jetzt implementieren wir den SelectionListener auf dem Button, wobei das interessante Code-Fragment im folgenden aufgelistet ist:

ICommandService cS = (ICommandService)getSite().getService(ICommandService.class);
IHandlerService hS = (IHandlerService)getSite().getService(IHandlerService.class);

Command openView = cS.getCommand(„de.md.example.commandparameter.openview“);

Map<String,String> params = new HashMap<String,String>();
params.put(„myViewID“, text.getText());
ParameterizedCommand pC = ParameterizedCommand.generateCommand(openView, params);
try {
hS.executeCommand(pC, null);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

Zuerst holen wir uns den IHandlerService + ICommandService.

Den CommandService brauchen wir, um uns das auszuführende Command zu holen, den HandlerService brauchen wir, um das Command auszuführen.

Ok, der Reihe nach, zuerst holen wir uns das zuvor in der plugin.xml deklarierte openView-Command

Command openView = cS.getCommand(„de.md.example.commandparameter.openview“);

Dann erzeugen wir uns eine Map, die den von uns übergebenen Parameter beinhaltet

Map<String,String> params = new HashMap<String,String>();
params.put(„myViewID“, „de.md.example.views.myView1″);

de.md.example.views.myView1 ist hier die ID eines Views, die geöffnet werden soll, sobald der Button gedrückt wird.

Zur Ausführung einer Command mit einem Parameter gibt es die Abstraktion ParameterizedCommand,was im Prinzip einfach eine Command mit einem Parameter ist.  Seit Eclipse 3.4 gibt es hier den sehr einfache Weg über

ParameterizedCommand pC = ParameterizedCommand.generateCommand(openView, params);

Wie man sieht, bekommt die Convenience-Methode das Command mit den zuvor definieren Parametern. Führt man dies jetzt aber so aus, wird diese Methode immer „null“ liefern, warum? Weil wir bisher keinen Parameter für unsere Command definiert haben. Das holen wir gleich mal nach und passen die Command-Deklaration folgendermaßen an:

<extension
point=“org.eclipse.ui.commands“>
<command
id=“de.md.example.commandparameter.openview“
name=“name“>
<commandParameter
id=“myViewID“
name=“name“
optional=“true“>
</commandParameter>
</command>
</extension>

Jeder Parameter, der einer Command übergeben wird, muss zuvor hier deklariert werden.

Führen wir jetzt das ganze aus, und fügen in unserem Handler mal eine einfache Ausgabe ein, sieht man sofort folgendes:

viewcommand

Cool! Scheint zu funktionieren, ok, wir implementieren mal 2 Views.

<view
class=“de.md.example.commandparameter.views.View1″
id=“de.md.example.commandparameter.views.view1″
name=“name“
restorable=“true“>
</view>
<view
class=“de.md.example.commandparameter.views.View2″
id=“de.md.example.commandparameter.views.view2″
name=“name“
restorable=“true“>
</view>

Die richtige Implementierung des Handlers ist jetzt denkbar einfach:

String param = (event.getParameter(„myViewID“));
try {
HandlerUtil.getActiveWorkbenchWindow(event).getActivePage().showView(param);
} catch (PartInitException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;

Führt man das ganze jetzt aus, sieht man das das wunderbar funktioniert.

views

Ok, das war der gemütliche Teil, jetzt wollen wir das ganze Mal mit beliebig komplexen Objekten probieren. Man könnte jetzt auf die Idee kommen, das dies im Prinzip genau gleich funktionieren müsste, wir haben hier nur ein Problem:

Map<String,String> params = new HashMap<String,String>();

Autsch!! Die Commands können von Haus aus erst mal nur mit Strings umgehen:-(

Wollte man hierfür also den gleichen Mechanismus verwenden, dann müsste man folgendermaßen vorgehen:

command

Man sieht in der Definition einer Command die beiden Elemente „values“ und „typeid“.

Was uns hier primär interessiert ist der Parameter „typeid“, hier kann man einen Parametertyp angeben, den man zuvor aber auch wieder deklarieren muss, zuvor aber implementieren wir uns das einfachse DomainObjekt, das man sich vorstellen kann, das wir dann als Parameter übergeben können.

public class MyDomainObject {

private String name;
public MyDomainObject(String name) {
this.name = name;
}

public String toString(){
return name;
}

So, jetzt können wir den CommandParameter deklarieren, und zwar so:

parametertype

Das ganze erwartet folgende Parameter:

typeconverter

Zunächst den type, was eben genau unser Domain-Objekt ist und weiterhin einen Converter vom Typ AbstractParameterValueConverter, und hier liegt genau der Hase im Pfeffer, da nämlich Commands nur mit Strings umgehen können, muss jeder Parameter zwingend in einen String konvertiert werden:-(… Keine Angst, das geht besser, der Vollständigkeithalber machen wir das jetzt mal, denn für unser einfachen DomainObject geht das noch relativ einfach, der Converter könne in etwa so aussehen:

public class MyDomainParameterConverter extends AbstractParameterValueConverter {

public MyDomainParameterConverter() {
// TODO Auto-generated constructor stub
}

@Override
public Object convertToObject(String parameterValue)
throws ParameterValueConversionException {
return new MyDomainObject(parameterValue);
}

@Override
public String convertToString(Object parameterValue)
throws ParameterValueConversionException {
return parameterValue.toString();
}

}

Das mag für einfache Objekte ja noch akzeptabel sein, für komplexe Objekthierarchien eher nicht…

Hat man diesen ParameterTypen deklariert, kann man diesen auch in der Definition des COmmandParameters auswählen:

commandids

Hierfür definieren wir uns mal einen zweiten Button, der folgende Implementierung hat:

button = new Button(container, SWT.PUSH);
button.setText(„PrintMyDomainObject“);
button.addSelectionListener(new SelectionAdapter(){
@Override
public void widgetSelected(SelectionEvent e) {
ICommandService cS = (ICommandService)getSite().getService(ICommandService.class);
IHandlerService hS = (IHandlerService)getSite().getService(IHandlerService.class);

Command openView = cS.getCommand(„de.md.example.commandparameter.openview“);

Map<String,Object> params = new HashMap<String,Object>();
params.put(„myDomain“, new MyDomainObject(text.getText()));

ParameterizedCommand pC = ParameterizedCommand.generateCommand(openView, params);
try {
hS.executeCommand(pC, null);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});

Um das Domain-Objekt im Handler zu extrahieren müssen wir die Konvertierung von Hand anstossen!!

String param = event.getParameter(„myDomain“);
try {

MyDomainObject mD = (MyDomainObject)event.getCommand().getParameterType(„myDomain“).getValueConverter().convertToObject(param);
System.out.println(mD);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return null;

Man holt sich also im Prinzip über das Command den CommandParameterType, über diesen den Converter, stößt die Konvertierung an und erhält hierdurch sein Objekt…. brrhhh, grausig.. Die Bundles für diesen aktuellen Stand findet ihr hier

Warum ist das aber so implementiert?? Die traurige Wahrheit ist, dieser Mechanismus ist nicht gedacht, hier beliebig komplexe Parameter zu übergeben, sondern dies ist eher dafür gedacht, in der Benutzeroberfläche für Menüs etc. Auswahlmöglichkeiten bereitzustellen, will man wirklich Parameter an Commands übergeben, gibt es hierfür bessere Mechanismen, und die schauen ich mir im nächsten Blogeintrag etwas genauer an. Bis dahin…

Veröffentlicht in cool eclipse | 4 Kommentare »

Wizard und IRunnableWithProgress

Verfasst von splitshade am November 17, 2008

Manchman ist es wirklich immer wieder so, das man coole Sachen entdeckt, und sich fragt, wie man bisher ohne ausgekommen ist.

Beispielsweise kann man ein „IRunnableWithProgress“ in einem Wizard so ausführen:

getContainer.run(true, false, new IRunnableWithProgress(){..});

So kann man synchrone Aktionen ausführen, und trotzdem einen Progressmonitor verwenden.

Sehr cool, hab ich gebraucht, um in der performFinish() Methode in einem Wizard eine langläufige Datenbank-Operation anzustossen, der Wizard durfe sich aber nur beenden, wenn diese erfolgreich ohne Transaktionsfehler durchlaufen wird.

Veröffentlicht in cool eclipse | Kommentar schreiben »