11.03.2014 Aufrufe

Skript zu Benutzungsoberflächen

Skript zu Benutzungsoberflächen

Skript zu Benutzungsoberflächen

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

Benut<strong>zu</strong>ngsoberflächen<br />

<strong>Skript</strong> V0.3 vom 11.10.2010<br />

Prof. Dr.-Ing. Holger Vogelsang<br />

SWT/JFace und RCP


Don’t<br />

panic


Inhaltsverzeichnis<br />

1 Einleitung 5<br />

1.1 Organisation der Vorlesung . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

1.2 Vorbereitungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2 Kassifikation von Anwendungen 7<br />

2.1 Einteilung nach Architekturen . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

2.2 Einteilung nach Zielplattformen . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

2.3 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

2.4 Architekturen von Fat-Client-Anwendungen . . . . . . . . . . . . . . . . . 10<br />

2.5 Interaktionseigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

3 SWT und JFace 14<br />

3.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />

3.2 Layout-Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

3.3 SWT-Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />

3.4 Menüs und Toolbar-Leisten . . . . . . . . . . . . . . . . . . . . . . . . . . 54<br />

3.5 Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

3.6 Ereignisbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

3.7 Model-View-Controller mit JFace . . . . . . . . . . . . . . . . . . . . . . . 79<br />

3.8 Weitere Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100<br />

4 Anbindung an die Geschäftslogik 101<br />

5 Deklarative Beschreibungen 102<br />

6 Eclipse Rich Client Platform 103<br />

7 Multithreading in Java 104<br />

7.1 Threads erzeugen und starten . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

7.2 Threads beenden und löschen . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

7.3 Zugriff auf gemeinsam genutzte Ressourcen . . . . . . . . . . . . . . . . 106<br />

7.4 Synchronisation von Threads . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

7.5 Threads als Daemons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108


Inhaltsverzeichnis<br />

8 Abbildungsverzeichnis 109<br />

9 Literaturverzeichnis 112<br />

10 Stichwortverzeichnis 114


1<br />

Einleitung<br />

Diese Veranstaltung behandelt grafische Benut<strong>zu</strong>ngsoberflächen auf Basis der Eclipse<br />

Rich Client Platform. Ergonomische Aspekte werden nicht betrachtet, da es <strong>zu</strong> diesem<br />

Thema eine eigenständige Vorlesung gibt.<br />

1.1 Organisation der Vorlesung<br />

Neben der Klausur kann auch eine Programmieraufgabe gelöst werden. Durch Abgabe<br />

einer korrekten Lösung erhalten Sie Bonuspunkte für die Klausur. Nähere Informationen<br />

sind auf der Webseite <strong>zu</strong>r Vorlesung im Ilias unter dem FAQ <strong>zu</strong> finden.<br />

1.2 Vorbereitungen<br />

1.2.1 Installation des JDK unter Windows<br />

Zur reinen Ausführung von Java-Programmen genügt das JRE (Java Runtime Environment).<br />

Darin fehlen aber beispielsweise wichtige Werkzeuge wie der Compiler. Hier in<br />

der Vorlesung sollen eigene Programme erstellt werden. Daher muss das JDK (Java<br />

Development Kit, manchmal auch SDK genannt) verwendet werden.<br />

1.2.2 Integrierte Entwicklungsumgebungen<br />

Die Anwendung des „nackten“ JDKs ist nicht sehr komfortabel. Daher bietet sich die Verwendung<br />

einer integrierten Entwicklungumgebung an. Da in der Vorlesung die Eclipse<br />

Rich Client Platform eingesetzt wird, bietet es sich an, Eclipse auch als IDE <strong>zu</strong> verwenden.<br />

Bitte wählen Sie auf den Pool-Rechnern als Projektverzeichnis ein Verzeichnis auf<br />

dem Netzlaufwerk. Zur lokalen Installation auf einem privaten Rechner laden Sie von<br />

http://www.eclipse.org/downloads die Version „Eclipse for RCP and RAP Developers“<br />

herunter. Sie beinhaltet bereits alle notwendigen Plugins für die Vorlesung. Die<br />

ZIP-Datei wird einfach in ein eigenes Verzeichnis extrahiert. Damit Sie später die Oberflächen<br />

nicht manuell erstellen müssen, bietet sich die Verwendung eines GUI-Editors an.<br />

Eclipse liefert mit dem VEP (Visual Editor Plugin) eine eigene Implementierung, die aber<br />

nicht immer im neuesten Eclipse funktionsfähig ist. Statt dessen kann für private Zwecke<br />

der Jigloo GUI-Editor (http://www.cloudgarden.com/jigloo/) oder noch besser


1.2 Vorbereitungen<br />

der WindowBuilder Pro (http://code.google.com/webtoolkit/tools/wbpro/<br />

index.html) von Google als Eclipse-Plugin bezogen werden. Auf den Seiten befinden<br />

sich auch die Installationsanleitungen.<br />

1.2.3 Eclipse Rich Client Platform<br />

Im Laufe der Vorlesung wird sich zeigen, dass die Eclipse Rich Client Platform am besten<br />

separat installiert wird. Da<strong>zu</strong> wird unterhttp://download.eclipse.org/eclipse/<br />

downloads/ die Eclipse-Version ausgewählt (am besten „Latest Release“) und auf der<br />

folgenden Seite das „Platform SDK“ für die verwendete Betriebssystem-Version. Bitte<br />

achten Sie darauf, dass inzwischen neben 32-Bit- auch 64-Bit-Betriebssysteme unterstützt<br />

werden. Wählen Sie Variante, die <strong>zu</strong> Ihrer installierten Java-Version passt. Die<br />

Plattform wird ebenso wie die IDE in ein Verzeichnis entpackt. Es darf sich dabei nicht<br />

um dasselbe Verzeichnis wie die IDE handeln.<br />

1.2.4 Aufbau dieses <strong>Skript</strong>es<br />

Das <strong>Skript</strong> beinhaltet eine Einführung in alle wichtigen Themen, die <strong>zu</strong>r Lösung der<br />

Aufgabe notwendig sind. Weiterhin betrachtet es viele Beispiele ausführlicher als die<br />

PowerPoint-Unterlagen der Vorlesung. So wird beispielsweise im <strong>Skript</strong> deutlich mehr<br />

Be<strong>zu</strong>g auf das verwendete API genommen.<br />

1. Einleitungskapitel: Dieses lesen Sie jetzt gerade.<br />

2. Klassifikation von Anwendungen: Wie unterscheiden sich Anwendungen, bezogen<br />

auf die Benut<strong>zu</strong>ngsoberfläche?<br />

3. Einführung in SWT: Hier finden Sie eine Einführung in SWT, die alle wichtigen Konzepte<br />

vorstellt. Parallel da<strong>zu</strong> sollten immer sowohl die JDK-Dokumentation als auch<br />

die Online-Hilfe aus Eclipse griffbereit sein.<br />

4. Einführung in JFace: JFace setzt auf SWT auf und bietet eine höhere Abstraktion<br />

von den einzelnen Fensterelementen. So wird beispielsweise das MVC-Muster unterstützt.<br />

5. Anbindung an die Geschäftslogik: Dieses Kapitel beschreibt die Trennung von Anwendungslogik<br />

und Benut<strong>zu</strong>ngsoberfläche.<br />

6. Deklarative Beschreibungen: Hier wird gezeigt, wie sich Oberflächen deklarativ mit<br />

XML-Dialekten beschrieben lassen.<br />

7. Rich Client Platform: Es wird die Idee des Frameworks vorgestellt.<br />

Am Ende des Dokumentes befindet sich in Kapitel 7 noch eine Einführung in das Multithreading<br />

unter Java. Abgeschlossen wird das <strong>Skript</strong> mit einem Abbildungs-, Literatursowie<br />

einem Stichwortverzeichnis.<br />

6


2<br />

Kassifikation von Anwendungen<br />

Anwendungen lassen sich sehr unterschiedlich in Kategorien einteilen. Da wir uns mit<br />

grafischen Benut<strong>zu</strong>ngsoberflächen befassen, erfolgt hier <strong>zu</strong>nächst die Einordnung anhand<br />

der Architekturmerkmale für die Oberflächenkonstruktuktion.<br />

2.1 Einteilung nach Architekturen<br />

In der Literatur sind die verwendeten Begriffe nicht so einheitlich <strong>zu</strong> finden, wie es die<br />

Aufstellung hier vermuten lässt.<br />

2.1.1 Thin Client<br />

Es handelt sich dabei beispielsweise um eine Ajax-Anwendung, die in einem Browser<br />

ausgeführt wird. Charakteristisch sind:<br />

Start über eine URL<br />

Eine lokale Installation auf dem Client ist nicht erforderlich, da ein halbwegs aktueller<br />

Browser immer vorhanden sein sollte.<br />

Die Geschäftslogik befindet sich überwiegend auf dem Server.<br />

Die Kommunikation erfolgt nach dem Request/Response-Prinzip. Somit kann nur<br />

der Client den Server kontaktieren. Inzwischen existieren auch neuere Ansätze (z.B.<br />

mit Apache Comet), die es erlauben, den Client auch vom Server aus <strong>zu</strong> kontaktieren.<br />

Browser<br />

(HTML, JavaScript)<br />

Server<br />

(Application-Server,<br />

Datenbank, …)<br />

Abbildung 2.1: Kommunikation bei einem Thin-Client


2.1 Einteilung nach Architekturen<br />

2.1.2 Rich Thin Client<br />

Die Anwendung läuft wie bei einem Thin Client im Browser ab. Allerdings wird hier nicht<br />

mehr auf die reinen Browser-Techniken gesetzt. Statt dessen führt ein Plugin im Browser<br />

die Anwendung aus. Dabei kann es sich beispielsweise um Java-Applets oder Flash-<br />

Programme handeln. So stehen wesentlich mächtigere Sprachmittel als bei reinen Thin<br />

Clients <strong>zu</strong>r Verfügung. Eigenschaften:<br />

Start über eine URL<br />

Eine lokale Installation auf dem Client ist nicht erforderlich. Das Plugin kann automatisch<br />

durch den Browser nachinstalliert werden<br />

Die Geschäftslogik befindet sich überwiegend auf dem Server.<br />

Die Kommunikation erfolgt auch hier in der Regel nach dem Request/Response-<br />

Prinzip.<br />

Browser<br />

(HTML, JavaScript,<br />

Flash, Applet)<br />

Server<br />

(Application-Server,<br />

Datenbank, …)<br />

Abbildung 2.2: Kommunikation bei einem Rich Thin Client<br />

2.1.3 Rich Fat Client<br />

Ein Rich Fat Client (oder auch kurz Rich Client) ist eine Anwendung, die großenteils auf<br />

dem Client abläuft. Sie basiert auf einer Plattform wie z.B. Eclipse RCP oder NetBeans<br />

Platform. Merkmale:<br />

Es ist eine lokale Installation auf dem Client erforderlich. Java bietet mit Java Web<br />

Start einen eleganten Mechanismus, die Java-Anwendung durch einen Browser<br />

nach<strong>zu</strong>installieren. Damit ist nur noch die manuelle Einrichtung eines JRE auf dem<br />

Client erforderlich.<br />

Die Plattform kann <strong>zu</strong>sätzliche Module vom Server nachladen. Weiterhin bieten viele<br />

Plattformen auch einen automatischen Update-Mechanismus, indem sie in bestimmten<br />

Abständen den Server nach neuen Modulversionen befragen und diese<br />

herunterladen.<br />

Die Geschäftslogik kann sich auf Client und Server befinden.<br />

Die Kommunikation zwischen Client und Server findet entweder nach dem Request/-<br />

Response-Prinzip oder aber wirklich bidirektional statt.<br />

8


2.2 Einteilung nach Zielplattformen<br />

Client<br />

(Eclipse RCP, …)<br />

Server<br />

(Application-Server,<br />

Datenbank, …)<br />

Abbildung 2.3: Kommunikation bei einem Rich Fat Client<br />

2.1.4 Smart Client<br />

Der Begriff des Smart Clients ist häufig im Microsoft-Umfeld <strong>zu</strong> finden. Er bezieht sich fast<br />

immer auf Anwendungen, die auf .NET, ASP.NET, XAML usw. basieren. Eigenschaften:<br />

Der Start der Anwendung erfolgt aus dem Browser heraus über eine URL. Somit ist<br />

keine lokale Installation erforderlich.<br />

Die Geschäftslogik kann sich auf Client und Server befinden.<br />

2.1.5 Fat Client (plattformunabhängig)<br />

Hierbei handelt es sich um eine „klassische“ Desktop-Anwendung. Sie wird beispielsweise<br />

in Java mit Swing oder SWT bzw. in C++ mit QT usw. erstellt. Wichtig ist also,<br />

dass sowohl die verwendeten Bibliotheken als auch der eigentliche Quelltext plattformunabhängig<br />

sind. Damit ist die Anwendung auf binärer Ebene zwar nicht direkt auf allen<br />

unterstützten Plattformen ausführbar, aus dem Quelltext lässt sich aber durch einfaches<br />

Übersetzen auf einer anderen Zielplattform ein lauffähiges Programm erzeugen. Charakteristisch<br />

sind:<br />

Der Start erfolgt lokal auf dem Client. Da<strong>zu</strong> ist auch eine lokale Installation erforderlich.<br />

Die Geschäftslogik ist überwiegend auf dem Client, teilweise aber auch auf dem<br />

Server vorhanden.<br />

2.1.6 Fat Client (plattformabhängig)<br />

Prinzipiell gelten dieselben Aussagen wie für den plattformunabhängigen Fat Client. Als<br />

Besonderheit kommt aber hin<strong>zu</strong>, dass plattformabhängige Bibliotheken verwendet werden.<br />

So könnte die Benut<strong>zu</strong>ngsoberfläche mit Hilfe der MFC-Klassen von Microsoft erstellt<br />

worden sein, für die es keine entsprechende Implementierung auf anderen Systemen<br />

gibt.<br />

2.2 Einteilung nach Zielplattformen<br />

Neben den klassischen Desktop-PCs und Laptops gibt es eine Reihe weiterer Hardwareplattformen,<br />

die eine Anwendung unterstützen kann.<br />

Mobile Geräte: Diese besitzen unterschiedliche Betriebssysteme wie Windows CE/-<br />

Mobile, Symbian OS, Google Android, iOS usw.<br />

9


2.4 Architekturen von Fat-Client-Anwendungen<br />

Unterschiedliche Leistungen: Nicht alle Geräte haben genügend Speicher oder ausreichend<br />

leistungsfähige Prozessoren. Auch die Größen der Displays können sich<br />

signifikant unterscheiden: Normale mobile Telefone, Smartphones, PDAs, Tablet-<br />

PCs, usw.<br />

Unterschiedliche Programmierumgebungen: Es gibt nicht auf allen Geräten einheitliche<br />

Entwicklungssprachen und -umgebungen: Java ME, C++, C#, usw.<br />

2.3 Übersicht<br />

Die folgende Tabelle stellt die Vor- und Nachteile der verschiedenen Architekturen einander<br />

gegenüber. Die Einteilung ist nicht vollständig.<br />

Tabelle 2.1: Architekturvergleich<br />

Thin<br />

Client<br />

(Ajax)<br />

Rich Thin<br />

Client<br />

(Browser<br />

+ Plugin)<br />

Rich Fat<br />

Client<br />

(Java)<br />

Microsoft<br />

Smart<br />

Client<br />

Java Fat<br />

Client<br />

Windows<br />

oder<br />

LinuX Fat<br />

Client<br />

Installation ++ ++ / − 1 −− − −− −<br />

Administration ++ ++ / − 2 − − − −<br />

Wartung (SW) − ++ ++ ++ ++ ++<br />

Interaktion − ++ ++ ++ ++ ++<br />

Performance − − ++ ++ ++ ++<br />

GUI-Konsistenz − − ++ ++ ++ ++<br />

Plattformunabh. ++ + ++ −− ++ −−<br />

Arbeit bei<br />

Serverausfall<br />

− − ++ / −− 3 − ++ / −− 3 ++ / −− 3<br />

Applikationslogik Server Server Client und<br />

Server<br />

Client und<br />

Server<br />

überwiegend<br />

auf<br />

dem Client<br />

überwiegend<br />

auf<br />

dem Client<br />

2.4 Architekturen von Fat-Client-Anwendungen<br />

Eine Fat-Client-Anwendung besteht typischerweise aus mehreren Schichten, von denen<br />

hier nur einige betrachtet werden sollen:<br />

Hardware: Diese Schicht beinhaltet die eigentliche Ein- und Ausgabehardware, über<br />

die mit der Anwendung kommuniziert wird.<br />

Betriebssystem: Das Betriebssystem greift über Gerätetreiber auf die Hardware <strong>zu</strong><br />

und abtrahiert gleichzeitig von ihr.<br />

Das Fenstersystem wird häufig als Bestandteil des Betriebssystems angesehen.<br />

Es nutzt die Abstraktionen von der Hardware, um den Anwendungen Fenster <strong>zu</strong>r<br />

Darstellung von Inhalten sowie Interaktionen mit den Fenstern an<strong>zu</strong>bieten.<br />

1 Eventuell notwendige Installation einer Laufzeitumgebung<br />

2 Eventuell notwendige Einrichtung einer Laufzeitumgebung<br />

3 Hängt von der Konzeption ab<br />

10


2.4 Architekturen von Fat-Client-Anwendungen<br />

Benut<strong>zu</strong>ngsschnittstelle (Struktur): Sie ist für die Darstellung und Verwaltung des<br />

Inhalts in den Fenstern verantwortlich. Sie übernimmt auch die Anordnung der einzelnen<br />

Fensterelemente (z.B. Widgets) untereinander.<br />

Dialogsteuerung, Ein- und Ausgabe von Daten: Diese Schicht kontrolliert die Aktionen<br />

des Benutzers in Be<strong>zu</strong>g auf deren Plausibilität. Sie prüft beispielsweise, ob<br />

gewisse Eingaben sinnvoll und korrekt sind. Weiterhin steuert sie die Benut<strong>zu</strong>ngsschnittstelle<br />

und die Geschäftslogik in der übergeordneten Schicht, indem sie auch<br />

die Daten in der Oberfläche und Geschäftslogik synchronisiert und Ereignisse weitermeldet.<br />

Anwendungskern (Geschäftslogik): Hier befindet sich die eigentliche Funktionalität<br />

der Anwendung. Sie wird teilweise von der Dialogsteuerung aufgerufen, wenn<br />

Ereignisse eingetreten sind.<br />

Anwendungskern<br />

(Geschäftslogik)<br />

Dialogsteuerung,<br />

Ein-/Ausgabe<br />

von Daten<br />

Benut<strong>zu</strong>ngsschnittstelle<br />

(Struktur)<br />

Vorlesungsinhalt<br />

Fenstersystem<br />

(Präsentation)<br />

Betriebssystem<br />

Tastaturtreiber<br />

Maustreiber<br />

Grafiktreiber<br />

Hardware<br />

Abbildung 2.4: Architektur einer Fat-Client-Anwendung<br />

Die Vorlesung beschäftigt sich im Wesentlichen mit der Dialogsteuerung sowie der Struktur<br />

der Benut<strong>zu</strong>ngsschnittstelle. Zusätzlich wird mit der Eclipse Rich Client Platform ein<br />

Framework betrachtet, das ein Gerüst für eine Fat-Client-Anwendung vorgibt.<br />

11


2.5 Interaktionseigenschaften<br />

Das folgende Beispiel soll die einzelnen Schichten verdeutlichen:<br />

Struktur<br />

(Widgets und<br />

deren Anordnung)<br />

Geschäftslogik<br />

(Datum kleiner als<br />

aktuelles Datum)<br />

Ein- und Ausgabe<br />

(Daten aus dem<br />

Dialog)<br />

Präsentation<br />

(Farben,<br />

Zeichensätze, ...)<br />

Dialogsteuerung<br />

(entsperrt, wenn die<br />

Eingaben in Ordnung sind,<br />

löst Aktion aus)<br />

public class Customer {<br />

private String firstName;<br />

private String lastName;<br />

private int<br />

customerNumber;<br />

private boolean retired;<br />

private FamilyStatus familyStatus;<br />

private Date<br />

customerSince;<br />

// ...<br />

}<br />

Abbildung 2.5: Architekturschichten anhand eines Beispiels<br />

Durch die Struktur wird beschrieben, wie die einzelnen Widgets untereinander platziert<br />

sind und wie sich sich bei einer Größenänderung des Dialogs verhalten sollen.<br />

Das Aussehen der Widgets und der Fenster wird von der Präsentationsschicht des<br />

Fenstersystems übernommen. So kann ein einheitliches Erscheinungsbild aller Anwendungen<br />

in einem Fenstersystem erzielt werden. Die Eclipse Rich Client Platform<br />

<strong>zu</strong>sammen mit SWT und JFace erlaubt, wie andere Toolkits auch, das Aussehen und<br />

Verhalten in gewissen Grenzen <strong>zu</strong> ändern.<br />

Die Dialogsteuerung kann beispielsweise die OK-Taste erst dann freigeben, wenn<br />

alle Eingaben in Ordung sind. Weiterhin löst sie durch einen Druck auf die Taste<br />

Aktionen aus.<br />

Die Dialogsteuerung kann nicht immer alle Eingaben selbst validieren, weil ihr da<strong>zu</strong><br />

häufig Informationen fehlen, die nur in der Geschäftslogik <strong>zu</strong> finden sind. In dem<br />

Beispiel muss das eingegebene Datum bestimmte Bedingungen erfüllen, die von<br />

der Geschäftslogik festgelegt sind.<br />

Die im Dialog dargestellten oder veränderten Daten müssen der Geschäftslogik <strong>zu</strong>r<br />

Verfügung gestellt werden bzw. stammen von ihr. Diese uni- oder bidirektionale Kommunikation<br />

wird von der Ein- und Ausgabeschicht übernommen. Im diesem Beispiel<br />

beinhaltet eine Java-Klasse die Daten des Dialogs.<br />

2.5 Interaktionseigenschaften<br />

Ein Benutzer kann mit der Anwendung auf vielfältige Varianten kommunizieren, wenn<br />

diese das unterstützt:<br />

1. Menü-Auswahl: Steuerung der Anwendung erfolgt durch Menü-Kommandos.<br />

12


2.5 Interaktionseigenschaften<br />

2. Formular-basiert: Die Daten werden in Formulare eingegeben. Diese Variante ist<br />

besonders gut für einfach strukturierte Daten geeignet.<br />

3. Direkte Manipulation: Die Bearbeitung von Objekten erfolgt auf einer Arbeitsfläche<br />

durch Drag-und-Drop, kontextsensitive Menüs usw. Beispiele sind Drag-und<br />

Drop für Dateioperationen, UML-Editoren und Zeichenprogramme. Dafür gibt es bereits<br />

eine ganze Anzahl von Java-Bibliotheken wie Naked Objects http://www.<br />

nakedobjects.org/ und Eclipse GEFhttp://www.eclipse.org/gef/.<br />

4. Sprachgesteuerte Interaktionen (natürliche Sprache, anwendungsspezifische Sprache):<br />

Da<strong>zu</strong> gehören Anweisungen auf der Kommandozeile, in Anwendungen eingebettete<br />

<strong>Skript</strong>sprachen sowie die Erkennung gesprochener Befehle.<br />

In dieser Vorlesung spielen nur die Punkte 1 und 2 sowie etwas von Punkt 3 und 4 eine<br />

Rolle.<br />

13


3<br />

SWT und JFace<br />

Dieses Kapitel beschäftigt sich mit dem Standard Widget Toolkit („SWT“) sowie dem darauf<br />

aufsetzenden JFace, die beide <strong>zu</strong>r Gestaltung der Benut<strong>zu</strong>ngsoberflächen verwendet<br />

werden.<br />

Anwendungskern<br />

(Geschäftslogik)<br />

Dialogsteuerung,<br />

Ein-/Ausgabe<br />

von Daten<br />

Benut<strong>zu</strong>ngsschnittstelle<br />

(Struktur)<br />

Vorlesungsinhalt<br />

Fenstersystem<br />

(Präsentation)<br />

Betriebssystem<br />

Tastaturtreiber<br />

Maustreiber<br />

Grafiktreiber<br />

Hardware<br />

Abbildung 3.1: Struktur der Oberfläche<br />

Warum werden hier aber gerade SWT und JFace verwendet? Es gibt ja auch Swing,<br />

das plattforumabhängig und im JDK schon enthalten ist, QT, Windows-Forms, und viele<br />

andere Toolkits. Zur Beantwortung der Frage ist <strong>zu</strong>nächst ein Blick auf die Eigenschaften<br />

von SWT und JFace erforderlich:<br />

SWT wurde als Basis für die Entwicklung von Eclipse geschaffen.


3.1 Grundlagen<br />

SWT verwendet denselben Ansatz wie das „Abstract Window Toolkit“ (AWT). Dabei<br />

werden native Dialogelemente des Fenstersystems verwendet, soweit diese denn<br />

vorhanden sind. Ansonsten zeichnet SWT sie selbst. Somit gibt es innerhalb von<br />

SWT eine Bibliothek, die plattformabhängig ist. Die Programmierschnittstelle selbst<br />

ist plattformunabhängig.<br />

JFace bildet eine Abstraktionsschicht von SWT, indem es für viele Elemente den<br />

MVC-Ansatz anbietet und teilweise die Ereignisbehandlung vereinfacht. Darüber<br />

hinaus besitzt es eine Registry für Zeichensätze, Farben und Bilder sowie vordefinierte<br />

Dialoge und „Wizards“.<br />

Die folgende Aufstellung fasst die Vor- und Nachteile von SWT und JFace <strong>zu</strong>sammen.<br />

+ Native Dialogelemente sind häufig schneller als selbst gezeichnete. Sie passen sich<br />

auch der Darstellung des Fenstersystems an. Somit sieht eine SWT-Anwendung<br />

genauso aus wie jede andere Anwendung des Fenstersystems.<br />

+ SWT hat innerhalb der Eclipse Rich Client Platform inzwischen einen Ein<strong>zu</strong>g in die<br />

Industrie gehalten.<br />

+ Viele allgemeine Konzepte für Benut<strong>zu</strong>ngsoberflächen lassen sich anhand von SWT<br />

und JFace beispielhaft aufzeigen.<br />

+ Es gibt hervorragende Entwicklungsumgebungen und Modellierungswerkzeuge, die<br />

teilweise kostenlos verfügbar sind.<br />

+ Mit Hilfe der Rich Ajax Platform („RAP“) lassen sich aus bestehenden Fat-Clients<br />

relativ einfach Web-Anwendungen (Rich Thin Clients) erzeugen.<br />

+ Es sind viele Erweiterungen verfügbar (z.B. GEF für grafische Editoren, . . . ).<br />

- Die Programmierschnittstelle des SWT ist teilweise nicht sehr objekt-orientiert ausgelegt.<br />

Insbesondere die häufige Verwendung von Konstanten statt <strong>zu</strong>sätzlicher<br />

Klassen ist störend.<br />

- Es sind nicht so viele mächtige Dialogelemente wie in Swing vorhanden.<br />

- Teilweise ist keine Garbage-Collection möglich. Als Folge daraus müssen Betriebssystemressourcen<br />

manuell freigegeben werden.<br />

- Es werden keine Applets unterstützt. Das ist kein großer Mangel, da Applets in letzter<br />

Zeit immer seltener eingesetzt werden.<br />

Da SWT plattformabhängig ist, werden nicht automatisch alle Java-Plattformen unterstützt.<br />

Allerdings wurde SWT auf praktisch alle relevanten Fenstersysteme portiert:<br />

Windows 32 und 64 Bit: XP, Vista, 7<br />

Linux 32 und 64 Bit: x86 (GTK), x86 (Motif), PPC (GTK), S390 (GTK)<br />

Solaris: x86 (GTK), Sparc (GTK)<br />

AIX: PPC (Motif)<br />

HP-UX: ia64 (Motif)<br />

Mac OS X: Carbon, Cocoa<br />

Verschiedene eingebettete Geräte (im Rahmen von eRCP)<br />

Anmerkung: Sie finden die Quelltexte aller Beispiele als Download auf der Homepage. In<br />

jedem Beispiel ist der Name der Quelltextdatei im Archiv angegeben.<br />

15


3.1 Grundlagen<br />

3.1 Grundlagen<br />

Dieser Abschnitt beschreibt die SWT-Grundlagen. Da<strong>zu</strong> gehören die Platzierung von<br />

GUI-Elementen („Widgets“) durch so genannte Layout-Manager sowie die allgemeine<br />

Verwendung der Widgets.<br />

3.1.1 Die erste SWT-Anwendung<br />

Um einen ersten Eindruck vom Aufbau einer reinen SWT-Anwendung <strong>zu</strong> vermitteln, zeigt<br />

das folgende Beispiel ein kleines Programm, das nur einen Text in einer Taste darstellt<br />

(Quelltextdatei FirstSWTApplication.java). Die Seitenzahlen dienen nur der späteren<br />

Erklärung des Quelltextes.<br />

1 public class FirstSWTApplication {<br />

2<br />

3 private Display display;<br />

4 private Shell shell;<br />

5<br />

6 public FirstSWTApplication(Display display) {<br />

7 this.display = display;<br />

8 shell = new Shell(display);<br />

9 shell.setText("Titeltext");<br />

10 shell.setSize(200, 100);<br />

11 }<br />

12<br />

13 private void createGUI() {<br />

14 Button button = new Button(shell, SWT.PUSH);<br />

15 button.setText("Das ist ein Text");<br />

16 button.setBounds(shell.getClientArea());<br />

17 shell.pack();<br />

18 }<br />

19<br />

20 public void run() {<br />

21 createGUI();<br />

22 shell.open();<br />

23 while (!shell.isDisposed()) {<br />

24 if (!display.readAndDispatch())<br />

25 display.sleep();<br />

26 }<br />

27 display.dispose();<br />

28 }<br />

29<br />

30 public static void main(String[] args) {<br />

31 new FirstSWTApplication(Display.getDefault()).run();<br />

32 }<br />

33 }<br />

Damit ergeben sich die folgenden Bildschirmausgaben:<br />

Abbildung 3.2: Beispiel unter Windows 7, MacOS X und Linux (GTK)<br />

16


3.1 Grundlagen<br />

Erste Erklärungen <strong>zu</strong>m Quelltext:<br />

Zeile 3: Das Display-Objekt repräsentiert das Fenstersystem, in dem das Programm<br />

läuft.<br />

Zeile 4: EineShell ist ein „Top-Level“-Fenster des Fenstersystems. Später werden<br />

auch andere Elemente wie Dialoge vorgestellt.<br />

Zeil 14: Mit new Button wird eine Taste in das Fenster eingefügt. Wie die genaue<br />

Positionierung erfolgt, wird später erläutert. Wichtig <strong>zu</strong> wissen ist an dieser<br />

Stelle nur, dass keine Positionen angegeben werden, weil ein sogenannter Layout-<br />

Manager die Platzierung übernimmt. An diesem Widget ist auch erkennbar, das<br />

Widgets in SWT immer erzeugt werden, indem deren Vaterelement als Parameter<br />

übergeben wird. In diesem Beispiel handelt es sich beim Vaterelement um das<br />

Fenster selbst (Parametershell).<br />

Zeile 14: Durch Konstenten wieSWT.PUSH werden sehr häufig Eigenschaften eines<br />

Widgets bestimmt. Diese Taste ist eine normale Taste, die nach dem Drücken sofort<br />

wieder in ihren Ausgangs<strong>zu</strong>stand <strong>zu</strong>rückkehrt.<br />

Zeile 15:button.setText trägt die Beschriftung der Taste ein.<br />

Zeile 16: button.setBounds legt fest, dass die Taste so groß wie der Inhaltsbereich<br />

des Fensters werden soll.<br />

Zeile 17: Mitshell.pack wird die Layout-Berechnung gestarten. Die Fenstergröße<br />

ermittelt sich aus der Größen und Positionen der Widgets innerhalb des Fensters.<br />

Wie das genau funktioniert, wird später erläutert.<br />

Zeile 9:shell.setText trägt die Titelzeile des Fensters ein.<br />

Zeile 10: shell.setSize setzt manuell eine Fenstergröße. Normalerweise wird<br />

diese aus dem Inhalt des Fenster berechnet.<br />

Die Zeilen 23 bis 27 schließlich sehen <strong>zu</strong>nächst einmal einfach nur merkwürdig aus.<br />

Sie beinhalten die Ereignisschleife. Solange das Fenster nach dem Öffnen nicht<br />

geschlossen ist, werden Ereignisse <strong>zu</strong>gestellt. Danach wartet die Anwendung auf<br />

weitere Ereignisse (Zeile 25). Nach dem Schließen des Fenster beendet sich die<br />

Schleife und alle Ressourcen der Anwendung werden durchdisplay.dispose()<br />

freigegeben. Eine genauere Betrachtung der manuellen Freigabe erfolgt in Abschnitt<br />

3.1.4.<br />

Eine SWT-Anwendung hat generell diese Schichtenstruktur:<br />

Widget<br />

Widget<br />

Shell-Klasse<br />

Widget<br />

Display-Klasse<br />

Betriebssystemspezifische<br />

Klassen<br />

Betriebssystem<br />

Abbildung 3.3: Struktur einer SWT-Anwendung<br />

17


3.1 Grundlagen<br />

3.1.1.1 Erforderliche Dateien<br />

Um das Beispiel übersetzen und ausführen <strong>zu</strong> können, ist eine jar-Datei erforderlich.<br />

Sie finden diese entweder direkt im Verzeichnis plugins Ihrer Eclipse-Installation oder<br />

im selben Verzeichnis Ihrer Eclipse-Zielplattform.<br />

Mitorg.eclipse.swt.Windowsystem.OS.Arch_Version.jar ist vorerst nur eine<br />

Datei erforderlich. Dabei stehen die Abkür<strong>zu</strong>ngen für:<br />

Windowsystem: Name des Fenstersystems<br />

OS: Name des Betriebssystems<br />

Arch: Name der Prozessorarchitektur<br />

Version: Versionsnummer von SWT<br />

Die genauen Bezeichnungen für Eclipse 3.5 sehen auf den unterschiedlichen Plattformen<br />

so aus:<br />

org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar für Windows 7, 32<br />

Bit Intel-Architektur, SWT 3.5<br />

org.eclipse.swt.gtk.linux.x86_3.5.2.v3557f.jar für Linux, GTK, 32<br />

Bit Intel-Architektur, SWT 3.5<br />

org.eclipse.swt.cocoa.macosx.x86_64_3.5.2.v3557f.jar für Mac OS<br />

X, Cocoa, 64 Bit Intel-Architektur, SWT 3.5<br />

Die wichtigsten darin enthaltenen Pakete, die für die ersten Beispiele benötigt werden,<br />

sindorg.eclipse.swt undorg.eclipse.swt.widget. Später werden noch weiterejar-Dateien<br />

und Pakete hin<strong>zu</strong>kommen.<br />

3.1.1.2 Installation der jar-Dateien<br />

Die <strong>zu</strong>r Überset<strong>zu</strong>ng und Ausführung verwendeten jar-Dateien lassen sich auf mehreren<br />

Wegen in das eigene Projekt einbinden. In Be<strong>zu</strong>g auf die spätere Vorstellung der<br />

Eclipse Rich Client Platform ist es aber am sinnvollsten, die Dateien nicht direkt in das<br />

eigene Projekt <strong>zu</strong> kopieren. Es gibt mehrere Möglichkeiten:<br />

In der Installation der Eclipse-IDE sind allejar-Dateien vorhanden. Diese lässt sich<br />

als Plattform, gegen die entwickelt wird, eintragen. Das hat aber den großen Nachteil,<br />

dass die eigene Anwendung von der Version der IDE abhängig ist. Weiterhin<br />

sind in der IDE jar-Dateien vorhanden, die hauptsächlich für die IDE erforderlich<br />

sind. Diese Dateien könnten versehentlich auch in eigenen Projekten verwendet<br />

werden.<br />

Besser ist es, das RCP-SDK von http://download.eclipse.org/eclipse/<br />

downloads/ herunter<strong>zu</strong>laden und in ein separates Verzeichnis aus<strong>zu</strong>packen. Danach<br />

wird das SDK als Zielplattform eingetragen.<br />

Statt also die Dateien direkt in das eigene Projekt <strong>zu</strong> kopieren, ist es sinnvoller, mit einer<br />

sogenannten Zielplattform <strong>zu</strong> arbeiten. In dieser sind allejar-Dateien vorhanden, die in<br />

den eigenen Projekten Verwendung finden sollen. Die Zielplatform kann in den globalen<br />

Eigenschaften der IDE eingestellt werden, wie Abbildung 3.4 zeigt.<br />

18


3.1 Grundlagen<br />

Die Ziel-Plattform:<br />

Abbildung 3.4: Zielplattform einstellen<br />

Mit „Add...“ lassen sich neue Zielplattformen wie z.B. das entpackte RCP-SDK hin<strong>zu</strong>fügen<br />

(Abbildung 3.5).<br />

Abbildung 3.5: Zielplattform einrichten<br />

Eine der Plattformen wird schließlich als aktiv markiert, indem der Haken vor deren<br />

Namen gesetzt wird. Jetzt können Abhängigkeiten <strong>zu</strong> einzelnen jar-Dateien der Zielplattform<br />

im eigenen Projekt eingestellt werden. Da<strong>zu</strong> dient die vordefinierte Variable<br />

ECLIPSE_HOME, die auf die aktuelle Zielplattform verweist. Abhängigkeiten sollten immer<br />

relativ <strong>zu</strong> dieser Variablen angegeben werden, damit die Plattform austauschbar<br />

bleibt (Abbildung 3.6). Eclipse hat manchmal Probleme, nach einem Wechsel der Zielplattform<br />

die Paket- und Klassennamen in den Projekten korrekt auf<strong>zu</strong>lösen. Ein Neustart<br />

der IDE bewirkt hier Wunder.<br />

19


3.1 Grundlagen<br />

über Variable<br />

einbinden<br />

Abbildung 3.6: Abhängigkeit von Dateien der Zielplattform<br />

3.1.2 Die erste JFace-Anwendung<br />

Wie bereits erwähnt, ist JFace eine Klassenbibliothek, die auf SWT aufsetzt und bestimmte<br />

Interaktionen vereinfacht und Abstraktion wie z.B. den MVC-Ansatz bietet. Auf<br />

die direkte Verwendung von SWT-Klassen kann dennoch nicht verzichtet werden.<br />

Der folgende Quelltext zeigt eine Umset<strong>zu</strong>ng des ersten SWT-Beispiels aus Abschnitt<br />

3.1.1 mit den Mitteln von JFace (QuelltextdateiFirstJFaceApplication.java). Die<br />

Seitenzahlen dienen auch hier nur der späteren Erklärung des Quelltextes.<br />

1 public class FirstJFaceApplication<br />

2 extends ApplicationWindow {<br />

3<br />

4 public FirstJFaceApplication() {<br />

5 super(null); // kein Vater-Fenster<br />

6 }<br />

7<br />

8 // Erzeugen der Oberfläche<br />

9 @Override<br />

10 protected Control createContents(Composite parent) {<br />

11 Button button = new Button(parent, SWT.PUSH);<br />

12 button.setText("Das ist ein Text");<br />

13 button.setBounds(parent.getClientArea());<br />

14 parent.pack();<br />

15 return parent;<br />

16 }<br />

17<br />

18 public void run() {<br />

19 setBlockOnOpen(true);<br />

20 open();<br />

21 Display.getDefault().dispose();<br />

22 }<br />

23<br />

24 public static void main(String[] args) {<br />

25 new FirstJFaceApplication().run();<br />

26 }<br />

27 }<br />

Schon auf den ersten Blick ist erkennbar, dass das Grundgerüst der Anwendung anders<br />

ausfällt und die Ereignisschleife nicht mehr direkt sichtbar ist. Erklärungen:<br />

Zeile 10: Die Methode createContents wird intern automatisch von der Basisklasse<br />

ApplicationWindow aufgerufen, um die Oberfläche <strong>zu</strong> erzeugen. Als Er-<br />

20


3.1 Grundlagen<br />

gebnis liefert sie die Vater-Komponente <strong>zu</strong>rück, die alle anderen Komponenten beinhaltet.<br />

Es gibt weitere Methoden mit dem NamensaufbaucreateXYZ, die auch automatisch<br />

aufgerufen werden. Sie dienen beispielsweise der Erzeugung von Menüs.<br />

Zeile 19: setBlockOnOpen: Mit dem Parameter true kehrt der Aufruf der open-<br />

Methode erst nach dem Schließen des Fensters <strong>zu</strong>rück.<br />

Zeile 20: open: Das Fenster wird angezeigt. Der Aufruf kehrt in diesem Beispiel<br />

erst nach dem Schließen des Fensters <strong>zu</strong>rück. Hier verbirgt sich dann auch die<br />

Ereignisschleife.<br />

Zeile 21: dispose: Alle Ressourcen des Fensters sowie die Ressourcen seiner<br />

Kindelemente werden wieder freigeben. Unterbleibt der Aufruf, dann kann Java manche<br />

Objekt nicht per Garbage-Collector freigeben. Weiterhin kann es passieren,<br />

dass dem Fenstersystem irgendwann die Ressourcen ausgehen. Die Freigabeproblematik<br />

wird gleich noch genauer besprochen.<br />

Eine JFace-Anwendung hat generell diese Schichtenstruktur:<br />

Widget<br />

Widget<br />

Widget<br />

Composite-Klasse<br />

ApplicationWindow-Klasse<br />

Shell<br />

Display-Klasse<br />

von der ApplicationWindow-<br />

Klasse erzeugt<br />

Betriebssystemspezifische<br />

Klassen<br />

Betriebssystem<br />

Abbildung 3.7: Struktur einer JFace-Anwendung<br />

Im Unterschied <strong>zu</strong>m reinen SWT wird hier durch die BasisklasseApplicationWindow<br />

eine Struktur der Anwendung vorgegeben.<br />

3.1.2.1 Erforderliche Dateien<br />

Zur Überset<strong>zu</strong>ng und Ausführung sind einige plattformunabhängige jar-Dateien von<br />

JFace erforderlich. Auch diese sollten aus der Zielplattform stammen:<br />

org.eclipse.jface_Version.jar: Allgemeine JFace-Klassen<br />

org.eclipse.jface.text_Version.jar: Klassen <strong>zu</strong>r Behandlung von Textdokumenten<br />

org.eclipse.equinox.common_Version.jar: Klassen für das <strong>zu</strong>grunde liegende<br />

OSGi-Framework. OSGi wird später im Rahmen der Rich Client Platform<br />

eingeführt.<br />

org.eclipse.core.commands_Version.jar: Klassen, die hauptsächlich von<br />

anderen JFace-Klassen verwendet werden<br />

21


3.1 Grundlagen<br />

org.eclipse.core.runtime_Version.jar: Klassen für die Eclipse-Laufzeitumgebung.<br />

Auch diese wird mit der Rich Client Platform später betrachtet.<br />

org.eclipse.osgi_Version.jar: Weitere Klassen für das OSGi-Framework<br />

Zusätzlich sind für das Text-Beispiel später diese beidenjar-Dateien notwendig, die nicht<br />

Bestandteil des RCP-SDK sind. Sie können aus der Installation der Eclipse-IDE kopiert<br />

werden.<br />

org.eclipse.text_Version.jar<br />

com.ibm.icu.text_Version.jar<br />

Diese Dateien sollten auch wirklich nur dann kopiert werden, wenn deren Funktionalität<br />

erforderlich ist. Sie werden hier im <strong>Skript</strong> eingesetzt, um einen „mächtigen“ Texteditor mit<br />

Syntaxhervorhebung <strong>zu</strong> bauen. Die Dateien enthalten Teile des Editors aus der IDE.<br />

3.1.3 Lauffähige Anwendung<br />

Obwohl SWT eine plattformabhängige Bibliothek benötigt, lässt sich aus einem solchen<br />

Projekt eine eigenständig lauffähige Anwendung erzeugen. Da<strong>zu</strong> muss das Eclipse-<br />

Projekt für Windows und LinuX lediglich als lauffähige jar-Anwendung mit allen Bibliotheken<br />

und Ressourcen exportiert werden. Für MacOS X ist es etwas komplizierter.<br />

Unter der URL http://www.eclipse.org/swt/macosx/ ist eine schrittweise Beschreibung<br />

vorhanden.<br />

3.1.4 Freigabe von Ressourcen<br />

Im Zusammenhang mit SWT tritt ein Problem auf, mit dem in Java so normalerweise<br />

nicht gerechnet wird. Werden Ressourcen wie Objekte mit new auf dem Heap angelegt,<br />

dann kann der Garbage-Collector diese wieder freigeben, sobald keine Referenz<br />

mehr darauf verweist. 1 Das funktioniert bei Anwendungen mit SWT so nicht immer. Der<br />

Grund ist darin <strong>zu</strong> sehen, dass SWT Ressourcen des Fenstersystems verwendet, um<br />

seine Widgets dar<strong>zu</strong>stellen. Zu diesen Ressourcen gehören beispielsweise Fenster, Farben,<br />

Zeichensätze und Tasten. Java-Objekte, die solche Ressourcen kapseln, können<br />

vom Garbage-Collector nicht automatisch wieder freigeben werden. Dieses muss manuell<br />

durch den Aufruf der dispose-Methode des Objektes erfolgen. Nach diesem Aufruf<br />

darf weder lesend noch schreibend auf das Objekt <strong>zu</strong>gegriffen werden. Sollte das doch<br />

geschehen, dann quittiert SWT das mit dem Auslösen einer Ausnahme. Wie sieht das in<br />

der Praxis aus? Es gibt zwei einfache Regeln, die das Verhalten beschreiben.<br />

Regel 1: Wer ein Element erzeugt, gibt es auch mitdispose() wieder frei. Beispiel<br />

für die Erzeugung eines neuen Farb-Objektes:<br />

Color color = new Color(display, 0, 0, 255);<br />

...<br />

color.dispose();<br />

Wird dagegen ein existierendes, vordefiniertes Element verwendet, dann darf es<br />

nicht freigeben werden. Beispiel für die Verwendung eines von SWT erzeugten Farb-<br />

Objektes:<br />

1 Ok, das ist etwas vereinfacht ausgedrückt.<br />

22


3.1 Grundlagen<br />

Color color = display.getSystemColor(SWT.COLOR_RED);<br />

Regel 2: Viele Widgets und andere Ressourcen werden hierarchisch geschachtelt.<br />

Bei ihrer Erzeugung wird ein Vaterelement im Konstruktor übergeben. Wird jetzt<br />

das Vaterelement gelöscht, dann werden automatisch auch alle seine Kindelemente<br />

entfernt und korrekt mit dispose() freigegeben. Beispiel, in dem eine Taste als<br />

Kind einem Fenster hin<strong>zu</strong>gefügt wird:<br />

Shell shell = new Shell(display);<br />

Button button = new Button(shell, SWT.PUSH);<br />

Es ist in dem Beispiel ausreichend, das Fenster frei<strong>zu</strong>geben. Dieses löscht automatisch<br />

alle seine Kindelemente:<br />

shell.dispose(); // Gibt auch button wieder frei.<br />

Leider ist es in der Praxis nicht ganz einfach, immer daran <strong>zu</strong> denken, alle Ressourcen<br />

auch wieder frei<strong>zu</strong>geben. Daher sollte sich ein Entwickler an einen gewissen Standardaufbau<br />

in seiner Anwendung halten. Die folgenden beiden Abschnitt zeigen diesen<br />

sowohl für reine SWT-Lösungen als auch für Anwendungen, die JFace verwenden. Die<br />

SWT-Variante ist nur eine Notlösung. Wann immer es möglich ist, sollte der JFace-Weg<br />

gewählt werden.<br />

3.1.4.1 Ressourcenverwaltung bei reinen SWT-Anwendungen<br />

Die Hauptidee besteht darin, das Anlegen sowie die Freigabe von Ressourcen zentral <strong>zu</strong><br />

bündeln. Beispiel (QuelltextFirstSWTImageResourceApplication):<br />

private void createImages() {<br />

if (image1 == null)<br />

image1 = new Image(display, "resources/image1.gif");<br />

if (image2 == null)<br />

image2 = new Image(display, "resources/image2.gif");<br />

}<br />

private void disposeImages() {<br />

if (image1 != null)<br />

image1.dispose();<br />

if (image2 != null)<br />

image2.dispose();<br />

}<br />

public void run() {<br />

createImages();<br />

createGUI();<br />

// Ereignisschleife<br />

disposeImages();<br />

display.dispose();<br />

}<br />

Dieser Ansatz funktioniert für sehr kleine Programm ganz gut. Leider hat er aber gerade<br />

für „richtige“ Anwendungen große Nachteile. So werden unter Umständen Bilder<br />

angelegt, die nicht immer benötigt werden. Es ist ja bei der Erzeugung nicht sicher, dass<br />

beispielsweise der Dialog, der die Ressourcen verwendet, überhaupt angezeigt wird. Es<br />

ist auch bei vielen Klassen schwierig, Bilder und andere Ressourcen zwischen mehreren<br />

Dialogen, Menüs, usw. <strong>zu</strong> teilen.<br />

23


3.1 Grundlagen<br />

3.1.4.2 Ressourcenverwaltung bei JFace-Anwendungen<br />

Der JFace-Ansatz versucht, die Nachteile der SWT-Lösung <strong>zu</strong> umgehen. Da<strong>zu</strong> werden<br />

entweder ein zentrales oder aber mehrere Registry-Objekte für Bilder, Zeichensätze und<br />

Farben der Anwendung angelegt. Eine Registry nimmt normalerweise lediglich Beschreibungen<br />

der Ressourcen (sogenannte Deskriptoren) auf. Ein solcher Deskriptor beinhaltet<br />

z.B. den Namen der Bilddatei. Beim Programmstart werden die Deskriptoren aller<br />

benötigten Ressourcen in der Registry abgelegt. Ist sicher, dass bestimmte Ressourcen<br />

immer benötigt werden, dann lassen sich statt der Beschreibungen auch direkt die<br />

Ressource-Objekte erzeugen und in der Registry ablegen. Wird später im Betrieb ein<br />

Bild oder eine andere Ressource benötigt, dann erzeugt die Registry diese beim ersten<br />

Zugriff anhand der Beschreibung. Damit gehören die Ressourcen der Registry, so dass<br />

diese beim Programmenende für deren Freigabe verantwortlich ist. Und wann wird die<br />

Registry gelöscht? Jedes Registry-Objekt ist ein Kindelement einesDisplay-Objektes.<br />

Sobald das Display-Objekt der Anwendung beseitigt wird, entfernt es auch alle seine<br />

Kindelement. Beispiel (QuelltextFirstJFaceImageResourceApplication):<br />

public class FirstJFaceImageResourceApplication<br />

extends ApplicationWindow {<br />

private ImageRegistry imageRegistry;<br />

public FirstJFaceImageResourceApplication() {<br />

super(null);<br />

createImages();<br />

}<br />

@Override<br />

protected Control createContents(Composite parent) {<br />

Button button = new Button(parent, SWT.PUSH);<br />

button.setImage(imageRegistry.get("fighting-duke"));<br />

// ...<br />

return parent;<br />

}<br />

// Registry mit allen möglicherweise benötigten<br />

// Deskriptoren der Ressourcen erzeugen.<br />

private void createImages() {<br />

imageRegistry = new ImageRegistry(Display.getDefault());<br />

imageRegistry.put("fighting-duke",<br />

ImageDescriptor.createFromFile(this.getClass(),<br />

"/resources/duke-fight.gif"));<br />

}<br />

public void run() {<br />

setBlockOnOpen(true);<br />

open();<br />

Display.getDefault().dispose();<br />

}<br />

}<br />

public static void main(String[] args) {<br />

new FirstJFaceImageResourceApplication().run();<br />

}<br />

In dem Beispiel oben sollten die Ressourcen-Namen am besten noch durch String-<br />

Konstanten definiert werden.<br />

24


3.2 Layout-Management<br />

Der JFace-Ansatz ist recht mächtig und vermeidet <strong>zu</strong>verlässig Freigabefehler. Wenn es<br />

möglich ist, sollte er verwendet werden.<br />

3.2 Layout-Management<br />

3.2.1 Motivation<br />

Dieser Abschnitt behandelt generell das Thema, wie einzelne GUI-Elemente („Widgets“)<br />

in einem Dialog angeordnet werden. Da<strong>zu</strong> stellt sich natürlich sofort die Frage, warum<br />

man nicht einfach die Koordinaten der Elemente sowie deren Größen vorgibt. Das Ergebnis<br />

wäre also eine absolute Positionierung. Daraus resultieren eine ganze Anzahl von<br />

Problemen, die die folgenden Abschnitte anhand verschiedener Szenarien beschrieben.<br />

3.2.1.1 Unterschiedliche Plattformen<br />

Betrachtet man die Ausgabe auf unterschiedlichen Plattformen, dann ist leicht <strong>zu</strong> sehen,<br />

dass nicht immer alle Zeichensätze auf allen Betriebssystemen vorhanden sind und somit<br />

die Texte unterschiedliche Ausmaße besitzen. Weiterhin unterscheiden sich die Designs<br />

und damit die Größen der Widgets auf den Plattformen. Das folgende Beispiel wurde mit<br />

dem QuelltextNullLayout erstellt.<br />

Windows 7:<br />

Abbildung 3.8: Windows 7 Abbildung 3.9: LinuX, GNOME 2.28.2<br />

Windows XP:<br />

Mac OS X (10.6):<br />

Abbildung 3.10: Windows XP<br />

Abbildung 3.11: MacOS X<br />

Es ist gut <strong>zu</strong> sehen, dass das Layout, das für Windows 7 erstellt wurde, unter LinuX<br />

und MacOS X <strong>zu</strong> <strong>zu</strong> kleinen Tasten führt. Die Kernaussage lautet also, dass auf die<br />

Zeichensatz- und Widgetgrößen kein Verlass ist. Somit können die Größen einzelner<br />

Komponenten nicht manuell berechnet werden.<br />

3.2.1.2 Internationalisierung<br />

Programme werden in der Regel nicht nur mit einer Spracheinstellung ausgeliefert. Gerade<br />

für den internationalen Markt ist es wichtig, dass ein Programm beliebig viele Sprachen<br />

unterstützt. Dummerweise sind aber viele Texte in Sprachen wie deutsch und englisch<br />

unterschiedlich lang. Somit schwankt auch deren Platzbedarf auf dem Bildschirm.<br />

Die folgenden Darstellungen verdeutlichen das Problem (Quelltexte NullLayoutLanguagesEN.java<br />

undNullLayoutLanguagesDE.java, basieren auf dem vorherigem<br />

Beispiel).<br />

25


3.2 Layout-Management<br />

Abbildung 3.12: Englische Texte<br />

Abbildung 3.13: Deutsche Texte<br />

Die Idee, einen Dialog in seinem Aussehen manuell der jeweiligen Sprache an<strong>zu</strong>passen,<br />

sollte man schnell wieder begraben, da der Aufwand gewaltig wird. Es muss nicht nur<br />

für jede neue Sprache der Dialog angepasst werden, sondern es müssen dann auch<br />

verschiedene Varianten desselben Dialogs im Laufe der Jahre gepflegt werden.<br />

3.2.1.3 Interaktive Größenänderungen<br />

Dialoge sind für den Anwender dann besonders komfortabel <strong>zu</strong> verwenden, wenn sie in<br />

ihrer Größe so verändert werden können, dass sich der Inhalt sinnvoll anpasst. Damit ist<br />

gemeint, dass beispielsweise bei manueller Vergößerung eines Dialogs die Texteingabefelder<br />

breiter werden, damit mehr Text sichtbar wird. Beschriftungen dagegen sollten ihre<br />

Größe behalten. Bei absoluter Platzierung passiert das, was die folgenden Bilder zeigen:<br />

Abbildung 3.14: Vergrößerter Dialog<br />

Abbildung 3.15: Verkleinerter Dialog<br />

Wie <strong>zu</strong> sehen ist, werden im linken Bild die Tasten nicht größer, obwohl jetzt genügend<br />

Platz vorhanden ist. Auch bei anderen Widgets wie Textfeldern ist es durchaus interessant,<br />

den Dialog <strong>zu</strong> vergrößern, um mehr Text sehen <strong>zu</strong> können, wenn es die Bildschirmgröße<br />

<strong>zu</strong>lässt.<br />

3.2.1.4 Look and Feel<br />

SWT erlaubt, das Aussehen durch CSS-Dateien <strong>zu</strong> verändern. Da davon die Zeichensatzgrößen<br />

sowie Bilder betroffen sein können, führt auch hier die absolute Platzierung<br />

in eine Sackgasse.<br />

3.2.1.5 Zusammenfassung<br />

Eine echte plattformunabhängige Programmierung ist mit absoluten Layouts nicht sinnvoll<br />

möglich. Unterschiedliche Zeichensätze oder Zeichengrößen verbieten die manuelle<br />

Berechnung einer Größe. Weiterhin erfordert die Portierung einer Anwendung in mehrere<br />

Sprachen bei absoluten Layouts manuelles Nacharbeiten, was nicht praktikabel ist.<br />

Deshalb sollten in SWT und damit auch JFace Dialoge niemals ohne Layoutmanager<br />

erstellt werden. Aber auch andere Toolkits wie QT, GTK, Swing, AWT und WPF arbeiten<br />

mit Layouts. Die Manager funktionieren zwar überall etwas unterschiedlich, die Idee und<br />

grundsätzliche Arbeitsweise aber sind identisch.<br />

Die folgenden Abschnitte stellen die Verwendung sogenannter Layoutmanager vor, die<br />

die Platzierung von Widgets anhand von Regeln übernehmen. Eine gute Einführung ist<br />

auch unter [Layouts] <strong>zu</strong> finden.<br />

26


3.2 Layout-Management<br />

3.2.2 Größe einer Komponente<br />

Wenn jetzt auf die absolute Positionierung verzichtet wird und statt dessen Layoutmanager<br />

Einsatz finden, stellt sich die Frage, wie ein Layoutmanager die Größe aller möglichen<br />

Komponenten ermitteln kann. Die Antwort ist einfach: Er kann es nicht selbst. Nur eine<br />

Komponente bzw. deren Basisklasse Control weiß, wie groß sie gerne werden möchte.<br />

Und diese bevor<strong>zu</strong>gte Größe fragt der Layout-Manager an allen Komponenten ab,<br />

die er platzieren muss. Da jedes SWT-Widget vonControl erbt, steht die Angabe allen<br />

Widgets <strong>zu</strong>r Verfügung.<br />

Abbildung 3.16: HierarchieButton<br />

Abbildung 3.17: HierarchieLabel<br />

Die BasisklasseControl besitzt die MethodecomputeSize, die die bevor<strong>zu</strong>gte Größe<br />

des Widgets berechnet und <strong>zu</strong>rückgibt. Mit Hilfe der Methoden getSize bzw.setSize<br />

kann die aktuelle Größe des Widgets ausgelesen bzw. überschrieben werden. Somit<br />

besitzt jedes Widget eine bevor<strong>zu</strong>gte und eine aktuelle Größe. Die aktuelle Größe kann<br />

von der bevor<strong>zu</strong>gten abweichen, wenn beispielsweise der Dialog manuell in seiner Größe<br />

verändert und die Widgets angepasst werden. Dann kann ein Widget größer oder kleiner<br />

werden, als es eigentlich sein möchte.<br />

3.2.3 Fill-Layout<br />

Das Fill-Layout platziert alle darin enthaltenen Widgets nacheinander bzw. untereinander<br />

in einer einheitlichen Größe. Somit werden alle Widgets auf die Maße des größten im<br />

Layout vorhandenen Widgets gedehnt. Weiterhin lassen sich die Abstände zwischen den<br />

Widgets, die äußeren Ränder um das Layout sowie die Ausrichtung (horizontales oder<br />

verktikales Layout) angeben.<br />

Widget 1<br />

Widget 2<br />

...<br />

Widget n<br />

Widget 1<br />

Widget 2<br />

...<br />

Widget n<br />

Abbildung 3.18: Anordnung im Fill-Layout<br />

Die Ausrichtung wird – wie leider Vieles in SWT – durch Konstanten im Konstruktor festgelegt:<br />

SWT.HORIZONTAL: Die Widgets werden waagerecht angeordnet.<br />

SWT.VERTICAL: Die Widgets werden senkrecht angeordnet.<br />

Die folgenden Bilder zeigen verschiedene Anwendungen des Layouts. Es ist gut <strong>zu</strong> erkennen,<br />

dass der Layout-Manager trotz unterschiedlicher Plattformen immer sicherstellt,<br />

dass die Texte lesbar sind. Die Widgets, in diesem Fall die Tasten, liefern ja auf jeder<br />

Plattform unterschiedliche Werte für ihre bevor<strong>zu</strong>gte Größe <strong>zu</strong>rück.<br />

27


3.2 Layout-Management<br />

Abbildung 3.19: Horizontale Anordnung im Fill-Layout<br />

Manuelle Verkleinerung der Dialoge:<br />

Abbildung 3.20: Manuelle Verkleinerung im Fill-Layout<br />

Vertikale Ausrichtung ohne manuellen Eingriff:<br />

Abbildung 3.21: Vertikale Anordnung im Fill-Layout<br />

Das folgende, kleine Beispiel zeigt den Einsatz des Fill-Layouts mit vertikaler Ausrichtung<br />

(QuelltextFillLayoutApplication).<br />

...<br />

shell.setLayout(new FillLayout(SWT.VERTICAL));<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("1. Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("2. lange Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("3. ganz lange Taste");<br />

shell.pack();<br />

...<br />

Die Abstände um die Widgets sowie <strong>zu</strong>m Rand hin werden nicht verändert. SWT vergibt<br />

da<strong>zu</strong> Standardwerte. Sollen diese überschrieben werden, dann kann das mit Hilfe<br />

öffentlicher Attribute geschehen. Dieses ist ohnehin ein Prinzip der Layouts in SWT und<br />

JFace. Die Eigenschaften werden nicht über Getter- und Setter-Methoden gelesen und<br />

beschrieben, sondern durch direkten Zugriff auf die entsprechenden Attribute.<br />

Einige Attribute der KlasseFillLayout:<br />

28


3.2 Layout-Management<br />

marginWidth: linker und rechter Rand um das Element, das dieses Layout besitzt<br />

marginHeight: oberer und unterer Rand um das Element, das dieses Layout besitzt<br />

spacing: Abstand zwischen den einzelnen Widgets im Layout<br />

Das Vaterelement aller Widgets im Beispiel ist das Fenster. Ihm wird der Layout-Manager<br />

<strong>zu</strong>gewiesen. Wichtig ist noch der Aufruf shell.pack() am Ende des Beispiels. Mit<br />

ihm werden die Layout-Berechnung gestartet und somit die Widgets durch den Manager<br />

platziert.<br />

3.2.4 Row-Layout<br />

DerRowLayout ähnelt in gewisser Weise demFillLayout. Auch hier werden die Widgets<br />

vertikal oder horizontal angeordnet. Allerdings berücksichtigt der Layout-Manager<br />

die bevor<strong>zu</strong>gten Größen der Widgets und verwendet sie. Somit werden in der Standardeinstellung<br />

alle Widgets in ihrer bevor<strong>zu</strong>gten Größe platziert. Dieses Verhalten lässt sich<br />

allerdings durch Schreiben eines Attributes ändern. So wird manuell erzwungen, dass alle<br />

Komponenten – wie beimFillLayout – so groß wie die größte Komponente werden<br />

sollen. Eine genaue Betrachtung erfolgt weiter unten bei der Vorstellung der Attribute des<br />

Layouts. Weiterhin kann bei diesem Layout festgelegt werden, dass ein manueller „Zeilenumbruch“<br />

erfolgen soll, wenn die Layout-Zeile voll ist und dadurch Widgets verkleinert<br />

werden müssten.<br />

Widget 1<br />

Widget 2<br />

Widget 3<br />

...<br />

Widget n<br />

Widget 1 Widget 2 ... Widget n<br />

Abbildung 3.22: Anordnung im Row-Layout<br />

Die Ausrichtung wird auch hier durch die bereits bekannten Konstanten im Konstruktor<br />

festgelegt:<br />

SWT.HORIZONTAL: Die Widgets werden waagerecht angeordnet.<br />

SWT.VERTICAL: Die Widgets werden senkrecht angeordnet.<br />

Die folgenden Bilder zeigen verschiedene Anwendungen des Layouts. In der Abbildung<br />

3.23 ist <strong>zu</strong> erkennen, wie alle Widgets in ihrer bevor<strong>zu</strong>gten Größe platziert wurden.<br />

Abbildung 3.23: Horizontale Anordnung im Row-Layout<br />

Nach einer manuellen Verkleinerung ergibt sich das folgende Bild. Zu erkennen ist, dass<br />

der automatische Zeilenumbruch nicht eingeschaltet wurde.<br />

29


3.2 Layout-Management<br />

Abbildung 3.24: Manueller Verkleinerung im Row-Layout (ohne Umbruch)<br />

Eine manuelle Verkleinerung mit aktivem Zeilenumbruch dagegen würde die Widgets in<br />

ihrer bevor<strong>zu</strong>gten Größe belassen:<br />

Abbildung 3.25: Manueller Verkleinerung im Row-Layout (mit Umbruch)<br />

Das Beispiel mit vertikaler Ausrichtung ist schließlich nicht mehr sonderlich spannend:<br />

Abbildung 3.26: Vertikale Anordnung im Row-Layout<br />

Der Programmcode für das Beispiel, mit dessen Hilfe die Screenshots hier angefertigt<br />

wurden, ist nicht komplex (RowLayoutApplication):<br />

...<br />

RowLayout layout = new RowLayout(SWT.HORIZONTAL);<br />

layout.wrap = false; // kein "Zeilenumbruch"<br />

shell.setLayout(layout);<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("1. Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("2. lange Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("3. ganz lange Taste");<br />

shell.pack();<br />

...<br />

Bei leistungsfähigeren und damit auch komplexeren Layout-Managern ist relativ Programmcode<br />

erforderlich, um alle Attribute ein<strong>zu</strong>stellen. Daher wurden mit JFace Fabrik-<br />

Klassen für alle Layout-Manager eingeführt, die die Erzeugung und das Einstellen des<br />

Layouts vereinfachen. Für das im Beispiel eingesetzte RowLayout handelt es sich um<br />

die KlasseRowLayoutFactory. Damit wird der Programmcode oben umgeformt:<br />

...<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("1. Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("2. lange Taste");<br />

button = new Button(shell, SWT.PUSH);<br />

button.setText("3. ganz lange Taste");<br />

30


3.2 Layout-Management<br />

RowLayoutFactory.swtDefaults().type(SWT.HORIZONTAL)<br />

.wrap(false).applyTo(shell);<br />

pack() wird nicht mehr manuell aufgeufen. Das geschieht automatisch nach Einstellen<br />

des Layouts. Daher muss die Layout-Erzeugung jetzt unbedingt nach Einfügen aller Widgets<br />

erfolgen. Eine genaue Erklärung der Fabrikklasse ist in der API-Dokumentation <strong>zu</strong><br />

JFace <strong>zu</strong> finden.<br />

Wie beimFillLayout gibt es auch beimRowLayout einige Attribute, die das Verhalten<br />

steuern. In JFace erlaubt die Klasse RowLayoutFactory hier das einfachere Setzen<br />

dieser Attribute innerhalb der Layout-Angabe. Attribute:<br />

center: Ist das Attributtrue, dann werden die Widgets bei unterschiedlichen Breiten<br />

bzw. Höhen <strong>zu</strong>einander zentriert Ansonsten werden sie linksbündig bzw. oben<br />

ausgerichtet.<br />

justify: Ist das Attribut true, dann werden die Widgets wie im Blocksatz angeordnet.<br />

Ist esfalse, dann werden die Widgets am Ursprung platziert.<br />

marginTop: oberer Rand über dem Layout<br />

marginLeft: linker Rand neben dem Layout<br />

marginBottom: unterer Rand unter dem Layout<br />

marginRight: rechter Rand neben dem Layout<br />

pack: Ist das Attribut true, dann verwendet der Layout-Manager die bevor<strong>zu</strong>gten<br />

Größen der Widgets. Ansonsten erzwingt er, dass alle Widgets die Maße der größten<br />

Komponente annehmen.<br />

spacing: Abstand zwischen den Widgets<br />

wrap: Ist das Attributtrue, dann wird der automatische Zeilenumbruch aktiviert.<br />

Mit diesen Attributen lassen sich bereits viele Eigenschaften des Layouts verändern.<br />

Gerade bei komplexeren Layout-Manager soll der Entwickler aber noch weitergehende<br />

Einflussmöglichkeiten erhalten. Da<strong>zu</strong> kann jedem Widget eine Layout-Eigenschaft <strong>zu</strong>geordnet<br />

werden. Diese Eigenschaft ist ein Objekt, das <strong>zu</strong>sätzliche Layout-Bedingungen<br />

aufnimmt, die der Layout-Manager berücksichtigen muss. Es existiert für nahe<strong>zu</strong> jeden<br />

Layout-Manager eine eigene Klasse mit Layout-Eigenschaften, weil die Eigenschaften<br />

eng an den Manager gekoppelt sind. Die Eigenschaften werden durch den Aufruf der<br />

MethodesetLayoutData (nichtsetData!!) am Widget abgelegt.<br />

Am Beispiel desRowLayouts lässt sich gut zeigen, was die <strong>zu</strong>sätzlichen Layout-Eigenschaften<br />

bewirken können. Möchte der Entwickler ein Widget etwas größer machen, als<br />

es die bevor<strong>zu</strong>gte Größe vorsieht, dann ist das bisher praktisch kaum möglich. Wird jetzt<br />

aber dem Widget eine Layout-Eigenschaft mit einer fest vorgegeben Größe übergeben,<br />

dann verwendet der Layout-Manager diese. Im RowLayout heißt die Eigenschaftsklasse<br />

RowData. Dieser Namensaufbau ist übrigens bei allen Layout-Managern einheitlich:<br />

StattLayout erhält die Eigenschaftsklasse die Namensendung Data.<br />

Das folgende Beispiel erweitert das bisherige, indem die erste Taste zwangsweise eine<br />

feste Breite vom 200 Pixeln erhält, aber ihre bevor<strong>zu</strong>gte Höhe beibehält (Verwendung<br />

vonSWT.DEFAULT für den Standardwert).<br />

31


3.2 Layout-Management<br />

...<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("1. Taste");<br />

RowData layoutData = new RowData(200, SWT.DEFAULT);<br />

button.setLayoutData(layoutData);<br />

...<br />

Abbildung 3.27: Row-Layout mit veränderter Widget-Breite<br />

Wie beim Layout werden auch bei den Eigenschaften die Werte direkt und nicht über<br />

Setter-Methoden manipuliert. Attribute der KlasseRowData:<br />

width: Manuell vergebene Breite des Widgets. Die Angabe vonSWT.DEFAULT bewirkt,<br />

dass die errechnete bevor<strong>zu</strong>gte Breite des Widgets verwendet wird.<br />

height: Manuell vergebene Höhe des Widgets. Die Angabe von SWT.DEFAULT<br />

bewirkt, dass die errechnete bevor<strong>zu</strong>gte Höhe des Widgets verwendet wird.<br />

exclude: Die Ausgabe des Widgets soll im Layout unterdrückt werden. Es ist somit<br />

unsichtbar und belegt auch keinen Platz.<br />

Manuelle Größenabgaben sollten möglichst sparsam eingesetzt werden, weil ansonsten<br />

spätestens im Zuge der Internationalisierung der Anwendung wieder das Problem auftritt,<br />

dass unterschiedlich lange Texte eventuell nicht mehr richtig passen. Für Widgets,<br />

die keinen eigenen Text haben, ist eine manuelle Größenvergabe dagegen manchmal<br />

sinnvoll. So können mehrzeilige Texteingabefelder einen vordefinierten Platz belegen.<br />

Dieser Platz kann nicht vom Widget selbst ermittelt werden, weil es nicht wissen kann,<br />

wieviel Text gleichzeitig sichtbar sein soll.<br />

3.2.5 Grid-Layout<br />

Nach den recht eingeschränkten FillLayout und RowLayout wird mit GridLayout<br />

jetzt ein Layout-Manager vorgestellt, der dem Entwickler deutlich mehr Möglichkeiten in<br />

Be<strong>zu</strong>g auf die Platzierung sowie die Größen der Widgets einräumt. Die Grundidee besteht<br />

darin, alle Widgets in einem tabellarischen Raster an<strong>zu</strong>ordnen. In jeder einzelnen<br />

Zelle des Rasters kann sich immer nur ein Widget befinden. Dabei dürfen sich allerdings<br />

einzelne Widgets über mehrere Zeilen und Spalten erstrecken. Weiterhin kann der<br />

Layout-Manager durch einstellbare Regeln auch Widgets über ihre bevor<strong>zu</strong>gte Größe<br />

hinaus vergrößern und eine einheitliche Spaltenbreite erzwingen.<br />

32


3.2 Layout-Management<br />

Widget 1 Widget 2<br />

Widget 7<br />

Widget 3<br />

Widget 8<br />

Widget 6<br />

Widget 5 Widget 4<br />

Widgets dürfen sich über<br />

mehrere Zeilen und/oder Spalten<br />

erstrecken.<br />

Zeilen dürfen unterschiedliche Höhen,<br />

Spalte unterschiedliche Breiten<br />

besitzen.<br />

Abbildung 3.28: Anordnung im Grid-Layout<br />

Bevor ein Beispiel betrachtet wird, sollen <strong>zu</strong>nächst die Attribute und somit die Eigenschaften<br />

des Layout-Managers vorgestellt werden.<br />

horizontalSpacing: Horizontaler Abstand zwischen den Zellen. Damit ist der<br />

Abstand zwischen den einzelnen Spalten gemeint.<br />

verticalSpacing: Vertikaler Abstand zwischen den Zellen. Dieser beschreibt die<br />

Distanz zwischen den Zeilen.<br />

makeColumnsEqualWidth: Wenn dieses Attributtrue ist, dann erzwingt das Layout<br />

eine einheitliche Breite aller Spalten. Die Breite des breitesten Widgets im kompletten<br />

Layout bestimmt die Breite aller Spalten. Ist der Wert dagegenfalse, dann<br />

bestimmt in jeder einzelnen Spalte die Breite des darin enthaltenen breitesten Widgets<br />

die Gesamtbreite der Spalte. Damit ist noch nicht gesagt, wie sich die schmaleren<br />

Widgets verhalten. Normalerweise belegen diese nur einen Teil der ihnen <strong>zu</strong>r<br />

Verfügung stehenden Zelle, indem sie an deren jeweiligen Ursprung (z.B. links oben)<br />

ausgerichtet werden. Dieses ist anhand der stilisierten Abbildung 3.28 ersichtlich.<br />

Es kann aber über die Layout-Eigenschaften einzelner Widgets eingestellt werden,<br />

dass sich die Widgets über ihre bevor<strong>zu</strong>gte Größe hinaus vergrößern und die Zelle<br />

komplett ausfüllen. Dieses Verhalten wird weiter unten näher betrachtet.<br />

marginTop: oberer Rand über dem Layout<br />

marginLeft: linker Rand neben dem Layout<br />

marginBottom: unterer Rand unter dem Layout<br />

marginRight: rechter Rand neben dem Layout<br />

marginHeight: Oberer und unterer Rand um das Layout. Diese Eigenschaft kann<br />

durch das Setzen vonmarginTop undmarginBottom überschrieben werden.<br />

marginWidth: Linker und rechter Rand neben dem Layout. Diese Eigenschaft kann<br />

durch das Setzen vonmarginLeft undmarginRight überschrieben werden.<br />

numColumns: Anzahl der Spalten im Layout. Diese Angabe ist erforderlich, weil die<br />

Widgets ohne die Angabe einer Zelle platziert werden. Statt dessen wird die Reihenfolge,<br />

in der die Widgets <strong>zu</strong> ihrem Vaterelement hin<strong>zu</strong>gefügt werden, verwendet,<br />

um daraus die Position im Layout <strong>zu</strong> ermittlen: Die Widgets werden, beginnend mit<br />

dem Ursprung, „von links nach rechts“ und „von oben nach unten“ platziert. Immer<br />

wenn die voreingestellte Spaltenzahl erreicht ist, wird eine neue Zeile angefangen.<br />

33


3.2 Layout-Management<br />

Wie für das RowLayout existiert ebenso für das GridLayout eine Fabrikklasse <strong>zu</strong>m<br />

leichteren Erzeugen des Layouts(GridLayoutFactory). Die Mächtigkeit des Layout-<br />

Managers ergibt sich aus den vielen Layout-Eigenschaften, die den Widgets übergeben<br />

werden können. Die dafür <strong>zu</strong>ständige Klasse heißt GridData. Dessen wichtigste Attribute<br />

sind:<br />

exclude: Die Anzeige des Widgets wird im Layout unterdrückt, so dass dessen<br />

Zelle leer erscheint.<br />

grabExcessHorizontalSpace: Dieses Attribut steuert das Verhalten des Widgets,<br />

wenn das Vaterelement (z.B. das Fenster) schmaler oder breiter wird. Ist das<br />

Attribut true, dann schrumpft oder wächst das Widget in der Breite, wenn sich die<br />

Breite des Vaterelementes ändert. So kann z.B. ein Textfeld dem Anwender mehr<br />

Zeichen gleichzeitig darstellen, wenn der Dialog größer wird. Ein Bezeichner („Label“)<br />

dagegen würde sich nicht verändern, weil sein kompletter Text ohnehin in der<br />

Regel sichtbar ist.<br />

grabExcessVerticalSpace: Dieses Attribut steuert das Verhalten des Widgets,<br />

wenn das Vaterelement flacher oder höher wird. Ist das Attributtrue, so schrumpft<br />

oder wächst das Widget in der Höhe, wenn sich die Höhe des Vaterelementes ändert.<br />

widthHint: Wird dieses Attribut verwendet, dann überschreibt es die bevor<strong>zu</strong>gte<br />

Breite eines Widgets. So lassen sich manuell Größen vorgeben. Wie schon beim<br />

Row-Layout beschrieben, sollte das manuelle Setzen einer Größe nur sparsam und<br />

gezielt eingesetzt werden, weil es ansonsten spätestens bei der Internationalisierung<br />

Probleme geben kann.<br />

heightHint: Dieses Attribut überschreibt die bevor<strong>zu</strong>gte Höhe eines Widgets.<br />

horizontalAlignment: Mit diesem Attribut wird gesteuert, wie sich ein Widget in<br />

einer Zelle verhalten soll:<br />

SWT.BEGINNG: Ist das Widget schmaler als die Zelle, dann wird es an den<br />

Anfang der Zelle platziert.<br />

SWT.CENTER: Ist das Widget schmaler als die Zelle, dann wird es innerhalb der<br />

Zelle zentriert.<br />

SWT.END: Ist das Widget schmaler als die Zelle, dann wird es an das Ende der<br />

Zelle platziert.<br />

SWT.FILL: Ist die bevor<strong>zu</strong>gte Breite des Widgets kleiner als die Zellenbreite,<br />

dann wird das Widget auf die Breite der Zelle gestreckt. Dieser Wert <strong>zu</strong>sammen<br />

mit dem Setzen vongrabExcessHorizontalSpace bewirkt, dass ein Widget<br />

immer die komplette Zellenbreite belegt, auch wenn das Vaterelement seine<br />

Größe verändert.<br />

verticalAlignment: Mit diesem Attribut wird gesteuert, wie sich ein Widget in<br />

einer Zelle verhalten soll, wenn seine bevor<strong>zu</strong>gte Höhe kleiner als die Zellenhöhe<br />

ist. Die Konstanten sind dieselben wie im vorherigen Auszählungspunkt bei der horizontalen<br />

Platzierung mithorizontalAlignment.<br />

34


3.2 Layout-Management<br />

horizontalIndent: Belegt ein Widget nicht die komplette Breite einer Zelle, dann<br />

kann mit diesem Ein<strong>zu</strong>g eingestellt werden, um wieviele Pixel es vom Platzierungsursprung<br />

entfernt angeordnet werden soll. Bei einer Ausrichtung am Anfang der Zelle<br />

würde es um die angegebene Pixelzahl vom Anfang der Zelle entfernt platziert<br />

werden. Bei einer Ausrichtung am Ende der Zelle dagegen würde der Ein<strong>zu</strong>g den<br />

Abstand vom Ende der Zelle definieren.<br />

verticalIndent: WiehorizontalIndent, nur auf Höhe des Widgets im Layout<br />

bezogen.<br />

horizontalSpan: Anzahl Spalten, die das Widget im Layout belegen soll<br />

verticalSpan: Anzahl Zeilen, die das Widget im Layout belegen soll<br />

minimumWidth: Manche Widgets sollen im Layout bei einer Größenänderung des<br />

Dialogs eine gewisse Mindestbreite nicht unterschreiten, damit wichtige Informationen<br />

immer sichtbar bleiben. Diese Mindestbreite kann hier festgelegt werden.<br />

minimumHeight: Manche Widgets sollen im Layout bei einer Größenänderung des<br />

Dialogs eine gewisse Mindesthöhe nicht unterschreiten, damit wichtige Informationen<br />

immer sichtbar bleiben. Diese Mindesthöhe kann hier festgelegt werden.<br />

Aus der langen Aufstellung ist ersichtlich, dass im schlimmsten Fall ziemlich viele Attribute<br />

eingestellt werden müssen. Glücklicherweise können die häufigsten Parameter direkt<br />

über die Konstruktoren der Klasse GridData übergeben werden. Näheres da<strong>zu</strong> ist in<br />

der API-Dokumentation <strong>zu</strong> finden. Zusätzlich existiert auch für die KlasseGridData mit<br />

GridDataFactory eine Fabrikklasse <strong>zu</strong>m leichteren Befüllen.<br />

Das folgende Beispiel zeigt die Layout-Eigenschaften einer Taste, die sich über die volle<br />

Breite und Höhe einer Zelle erstrecken soll, obwohl sie eigentlich kleiner sein möchte.<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("Ok");<br />

data = new GridData(SWT.FILL, SWT.FILL, true, true);<br />

button.setLayoutData(data);<br />

Die vier Parameter des Konstruktors der KlasseGridData bedeuten:<br />

1. SWT.FILL: Die Taste belegt die komplette Breite der Zelle.<br />

2. SWT.FILL: Die Taste belegt die komplette Höhe der Zelle.<br />

3. true: Die Taste wächst und schrumpft in der Breite, wenn sich die Zellenbreite<br />

ändert.<br />

4. true: Die Taste wächst und schrumpft in der Höhe, wenn sich die Zellenhöhe ändert.<br />

Anhand des folgenden, relativ sinnlosen Dialogs soll gezeigt werden, wie mit Hilfe des<br />

GridLayouts Widgets platziert werden.<br />

Abbildung 3.29: Beispieldialog für das Grid-Layout<br />

35


3.2 Layout-Management<br />

Zunächst wird ein Raster über den Dialog gelegt. Immer dort, wo Widgets aneinander<br />

stoßen, entstehen neue Spalten bzw. Zeilen:<br />

Abbildung 3.30: Rasterhilfslinien auf dem Dialog<br />

Jetzt kann das Layout-Objekt für drei Spalten erzeugt werden. Dabei wird keine identische<br />

Spaltenbreite erzwungen. Der folgende Quelltext (GridLayoutApplication)<br />

verzichtet auf den Einsatz der Fabrikklasse:<br />

...<br />

GridLayout layout = new GridLayout(3, false);<br />

shell.setLayout(layout);<br />

Im nächsten Schritt wird das obere Label als Überschrift in den Dialog eingefügt. Es<br />

soll sich über alle drei Spalten erstrecken, weil der Text in anderen Sprachen ja durchaus<br />

länger werden kann. Würde das Label lediglich in Spalte 1 stehen, dann würde die Spalte<br />

durch einen längeren Labeltext breiter werden. Das ist hier aber nicht gewünscht.<br />

Abbildung 3.31: Platzierung des Labels im Grid-Layout<br />

Label label = new Label(shell, SWT.BEGINNING);<br />

label.setText("Titel");<br />

data = new GridData(SWT.FILL, // Zelle horizontal füllen<br />

SWT.DEFAULT, // bevor<strong>zu</strong>gte Höhe<br />

true, // horizontal wachsen<br />

false, // vertikal nicht wachsen<br />

3, // 3 Spalten<br />

1); // eine Zeile<br />

label.setLayoutData(data);<br />

Das mehrzeilige, scrollbare Texteingabefeld ist sicherlich das interessanteste Widget im<br />

Beispiel. Es soll sowohl horizontal als auch vertikal die komplette Zelle belegen und mitwachsen.<br />

Weiterhin soll dem Widget eine bevor<strong>zu</strong>gte Breite übergeben werden, weil das<br />

Widget selbst eine viel <strong>zu</strong> kleine annimmt.<br />

36


3.2 Layout-Management<br />

Abbildung 3.32: Platzierung des Textfeldes im Grid-Layout<br />

Text text = new Text(shell, SWT.MULTI | SWT.WRAP | SWT.BORDER<br />

| SWT.H_SCROLL | SWT.V_SCROLL);<br />

data = new GridData(SWT.FILL, // Zelle horizontal füllen<br />

SWT.FILL, // Zelle vertikal füllen<br />

true, // horizontal wachsen<br />

true, // vertikal wachsen<br />

1, // eine Spalte<br />

2); // zwei Zeilen<br />

data.widthHint = 200; // bevor<strong>zu</strong>gte Breite 200 Pixel<br />

text.setLayoutData(data);<br />

Abschließend soll noch eine der beiden Tasten betrachtet werden. Zu beachten ist, dass<br />

beide Tasten unabhängig von ihrem Inhalt (ihrer Beschriftung) immer gleich breit sein<br />

sollen. Normalerweise würde der Text die Breite bestimmen. Weiterhin ist im Beispiel<br />

verlangt, dass die Tasten auch horizontal wachsen. Das ist in der Praxis natürlich recht<br />

unsinnig.<br />

Abbildung 3.33: Platzierung einer Taste im Grid-Layout<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("Ok");<br />

data = new GridData(SWT.FILL, // Zelle horizontal füllen<br />

SWT.FILL, // Zelle vertikal füllen<br />

false, // horizontal nicht wachsen<br />

true); // vertikal wachsen<br />

button.setLayoutData(data);<br />

Soll die Taste dagegen ihre bevor<strong>zu</strong>gte Breite behalten, dann dürfte sie nicht auf die<br />

Zellenbreite gestreckt werden:<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("Ok");<br />

data = new GridData(SWT.DEFAULT, // BEVORZUGTE BREITE!<br />

SWT.FILL, // Zelle vertikal füllen<br />

false, // horizontal nicht wachsen<br />

true); // vertikal wachsen<br />

button.setLayoutData(data);<br />

37


3.2 Layout-Management<br />

Das Ergebnis zeigt Abbildung 3.34.<br />

Abbildung 3.34: Platzierung einer Taste im Grid-Layout ohne Streckung<br />

3.2.6 Geschachtelte Layout<br />

Die bisher vorgestelten Layout-Manager waren für sich alleine gesehen zwar schon recht<br />

hilfreich, scheitern aber schnell bei komplexen Layouts. Das liegt daran, dass immer jedem<br />

Fenster genau ein Layout-Manager <strong>zu</strong>geordnet war, der das Aussehen des Dialoginhalts<br />

selbst festlegen musste. Dieser Ansatz ist allerdings für komplizierte Layouts nicht<br />

flexibel genug, bzw. würde extrem mächtige und damit auch umständlich <strong>zu</strong> verwendende<br />

Layout-Manager benötigen. Eine einfachere Lösung besteht darin, Layouts ineinander<br />

<strong>zu</strong> schachteln. Da<strong>zu</strong> existieren Widgets, die selbst wiederum als Container für andere<br />

Widgets dienen. Diesen Containern kann ein eigener Layout-Manager <strong>zu</strong>gewisen werden.<br />

Somit ergibt sich eine hierarchische Schachtelung von Layouts. Im folgenden Bild<br />

wird ein Composite-Widget als Container eingesetzt. Das Widget hat bei dieser Verwendung<br />

normalerweise keine eigene Darstellung. Es dient hauptsächlich da<strong>zu</strong>, andere<br />

Widgets auf<strong>zu</strong>nehmen und diese mit Hilfe eines eigenen Layouts dar<strong>zu</strong>stellen.<br />

Fenster mit GridLayout<br />

Widget 1 Widget 2<br />

Composite-Widget<br />

mit FillLayout<br />

Abbildung 3.35: Verschachtelung von Containern mit jeweils eigenen Layouts<br />

Das folgende Beispiel zeigt einen Dialog mit zwei nebeneinander angeordneten, identisch<br />

breiten Tasten (NestedLayoutsApplication). Für solche Dialoge mit Standardtasten<br />

werden später spezielle Fensterklassen vorgestellt, so dass es normalerweise<br />

unnötig ist, die Tasten wie im Beispiel selbst <strong>zu</strong> platzieren.<br />

NestedLayoutsApplication<br />

Composite-Widget mit FillLayout<br />

(der Rahmen zeigt die Ausmaße<br />

des Composite-Objektes)<br />

Abbildung 3.36: Beispiel für ineinander geschachtelte Layouts<br />

38


3.2 Layout-Management<br />

Der Rahmen um das Composite-Objekt mit den beiden Tasten soll nur die Ausmaße<br />

des Containers verdeutlichen.<br />

// Der Container für die Tasten erhält ein FillLayout.<br />

Composite buttonPanel = new Composite(shell, SWT.NONE);<br />

buttonPanel.setLayout(new FillLayout());<br />

// Der Container streckt sich horizontal über<br />

// zwei Spalten. Er wird im übergeordneten GridLayout<br />

// rechtsbündig (SWT.END) in seine Zelle gesetzt.<br />

// Er soll nicht mit der Zelle mitwachsen.<br />

data = new GridData(SWT.END, SWT.DEFAULT, false, false, 2, 1);<br />

buttonPanel.setLayoutData(data);<br />

// Die Tasten werden <strong>zu</strong>m Composite-Objekt hin<strong>zu</strong>gefügt.<br />

new Button(buttonPanel, SWT.PUSH).setText("Ok");<br />

new Button(buttonPanel, SWT.PUSH).setText("Cancel");<br />

Die Screenshots zeigen das Verhalten des Dialogs bei Größenänderungen unter Windows<br />

und Mac OS X.<br />

Abbildung 3.37: Größenänderungen bei ineinander geschachtelten Layouts<br />

Man erkennt, dass die Reihenfolge der Tasten unter Mac OS X nicht den Vorgaben Apples<br />

entspricht. Dieses Problem lösen die vordefinierten Dialogklassen, die später betrachtet<br />

werden.<br />

3.2.7 Form-Layout<br />

DasFormLayout ist das flexibelste und komplexeste Standardlayout im SWT. Hier werden<br />

die Widgets relativ <strong>zu</strong>einander platziert und an ihren Kanten ausgerichtet.<br />

3.2.7.1 Grundidee<br />

Die Abbildung 3.38 zeigt, wie die Widget-Ränder entweder an den Rändern des Vater-<br />

Containers (hier das Fenster) oder an den Rändern anderer Widgets ausgerichtet wer-<br />

39


3.2 Layout-Management<br />

den. Zusätzlich lassen sich auch relative Maße angeben, so dass z.B. ein Widgetrand<br />

immer an 50 % der Fensterbreite ausgerichtet sein soll. Ändert sich die Fensterbreite,<br />

dann „wandert“ das Widget bzw. dessen Rand mit.<br />

Widget 2<br />

Widget 1<br />

Widget 3<br />

Widget 1 wird mit seinem linken und<br />

rechten Rand am Fenster<br />

ausgerichtet.<br />

Widget 3 wird mit seinem rechten<br />

Rand immer am Rand des Fenster<br />

ausgerichtet.<br />

Widget 4<br />

Widget 5<br />

Widget 3 wird mit seinem unteren Rand<br />

am oberen Rand von Widget 5<br />

ausgerichtet.<br />

Widget 5 wird mit seinem unteren<br />

Rand immer am Fenster<br />

ausgerichtet.<br />

Abbildung 3.38: Anordnung im Form-Layout<br />

DasFormLayout ist sehr mächtig, dummerweise aber auch etwas unhandlich in seiner<br />

Anwendung. Zunächst werden die wichtigsten Eigenschaften des Layouts vorgestellt.<br />

spacing: Standardabstand zwischen den einzelnen Widgets in Pixeln<br />

marginTop: Größe des Randes oberhalb des Layouts in Pixeln<br />

marginLeft: Größe des Randes links des Layouts in Pixeln<br />

marginBottom: Größe des Randes unterhalb des Layouts in Pixeln<br />

marginRight: Größe des Randes rechts des Layouts in Pixeln<br />

marginHeight: Größe des Randes oberhalb und unterhalb des Layouts in Pixeln<br />

marginWidth: Größe des Randes links und rechts des Layouts in Pixeln<br />

Auch beimFormLayout werden die eigentlichen Platzierungsangaben für einzelne Widgets<br />

über Layout-Eigenschaften der Widgets eingestellt. In diesem Fall ist es die Klasse<br />

FormData. Sie nimmt für die vier Seiten des Widgets die Platzierungsbeziehungen auf.<br />

Jede Beziehung wird wiederum durch ein Objekt der KlasseFormAttachment definiert.<br />

Attribute der KlasseFormData:<br />

top: Ausrichtung der oberen Kante des Widgets<br />

left: Ausrichtung der linken Kante des Widgets<br />

bottom: Ausrichtung der unteren Kante des Widgets<br />

right: Ausrichtung der rechten Kante des Widgets<br />

width: Wenn die bevor<strong>zu</strong>gte Breite des Widgets nicht <strong>zu</strong>m Layout passt, dann kann<br />

hier manuell eine Breite vergeben werden. Das sollte wie bei den anderen Layouts<br />

in der Regel nur dann verwendet werden, wenn das Widget nicht in der Lage ist,<br />

eine sinnvolle Breite selbst <strong>zu</strong> berechnen (z.B. mehrzeilige Texteingabefelder).<br />

height: Wenn die bevor<strong>zu</strong>gte Höhe des Widgets nicht <strong>zu</strong>m Layout passt, dann<br />

kann hier eine Höhe vorgegeben werden.<br />

40


3.2 Layout-Management<br />

Schließlich fehlt noch die Klasse FormAttachment, die eine Platzierungsregel für eine<br />

Kante enthält. Wichtig ist hierbei, dass es prinzpiell zwei Möglichkeiten der Anordnung<br />

einer Kante gibt: Sie wird an einer Kante eines Zielwidgets im selben Layout oder an<br />

einer Kante des Vater-Containers (z.B. des Fensters) ausgerichtet.<br />

alignment: Kante des Widgets, an dem sich dieses Widget ausrichten soll. Die<br />

möglichen Werte für die eigene obere oder untere Kante sind:<br />

SWT.TOP: Die Platzierungsangabe bezieht sich auf den oberen Rand des Zielwidgets.<br />

SWT.CENTER: Die Platzierungsangabe bezieht sich auf die Mitte des Zielwidgets.<br />

SWT.BOTTOM: Die Platzierungsangabe bezieht sich auf den unteren Rand des<br />

Zielwidgets.<br />

Die möglichen Werte für die eigene linke oder rechte Kante sind:<br />

SWT.LEFT: Die Platzierungsangabe bezieht sich auf den linken Rand des Zielwidgets.<br />

SWT.CENTER: Die Platzierungsangabe bezieht sich auf die Mitte des Zielwidgets.<br />

SWT.RIGHT: Die Platzierungsangabe bezieht sich auf den rechten Rand des<br />

Zielwidgets.<br />

control: Dieses Attribut nimmt die Referenz des Widgets auf, <strong>zu</strong> dem das eigene<br />

Widget relativ positioniert werden soll. Hat control den Wert null, dann soll die<br />

eigene Widgetseite relativ <strong>zu</strong>m Vater-Container platziert werden.<br />

offset: Absoluter Abstand in Pixeln von der Kante des Zielwidgets<br />

numerator, denominator: Ein Beispiel verdeutlicht die Verwendung dieser beiden<br />

<strong>zu</strong>nächst merkwürdig erscheinenden Attribute: Ein Widget soll mit seinem linken<br />

Rand immer in der Mitte des Dialogs erscheinen. Somit muss sein linker Rand<br />

am linken Rand des Dialogs ausgerichtet werden. Die Angabe „in der Mitte“ wird<br />

durch den Prozentwert der Breite des Dialogs angegeben, hier also 50 % der Breite.<br />

Die beiden Attribute stellen jetzt Zähler (numerator) und Nenner (denominator,<br />

Standardwert ist 100) des Bruchs, mit dem der Prozentwert abgebildet wird, dar.<br />

Somit ergibt sich die aktuelle Position durch folgende Berechnung:<br />

pos = numerator ∗size+offset.<br />

denominator<br />

size ist hierbei in dem Beispiel die automatisch ermittelte Breite des Dialogs.<br />

Damit das Ganze nicht <strong>zu</strong> abstrakt bleibt, zeigen einige Beispiele in den folgenden Abschnitten<br />

die Verwendung der Klassen auf.<br />

3.2.7.2 Beispiel für die relative Anordnung <strong>zu</strong>m Vaterobjekt<br />

Hier soll sich Widget W1 links und rechts am Fenster ausrichten. Sein linker Rand bleibt<br />

also immer am linken Rand des Fenster, sein rechter am rechten Rand des Fenster.<br />

Damit belegt W1 die komplette Fensterbreite.<br />

41


3.2 Layout-Management<br />

FormData data = new FormData();<br />

W1<br />

// Der rechte Rand sitzt bei 100% Breite<br />

// des Fensters (100/100). Der Offset<br />

// ist 0.<br />

data.right = new FormAttachment(100);<br />

// Der linker Rand sitzt bei 0% Breite<br />

// des Fensters (0/100). Der Offset<br />

// ist 0.<br />

data.left = new FormAttachment(0);<br />

w1.setLayoutData(data);<br />

Abbildung 3.39: Beispiel 1 für die Anordnung im Form-Layout<br />

3.2.7.3 Beispiel für die relative Platzierung <strong>zu</strong> einem anderen Widget<br />

Widget W1 wird soll seinen linken Rand immer relativ <strong>zu</strong>m rechten Rand von W2 platzieren.<br />

Der Abstand zwischen beiden beträgt 0 Pixel, was nicht sehr praxisnah ist.<br />

FormData data = new FormData();<br />

// Der rechte Rand schließt direkt an<br />

// den linken Rand von W1 an. Der Abstand<br />

// wird durch das Layout vorgegeben.<br />

data.right = new FormAttachment(w2);<br />

W1<br />

W2<br />

// Der untere Rand sitzt bei 100% Höhe<br />

// des Fensters (100/100). Der Offset<br />

// ist 0.<br />

data.bottom = new FormAttachment(100);<br />

w1.setLayoutData(data);<br />

Abbildung 3.40: Beispiel 2 für die Anordnung im Form-Layout<br />

3.2.7.4 Beispiel für die relative Anordnung <strong>zu</strong>m Vaterobjekt und <strong>zu</strong><br />

einem anderen Widget<br />

W1 und W2 belegen jeweils 50 % der Breite des Fensters und haben in der Mitte einen<br />

<strong>zu</strong>sätzlichen Abstand von 4 Pixeln.<br />

42


3.2 Layout-Management<br />

W1<br />

W2<br />

FormData w1Data = new FormData();<br />

FormData w2Data = new FormData();<br />

// Der rechte Rand von W2 liegt am Fenster.<br />

w2Data.right = new FormAttachment(100);<br />

// Der linke Rand von W2 liegt mit 4 Pixeln<br />

// Abstand rechts von W1.<br />

w2Data.left = new FormAttachment(w1, 4);<br />

// Der rechte Rand von W1 liegt bei 50% der<br />

// Breite (minus 2 Pixel).<br />

w1Data.right = new FormAttachment(50, -2);<br />

// Der linke Rand von W1 liegt am Fenster.<br />

w1Data.left = new FormAttachment(0);<br />

w1.setLayoutData(w1Data);<br />

Abbildung 3.41: Beispiel 3 für die Anordnung im Form-Layout<br />

3.2.7.5 Aus<strong>zu</strong>richtende Zielseiten<br />

Es fällt auf, dass nirgendwo die Kanten, auf die sich die Positionierungen beziehen, angegeben<br />

sind.alignment wird also in der KlasseFormAttachment niemals beschrieben.<br />

Das ist in diesen Beispielen auch nicht notwendig, weil das Form-Layout die Angabe<br />

selbst ermitteln kann.<br />

Beispiel:<br />

W1<br />

W2<br />

Der linke Rand von W2 wird an dem rechten<br />

Rand von W1 ausgerichtet.<br />

Abbildung 3.42: Automatische Randerkennung im Form-Layout<br />

Der rechte Rand von W1 hat den linken von W2 als Nachbarn. Das wird automatisch<br />

erkannt und muss daher nicht angegeben werden. Lediglich in Szenarien, in denen die<br />

Kante des Zielobjektes nicht eindeutig erkennbar ist, muss sie angegeben werden. Das<br />

ist beispielsweise dann der Fall, wenn ein Widget über einem anderen steht und seine<br />

linke oder rechte Kante auf die linke oder rechte Kante des Ziels ausgerichtet werden<br />

muss. Dann kann das Form-Layout nicht entscheiden, welche Kante des Ziels gemeint<br />

ist. Beispiel:<br />

W1<br />

W2<br />

Soll der linke Rand von W2 an dem rechten oder linken<br />

Rand von W1 ausgerichtet werden?<br />

Abbildung 3.43: Keine automatische Randerkennung im Form-Layout<br />

43


3.2 Layout-Management<br />

3.2.7.6 Durchgängiges Beispiel<br />

Ein vollständiges Beispiel (FormLayoutApplication) schließt die Betrachtung des<br />

Form-Layouts ab. Viele der vorher vorgestellten Attribute lassen sich vergleichsweise<br />

bequem über die Kontruktoren setzen. Als Basis dient dieser Screenshot:<br />

Abbildung 3.44: Screenshot des Beispiels<br />

Die Rahmen um das Widget für die Titelzeile sowie das Bild dienen nur da<strong>zu</strong>, deren<br />

Ausmaße <strong>zu</strong> erkennen. Auch für dieses Beispiel gilt, dass es bereits vordefinierte Dialoge<br />

mit den Standardtasten gibt und dass diese der manuellen Programmierung vor<strong>zu</strong>ziehen<br />

sind. Im Layout erhalten alle Widgets einen Abstand untereinander von 4 Pixeln. Das ist<br />

auch der Abstand <strong>zu</strong>m Fensterrand.<br />

FormLayout layout = new FormLayout();<br />

layout.marginHeight = 4; // oberer und unterer Rand<br />

layout.marginWidth = 4; // linker und rechter Rand<br />

layout.spacing = 4; // Abstände zwischen den Komponenten<br />

shell.setLayout(layout);<br />

Im nächsten Schritt wird die Titelzeile eingefügt. Sie ist ein Label, das sich immer über<br />

die komplette Fensterbreite erstrecken soll und oben am Fenster platziert ist. Der untere<br />

Rand wird nicht festgelegt. Seine Position ergibt sich aus der des oberen <strong>zu</strong>züglich der<br />

bevor<strong>zu</strong>gten Höhe.<br />

Abbildung 3.45: Einfügen des Titels<br />

Label label = new Label(shell, SWT.BEGINNING | SWT.BORDER);<br />

label.setText("Titel");<br />

FormData data = new FormData();<br />

// linker Rand bei 0% Fensterbreite<br />

data.left = new FormAttachment(0);<br />

// rechter Rand bei 100% Fensterbreite<br />

data.right = new FormAttachment(100);<br />

label.setLayoutData(data);<br />

44


3.2 Layout-Management<br />

Jetzt wird die Taste für den Abbruch („Cancel“) in den Dialog eingefügt. Sie sitzt immer<br />

am unteren rechten Rand des Dialogs. Die Positonen ihres oberen und linken Randes<br />

ergeben sich dann aus der bevor<strong>zu</strong>gten Größe.<br />

Abbildung 3.46: Einfügen der Abbruch-Taste<br />

Button cancelButton = new Button(shell, SWT.PUSH);<br />

cancelButton.setText("Cancel");<br />

data = new FormData();<br />

// rechter Rand bei 100% Fensterbreite<br />

data.right = new FormAttachment(100);<br />

// unterer Rand bei 100% Fensterhöhe<br />

data.bottom = new FormAttachment(100);<br />

cancelButton.setLayoutData(data);<br />

Jetzt kann die Ok-Taste links neben die Abbruch-Taste gesetzt werden. Ihr rechter Rand<br />

positioniert sich relativ <strong>zu</strong>m linken Rand der Abbruch-Taste. Die Abstände zwischen den<br />

Tasten ergeben sich aus demspacing-Wert des Layouts. Sollen beide Tasten eine identische<br />

Breite aufweisen, dann müsste in das Form-Layout ein Fill-Layout eingefügt werden,<br />

das beide Tasten aufnimmt.<br />

Abbildung 3.47: Einfügen der Ok-Taste<br />

Button okButton = new Button(shell, SWT.PUSH);<br />

okButton.setText("Ok");<br />

data = new FormData();<br />

// rechten Rand an der Cancel-Taste ausrichten<br />

data.right = new FormAttachment(cancelButton);<br />

// unterer Rand bei 100% Fensterhöhe<br />

data.bottom = new FormAttachment(100);<br />

okButton.setLayoutData(data);<br />

Das Bild am linken Rand soll in der Höhe, aber nicht in der Breite wachsen. Es wird daher<br />

mit seinem linken Rand am Fenster, mit seinem oberen am Titel und mit seinem unteren<br />

an der Abbruch-Taste befestigt. Nur durch die Anbindung an die Taste wächst das Bild in<br />

45


3.2 Layout-Management<br />

der Höhe mit, weil die Taste ja bei einer Dialogvergrößerung „nach unten wandert“ und<br />

ihre Größe beibehält.<br />

Abbildung 3.48: Einfügen des Bildes<br />

Label dukeLabel = new Label(shell, SWT.CENTER | SWT.BORDER);<br />

dukeLabel.setImage(imageRegistry.get("duke"));<br />

data = new FormData();<br />

// linker Rand bei 0% Fensterbreite<br />

data.left = new FormAttachment(0);<br />

// oberen Rand am Titel-Label ausrichten<br />

data.top = new FormAttachment(label);<br />

// unteren Rand an der Cancel-Taste ausrichten<br />

data.bottom = new FormAttachment(cancelButton);<br />

dukeLabel.setLayoutData(data);<br />

Abschließend fehlt noch das mehrzeilige Texteingabefeld mit seinen Scrollbalken. Es soll<br />

sowohl in der Höhe als auch in der Breite mitwachsen. Da<strong>zu</strong> werden sein linker Rand am<br />

Bild, sein oberer Rand am Titel, sein rechter Rand am Fensterrahmen und sein unterer<br />

Rand an der Abbruch-Taste ausgerichtet. Durch die beiden <strong>zu</strong>letzt genannten Bedingungen<br />

kann der Text bei Größenänderung des Dialogs mitwachsen oder auch schrumpfen.<br />

Abbildung 3.49: Einfügen des Textfeldes<br />

Text text = new Text(shell, SWT.MULTI | SWT.WRAP | SWT.BORDER<br />

| SWT.H_SCROLL | SWT.V_SCROLL);<br />

data = new FormData();<br />

// linken Rand am Duke-Label (Bild) ausrichten<br />

data.left = new FormAttachment(dukeLabel);<br />

// oberen Rand am Titel-Label ausrichten<br />

data.top = new FormAttachment(label);<br />

// rechter Rand ist bei 100% Fensterbreite<br />

data.right = new FormAttachment(100);<br />

// unteren Rand an der Cancel-Taste ausrichten<br />

data.bottom = new FormAttachment(cancelButton);<br />

text.setLayoutData(data);<br />

46


3.2 Layout-Management<br />

3.2.8 Form-Layout (JGoodies)<br />

Das Form-Layout erlaubt zwar eine sehr flexible Gestaltung von Dialogen, hat aber die<br />

Mängel, dass einerseits eine große Menge Code geschrieben werden muss und andererseits<br />

das Vorgehen häufig als wenig intuitiv empfunden wird. Der zweite Punkt rührt<br />

daher, dass viele Entwickler ein Raster auf einen Dialogentwurf legen und so in Zeilen<br />

und Spalten denken. Genau das wird beim Form-Layout aber nicht direkt sichtbar. Das<br />

FormLayout von JGoodies schafft hier auf eine recht komfortable Art und Weise Abhilfe.<br />

An dieser Stelle soll keine komplette Einführung in das FormLayout erfolgen. Unter<br />

der URL http://ffxml.net/swtforms.html lässt sich die SWT-Portierung der eigentlich<br />

für Swing geschriebenen Klassenbibliothek mit einer sehr guten Dokumentation<br />

kostenlos herunterladen.<br />

3.2.8.1 Grundidee<br />

Im Form-Layout werden die einzelnen Komponenten tabellarisch angeordnet. Im Gegensatz<br />

<strong>zu</strong>m Form-Layout aus SWT lassen sich die Struktur der Tabelle und das Verhalten<br />

der einzelnen Zeilen und Spalten vorab durch einen String festlegen. Dadurch fallen die<br />

einzelnen Ausrichtungen beim Einfügen von Komponenten weg. Weiterhin erlaubt das<br />

Form-Layout auch Größenangaben in Form so genannter „Dialog Units“. Werden jetzt<br />

Abstände nicht pixel-genau sondern durch Dialog Units spezifiziert, dann passen sich<br />

die Abstände automatisch an, wenn beispielsweise die Zeichensatzgröße im Dialog oder<br />

die Pixelgröße des Bildschirm verändert werden. Durch eine Pixelangabe dagegen wären<br />

die Abstände bei vergrößerten Zeichensätzen dann <strong>zu</strong> klein.<br />

Die Abbildung 3.50 zeigt einen Beispieldialog mit dem <strong>zu</strong>grunde liegenden tabellarischen<br />

Raster. Wichtig ist hier, dass auch die Abstände zwischen den Komponenten als einzelne<br />

Zeilen und Spalten beschrieben werden.<br />

FormLayout layout = new FormLayout(<br />

"4dlu, left:pref, 4dlu, fill:max(80dlu;pref):grow, 4dlu, pref, 4dlu",<br />

"4dlu, pref, 4dlu, fill:pref:grow, 4dlu, fill:pref:grow", 4dlu);<br />

Zeilen<br />

fill:max(80dlu;pref):grow<br />

Abbildung 3.50: Dialog mit Form-Layout<br />

Die Definition der Zeilen und Spalten wird in Form zweier Strings an den Layout-Manager<br />

übergeben. Der erste String beinhaltet die Spaltenmaße:<br />

1. Die erste Spalte ist lediglich ein Abstandshalter <strong>zu</strong>m linken Rand mit einer Breite<br />

von vier Dialog-Einheiten („Dialog Units“).<br />

2. Die Spalte mit dem „Duke“ soll in der bevor<strong>zu</strong>gten Breite (pref) linksbündig (left)<br />

gesetzt werden. Die linksbündige Angabe ist eigentlich überflüssig, da sich in dieser<br />

Spalte nur eine Komponente befindet.<br />

3. Jetzt folgt wieder ein Platzhalter von vier Dialog-Einheiten.<br />

47


3.2 Layout-Management<br />

4. Die vierte Spalte ist etwas komplizierter definiert. Sie soll beim Start ihre bevor<strong>zu</strong>gte<br />

Breite (pref) bzw. mindestens 80 Dialog-Einheiten einnehmen. Die Angabe<br />

(80dlu,pref) beinhaltet ein Intervall mit einer Untergrenze von 80 Dialogeinheiten<br />

und einer Obergrenze, der bevor<strong>zu</strong>gten Breite. Die Spalte kann <strong>zu</strong>sätzlichen<br />

Platz belegen (fill) und sie darf, wenn der Dialog größer wird, auch in die Höhe<br />

wachsen (grow).<br />

5. Dann folgt der Platzhalter, der einen festen Abstand <strong>zu</strong> den beiden Tasten erzwingt.<br />

6. Die Tasten sollen in ihren bevor<strong>zu</strong>gten Breiten angeordnet werden.<br />

7. Abschließend kommt ein Platzhalter, der den Abstand <strong>zu</strong>m rechten Rand definiert.<br />

Nach den Spalten fehlt noch die Festlegung der Zeilen:<br />

1. Die erste Zeile ist ein Abstandshalter <strong>zu</strong>m oberen Rand mit einer Höhe von vier<br />

Dialog-Einheiten.<br />

2. Die Spalte mit der Überschrift soll in der bevor<strong>zu</strong>gten Höhe der Komponenten gesetzt<br />

werden. Hier bestimmt nur die bevor<strong>zu</strong>gte Höhe des Labels „Titel“ den Platz.<br />

3. Jetzt folgt wieder ein Platzhalter von vier Dialog-Einheiten.<br />

4. Die vierte Zeile besitzt drei Angaben. Sie soll beim Start ihre bevor<strong>zu</strong>gte Höhe einnehmen<br />

(pref), sie kann <strong>zu</strong>sätzlichen Platz belegen (fill) und sie darf, wenn der<br />

Dialog größer wird, auch in die Höhe wachsen (grow).<br />

5. Dann folgt der Platzhalter, der einen festen Abstand zwischen den beiden Tasten<br />

erzwingt.<br />

6. Diese Zeile verhält sich genau wie die vierte.<br />

7. Abgeschlossen wird die Beschreibung durch einen Platzhalter, der den Abstand <strong>zu</strong>m<br />

unteren Rand definiert.<br />

3.2.8.2 Einfügen von Komponenten im Beispiel<br />

Nachdem das Grundgerüst des Dialogs steht, lassen sich jetzt sehr einfach die Komponenten<br />

einfügen. In der Regel ist es ausreichend, ihre Position im Raster sowie die<br />

Anzahl Zeilen und Spalten, über die sich sich erstrecken, an<strong>zu</strong>geben. Dabei ist <strong>zu</strong> beachten,<br />

dass die Zählung der Zeilen und Spalten bei 1 beginnt! DerPanelBuilder hilft<br />

beim Einfügen von Widgets in den Vater-Container. Da<strong>zu</strong> werden ihm Bedingungen in<br />

Form der Klasse CellConstraints übergeben. Diese bestehen in der Regel aus der<br />

X- und Y-Position der Zelle, in der das Widget platziert sein soll. Zusätzlich lassen sich<br />

auch die Anzahl Spalten und Zeilen angeben, die ein Widget belegen soll. Fehlt diese<br />

Angabe, dann belegt das Widget genau eine Zelle.<br />

// Der Panel-Builder hilft beim Einfügen von Komponenten<br />

// in das Layout. Er bekommt als zweiten Parameter<br />

// den Container übergeben, in den die Widgets<br />

// eingefügt werden.<br />

PanelBuilder builder = new PanelBuilder(layout, shell);<br />

// Die Bedingungen werden später durch Aufruf von<br />

// Methoden übergeben. Das Objekt kann<br />

// wiederverwendet werden.<br />

CellConstraints cc = new CellConstraints();<br />

48


3.2 Layout-Management<br />

Die Titelzeile erstreckt sich über eine Zeile und fünf Spalten, wobei die Platzhalterspalten<br />

mitgezählt werden.<br />

Abbildung 3.51: Einfügen des Titels<br />

// Der Text über allen Komponenten befindet sich in<br />

// Zelle (2,2) und erstreckt sich über 5 Spalten<br />

// und eine Zeile.<br />

Label label = new Label(shell, SWT.BEGINNING);<br />

label.setText("Titel");<br />

builder.add(label, cc.xywh(2, 2, 5, 1));<br />

Das mehrzeilige Texteingabefeld erstreckt sich über eine Spalte und drei Zeilen.<br />

Abbildung 3.52: Einfügen des Textfeldes<br />

Text text = new Text(shell, SWT.MULTI | SWT.WRAP<br />

| SWT.BORDER | SWT.H_SCROLL<br />

| SWT.V_SCROLL);<br />

builder.add(text, cc.xywh(4, 4, 1, 3));<br />

Als letztes Widget in diesem Beispiel soll noch die Ok-Taste eingefügt werden. Sie belegt<br />

nur eine einzige Zelle.<br />

Abbildung 3.53: Einfügen der Ok-Taste<br />

Button button = new Button(shell, SWT.PUSH);<br />

button.setText("Ok");<br />

builder.add(button, cc.xy(6, 4));<br />

Das Form-Layout von JGoodies unterstützt auch identische Spaltenbreiten und Zeilenhöhen<br />

(siehe API-Dokumentation).<br />

49


3.3 SWT-Widgets<br />

3.2.9 GUI-Designer<br />

Es gibt Entwickler, denen die manuelle Programmierung von Layouts wenig Spaß bereitet.<br />

Daher bietet es sich in der Praxis an, die Oberfläche mit einen GUI-Editor interaktiv<br />

<strong>zu</strong> erzeugen. Unterstüt<strong>zu</strong>ng bieten unter Anderem:<br />

WindowBuilder Pro für Eclipse (kostenlos unter<br />

http://code.google.com/webtoolkit/tools/wbpro/index.html)<br />

Visual Editor Plugin für Eclipse (noch etwas „unfertig“, unterstützt nicht immer die<br />

aktuellsten Eclipse-Versionen, http://www.eclipse.org/vep/)<br />

Jigloo-Plugin für Eclipse (www.cloudgarden.com/jigloo/, kostenlos für den<br />

nicht-kommerziellen Einsatz)<br />

Hier können die Komponenten visuell den Zellen <strong>zu</strong>geordnet werden. Ein eigener Editor<br />

erlaubt die Änderung von Attributen einer Komponente oder eines Constraints. Der<br />

Nachteil des Ansatzes besteht darin, dass die mit einer IDE interaktiv erstellten Oberflächen<br />

sich nicht immer in einer anderen IDE interaktiv verändern lassen, da GUI-Editoren<br />

nicht zwangsweise dieselben Techniken verwenden, um die erzeugten Oberflächen so<br />

ab<strong>zu</strong>speichern, dass sie bearbeitbar bleiben. Hier hilft nach einem IDE-Wechsel gelegentlich<br />

nur noch der Weg der manuellen Programmierung. Ein GUI-Designer entbindet<br />

den Entwickler aber nicht davon, Layout-Manager und deren Verhalten <strong>zu</strong> kennen. Auch<br />

ein GUI-Designer verwendet für die Platzierung Layouts.<br />

3.3 SWT-Widgets<br />

In diesem Kapitel werden einige wichtige Widgets aus SWT vorgestellt. Das Kapitel 3.7<br />

stellt anhand eines Widgets aus JFace vor, wie es das entsprechende Gegenstück aus<br />

SWT kapselt und ihm einen sogenannten Model-View-Controller-Ansatz (MVC) hin<strong>zu</strong>fügt.<br />

Sie finden an dieser Stelle keine Erklärung der kompletten SWT-API. Diese würde sicherlich<br />

mehrere Bücher füllen. Statt dessen erhalten Sie eine Übersicht über die wichtigsten<br />

Komponenten, die <strong>zu</strong>m Bau einer Oberfläche benötigt werden. Diese Komponenten sind<br />

aber nicht unabhängig voneinander. Sie stehen vielmehr über eine Vererbungshierarchie<br />

mit gemeinsamen Basisklassen in Verbindung (siehe Abschnitt 3.5).<br />

Die folgende Aufstellung ist nicht vollständig. Sie soll Ihnen lediglich eine erste Orientierung<br />

<strong>zu</strong>r Auswahl und <strong>zu</strong>r Suche in der SWT-API geben. Sehen Sie sich einfach<br />

einmal die Übersicht über wichtige Widgets unter http://www.eclipse.org/swt/<br />

widgets/ an. Neben den Widgets aus dem Paket org.eclipse.swt existieren auch<br />

weitere im Paket org.eclipse.swt.custom. Diese haben ähnliche Namen wie die<br />

aus org.eclipse.swt, besitzen aber teilweise ein erweitertes Verhalten oder wurden<br />

in ihrer Darstellung so angepasst, dass sie optimal in Tabellen eingefügt werden können.<br />

50


3.3 SWT-Widgets<br />

Tabelle 3.1: Wichtige Widgets<br />

Name<br />

Browser<br />

Verhalten und Verwendung<br />

Der Browser ist ein Widget, das einen im Betriebssystem installierten<br />

Browser kapselt.<br />

Button<br />

(SWT.PUSH)<br />

Button<br />

(SWT.FLAT)<br />

EinButton, der mit der KonstantenSWT.PUSH erzeugt wurde, ist eine „normale“<br />

Taste. Sie kann gedrückt werden, wobei der Selektions<strong>zu</strong>stand nicht<br />

erhalten bleibt. Sie dient da<strong>zu</strong>, Aktionen an<strong>zu</strong>stoßen, indem Ereignisse ausgelöst<br />

werden.<br />

Die Taste verhält sich wie die mit der Konstanten SWT.PUSH erzeugte. Sie<br />

weist aber eine andere (flachere) Rahmendarstellung auf.<br />

Button<br />

(SWT.ARROW |<br />

SWT.LEFT)<br />

Button<br />

(SWT.CHECK)<br />

Button<br />

(SWT.RADIO)<br />

Button<br />

(SWT.TOGGLE)<br />

Combo<br />

(SWT.DROP_DOWN),<br />

CCombo<br />

Combo<br />

(SWT.SIMPLE)<br />

Die Taste verhält sich wie die mit der Konstanten SWT.PUSH erzeugte. Anhand<br />

der <strong>zu</strong>sätzlich übergebenen Konstante erzeugt sie eine Pfeildarstellung.<br />

Erlaubt sindSWT.LEFT,SWT.RIGHT,SWT.UP undSWT.DOWN.<br />

Es handelt sich um eine Taste, die selektiert und deselektiert werden kann.<br />

Der Selektions<strong>zu</strong>stand bleibt bis <strong>zu</strong>r Änderung erhalten. Die Komponente<br />

dient da<strong>zu</strong>, eine Möglichkeit an- oder ab<strong>zu</strong>wählen, ohne dass dadurch die<br />

Belegung einer anderen Komponente direkt beeinflusst wird. Beispiel: Fettund<br />

Kursivschrift können unabhängig von einander bestimmt werden.<br />

Ein Button, der mit der Konstanten SWT.RADIO erzeugt wurde, ist eine<br />

Taste, die sich an- oder abwählen lässt. Befinden sich andere solcher Tasten<br />

im Layout in ihrer Nähe, dann bilden diese eine Gruppe, von der immer<br />

nur eine Taste <strong>zu</strong> einem Zeitpunkt selektiert sein kann. Dadurch ist sichergestellt,<br />

dass bei Auswahl einer Belegung keine andere in der Gruppe möglich<br />

ist. Beispiel: Die Auswahl einer Textausrichtung für links- und rechtsbündig<br />

ist nicht gleichzeitig sinnvoll.<br />

Diese ist eine Taste, die wie eine Taste mit der KonstantenSWT.CHECK eingesetzt<br />

werden kann. Lediglich die Darstellung unterscheidet sich. Diese<br />

Taste wird sehr häufig mit einem eigenen Icon verwendet. Beispiel: Auswahl<br />

einer Textausrichtung<br />

Eine Combobox zeigt einen Eintrag aus einer Liste möglicher Einträge an.<br />

Der Eintrag kann frei gewählt werden. Je nach Einstellung der Combobox ist<br />

sogar die manuelle Eingabe eines Wertes möglich. Die Liste ist normalerweise<br />

verborgen und wird erst durch Interaktion mit dem Benutzer sichtbar.<br />

Die Klasse CCombo ist eine Combobox, die für die Darstellung in einer Tabellenzelle<br />

angepasst wurde.<br />

Wie eine Combobox mit der Konstanten SWT.DROP_DOWN erlaubt sie die<br />

Selektion aus bestimmten Vorgaben. Hin<strong>zu</strong> kommt aber noch, dass eine<br />

Mehrfachauswahl erlaubt werden kann. Weiterhin ist die maximale Anzahl<br />

gleichzeitig sichtbarer Vorgaben einstellbar. Die manuelle Eingabe neuer<br />

Werte ist allerdings nicht möglich.<br />

Date<br />

(SWT.DATE)<br />

Das Datumswidget erlaubt nur die Eingabe korrekter Datumswerte. Dieses<br />

kann entweder durch eine manuelle Eingabe oder durch Drücken der Tasten<br />

neben dem Feld erfolgen. Dieses Widget ist daher für die Datumseingabe<br />

einem einfachen Textfeld vor<strong>zu</strong>ziehen.<br />

51


3.3 SWT-Widgets<br />

Name<br />

Date<br />

(SWT.TIME)<br />

Date<br />

(SWT.CALENDAR)<br />

Verhalten und Verwendung<br />

Das Datumswidget erlaubt durch Setzen der KonstantenSWT.TIME statt einer<br />

Datums- eine korrekten Zeiteingabe. Dieses kann entweder durch eine<br />

manuelle Eingabe oder durch Drücken der Tasten neben dem Feld erfolgen.<br />

Dieses Widget ist daher für die Zeiteingabe einem einfachen Textfeld<br />

vor<strong>zu</strong>ziehen.<br />

Das Datumswidget erlaubt durch Setzen der Konstanten SWT.CALENDAR<br />

die komfortable Auswahl eines Datums aus einem Kalender.<br />

Group<br />

Eine Gruppe ist ein Container-Objekt, das für Radio-Buttons verwendet wird.<br />

Es gruppiert diese logisch und optisch. Es kann immer nur eine Taste dieser<br />

Gruppe <strong>zu</strong> einem Zeitpunkt selektiert sein.<br />

Label,CLabel<br />

Link<br />

List<br />

Das Label dient als Erklärung mit einer textuellen Bezeichnung bzw. einem<br />

Bild für eine andere Komponente. Es erlaubt keine direkte Eingabe von Werten.<br />

Die Klasse CLabel erweitert das normale Label um eine gleichzeitige<br />

Darstellung von Bild und Text. Es bietet einen Farbverlauf im Hintergrund,<br />

ein Hintergrundbild sowie automatische Textkür<strong>zu</strong>ngen, falls der Text <strong>zu</strong> lang<br />

sein sollte.<br />

Der Link zeigt einen Text an, innerhalb dessen der auswählbare Teil mit<br />

Text markiert ist. So kann z.B. innerhalb eines Textes in einem<br />

Dialog ein Verweis auf die <strong>zu</strong>gehörige Hilfe eingebaut werden.<br />

Eine List funktioniert ähnlich wie ein Combo-Widget mit der Konstanten<br />

SWT.SIMPLE.<br />

ProgressBar<br />

SWT.SMOOTH<br />

ProgressBar<br />

SWT.INDETERMINATE<br />

Scale<br />

ScrollBar<br />

Slider<br />

Der Fortschrittsbalken wird mit dieser Konstanten verwendet, um den aktuellen<br />

Stand einer eine länger laufenden Operation für eine bekannte Anzahl<br />

von Schritten bis <strong>zu</strong>m Ende an<strong>zu</strong>zeigen.<br />

Der Fortschrittsbalken wird der Konstanten SWT.INDETERMINATE eingesetzt,<br />

um den aktuellen Stand einer eine länger laufenden Operation für<br />

eine unbekannte Anzahl an Schritten bis <strong>zu</strong>m Ende an<strong>zu</strong>zeigen.<br />

Der Schieberegler erlaubt die Auswahl eines ganzzahligen Wertes aus einem<br />

vorgegebenen Bereich. Der Regler kann sowohl horizontal als auch<br />

vertikal angeordnet werden und an vorgegebenen Markierungen einrasten.<br />

Scrollbalken lassen sich vertikal und horizontal ausrichten. Normalerweise<br />

werden sie nicht direkt erzeugt, sondern durch Angabe einer entsprechenden<br />

Konstante beim Erzeugen eines Widgets, dass das Scrollen unterstützt.<br />

Bisher wurde in diesem <strong>Skript</strong> immer ein mehrzeiliges Texteingabefeld verwendet,<br />

dessen Inhalt gescrollt werden soll, wenn er <strong>zu</strong> groß für das Widget<br />

ist. Die Scrollbalken können mit Hilfe der Methoden getVerticalBar()<br />

bzw.getHorizontalBar() ausgelesen werden.<br />

Der Schieberegler erlaubt die Auswahl eines ganzzahligen Wertes aus einem<br />

vorgegebenen Bereich. Der Regler kann sowohl horizontal als auch<br />

vertikal angeordnet werden und an vorgegebenen Markierungen einrasten.<br />

52


3.3 SWT-Widgets<br />

Name<br />

Spinner<br />

StyledText<br />

Table<br />

Text<br />

Tree<br />

Verhalten und Verwendung<br />

Mit Hilfe dieses Widgets lassen sich ganzzahlige Werte bequem einstellen.<br />

Es können eine Obergrenze sowie die Schrittweite der Erhöhungen vergeben<br />

werden.<br />

Dieses Textfeld ist Text sehr ähnlich. Allerdings erlaubt es variable Textattribute.<br />

Ein Beispiel da<strong>zu</strong> ist in Abschnitt 3.7.3 <strong>zu</strong> finden.<br />

Die Table erlaubt die tabellarische Darstellung und Eingabe von Daten.<br />

Die Art der Darstellung kann vom Entwickler beeinflusst werden. Einzelne<br />

Zellen, Spalten, Zeilen oder Bereiche lassen sich auswählen. Die Abschnitte<br />

3.7.1 und 3.7.2 behandelen die Konzepte (ohne und mit MVC-Ansatz)<br />

genauer.<br />

Es handelt sich hierbei um ein einzeiliges Freitexteingabefeld mit Cursorsteuerung<br />

und Selektion. Variable Textattribute werden nicht unterstützt. Somit<br />

haben alle Zeichen dieselbe Größe, Farbe und einen identischen Zeichensatz.<br />

Das Textfeld wird auch für Passworteingaben verwendet. Da<strong>zu</strong><br />

muss die KonstanteSWT.PASSWORD übergeben werden.<br />

Ein Tree erlaubt die baumartige Darstellung mit ein- und ausklappbaren<br />

Knoten. Die Art der Darstellung kann ähnlich wie bei der Tabelle vom Entwickler<br />

beeinflusst werden.<br />

Daneben besitzt SWT eine ganze Anzahl weiterer Widgets. So wurde das Composite<br />

als Container für andere Widgets bereits vorgestellt. Weitere Container werden später<br />

im Abschnitt 3.8 im <strong>Skript</strong> betrachtet. Interessant ist auch dasCanvas-Widget. Innerhalb<br />

seiner Fläche kann der Entwickler durch Zeichenoperationen den Inhalt komplett frei<br />

gestalten. Weitere Widgets, die bisher nicht Bestandteil von SWT sind, befinden sich im<br />

Eclipse-Projekt Nebula (http://www.eclipse.org/nebula/).<br />

Abbildung 3.54: Einige Widgets aus dem Nebula-Projekt<br />

Aber auch außerhalb der Eclipse-Seiten gibt es viele Widgets, die <strong>zu</strong>m Teil kostenlos verfügbar<br />

sind. Ein Beispiel dafür ist die „RCPToolbox“ (http://www.richclientgui.<br />

com/detail.php?product_id=1). Sie besitzt unter Anderem ein Widget <strong>zu</strong>r Anzeige<br />

von Karten aus „Google Maps“.<br />

53


3.4 Menüs und Toolbar-Leisten<br />

Abbildung 3.55: Einige Widgets aus der RCPToolbox<br />

3.4 Menüs und Toolbar-Leisten<br />

Diese Kapitel zeigt die Verwendung von Menüs und Toolbar-Leisten mit reinem SWT.<br />

Später in Abschnitt 3.6.3 werden Sie sehen, dass unter Verwendung von JFace einige<br />

Schritte deutlich einfacher umgesetzt werden können.<br />

3.4.1 Normale Menüs<br />

Menüs setzen sich aus einer Anzahl unterschiedlicher Komponenten <strong>zu</strong>sammen, die im<br />

Folgenden dargestellt werden:<br />

Mnemonic<br />

MenuItem<br />

(SWT.CASCADE)<br />

MenuItem<br />

(SWT.NONE)<br />

MenuItem<br />

(SWT.CHECK)<br />

Menu (SWT.BAR)<br />

Accelerator<br />

Menu<br />

Abbildung 3.56: Komponenten in einem Menü<br />

54


3.4 Menüs und Toolbar-Leisten<br />

.CHECK)<br />

In Menüs dürfen auch Radio-Tasten dargestellt werden:<br />

MenuItem<br />

(SWT.RADIO)<br />

MenuItem<br />

(SWT.SEPARATOR)<br />

Abbildung 3.57: Radio-Tasten in einem Menü<br />

Bei Menüs werden ebenso wie bei den bisher vorgestellten Widgets Konstanten verwendet,<br />

um eigentlich unterschiedliche Klassen an<strong>zu</strong>sprechen.<br />

Menu(SWT.BAR): Das ist die Menüleiste selbst, die innerhalb eines Fensters platziert<br />

wird. Die Menüleiste nimmt die einzelnen Menüs auf.<br />

Menu (ohne Konstante): Ein solches Menü ist ein einzelnes Menü in der Menüleiste<br />

oder ein Untermenü in einem anderen Menü.<br />

MenuItem (SWT.CASCADE): Hierbei handelt es sich um einen Eintrag in einem<br />

Menü oder in der Menüleiste, der ein Untermenü enthält.<br />

MenuItem (SWT.NONE): Dieser Eintrag verhält sich wie eine Taste. Wenn er ausgewählt<br />

wird, löst er genau dieselben Ereignisse wie eine Taste aus. Es handelt sich<br />

also um einen „normalen“ Menüeintrag.<br />

MenuItem(SWT.CHECK): Hierbei handelt es sich um eine Checkbox, die dem Aussehen<br />

und Verhalten der Menüeinträge angepasst wurde. Ein solcher Eintrag kann<br />

also gewählt und auch wieder abgewählt werden.<br />

MenuItem (SWT.RADIO): Dieses Element entspricht einem Button mit der Konstanten<br />

SWT.RADIO, dessen Aussehen dem der Menüeinträge angepasst wurde.<br />

So können einzelne Auswahlen (z.B. von Optionen) direkt im Menü angezeigt werden.<br />

Um eine Selektion aus einer Anzahl solcher Tasten <strong>zu</strong> ermöglichen, werden<br />

Radio-Tasten <strong>zu</strong> Gruppen <strong>zu</strong>sammengefasst. Dieses geschieht automatisch anhand<br />

der Einträge vor und nach den Radio-Tasten. Alle <strong>zu</strong>sammengehörig platzierten<br />

Radio-Tasten bilden eine Gruppe, in der immer nur ein Eintrag ausgewählt sein<br />

kann.<br />

MenuItem(SWT.SEPARATOR): Dieser Eintrag ist ein optisches Trennelement zwischen<br />

zwei Menüeinträgen. So lassen sich logisch unterschiedliche Gruppen von<br />

Einträgen optisch trennen.<br />

Accelerator: Menüs sollen nicht nur mit der Maus, sondern auch über die Tastatur<br />

bedient werden können. Durch einen Accelerator kann ein Menüpunkt aufgerufen<br />

werden, ohne dass das <strong>zu</strong>gehörige Menü überhaupt sichtbar ist (z.B.F2,Ctrl-C).<br />

Der Accelerator wird normalerweise rechts innerhalb des Menüeintrags dargestellt.<br />

Mnemonic: Mittels Mnemonics kann durch die Tastatur innerhalb des Menüs navigiert<br />

werden. Ein Mnemonic ist ein einzelnes Zeichen, das in der Regel durch einen<br />

unterstrichenen Buchstaben im <strong>zu</strong>gehörigen Text dargestellt wird. Die Navigation<br />

erfolgt plattformabhängig (z.B. mit ALT-C unter Windows). Weil Apple die Verwendung<br />

von Mnemonics in seinen eigenen Richtlinien nicht empfiehlt, werden diese<br />

unter Mac OS X gar nicht erst dargestellt.<br />

55


3.4 Menüs und Toolbar-Leisten<br />

3.4.1.1 Bau von Menüstrukturen<br />

Der folgenden Schritte zeigen die Konstruktion eines Menüs, das aus mehreren Einträge<br />

besteht (QuelltextMenuApplication.java). Die Tastatursteuerung wird erst später im<br />

Abschnitt 3.4.1.6 ergänzt.<br />

1. Zuerst wird eine Menüleiste erzeugt und am Fenster registriert. Da<strong>zu</strong> wird der Leiste<br />

das Fenster als Vater-Element übergeben.<br />

Menu menubar = new Menu(shell, SWT.BAR);<br />

shell.setMenuBar(menubar);<br />

2. Jetzt wird der erste Eintrag in der Menüleiste erzeugt. An diesen Eintrag soll im<br />

Anschluss daran ein Menü angehängt werden.<br />

MenuItem fileItem = new MenuItem(menubar, SWT.CASCADE);<br />

fileItem.setText("File");<br />

3. Das Menü in Form eines Untermenüs kann an den erzeugten Menüeintrag angehängt<br />

werden. Das Untermenü hat den Eintrag, über den es aufgeklappt wird, als<br />

Vaterelement.<br />

Menu fileMenu = new Menu(menubar); // Drop-Down-Menue<br />

fileItem.setMenu(fileMenu);<br />

4. Das neue Untermenü wird mit zwei Einträgen („New“ und „Open“) gefüllt.<br />

MenuItem item = new MenuItem(fileMenu, SWT.NONE);<br />

item.setText("New");<br />

item = new MenuItem(fileMenu, SWT.NONE);<br />

item.setText("Open");<br />

Das ist das Ergebnis der Konstruktion, wobei die Schritte oben nicht ganz vollständig<br />

sind. Es werden nicht alls Menü-Einträge erzeugt. Außerdem fehlt noch die Tastatursteuerung.<br />

Abbildung 3.58: Ausgabe des ersten Menü-Beispiels<br />

Dürfen gewisse Einträge <strong>zu</strong> bestimmten Zeitpunkte nicht verwendet werden, dann bietet<br />

es sich an, diese mitsetEnabled(false) einfach <strong>zu</strong> sperren.<br />

3.4.1.2 Checkbox als Menü-Eintrag<br />

Menüs dürfen Tasten enthalten, die ihren Selektions<strong>zu</strong>stand speichern. Diese entsprechen<br />

denButton-Widgets mit der KonstantenSWT.CHECK.<br />

MenuItem checkItem = new MenuItem(fileMenu, SWT.CHECK);<br />

checkItem.setText("Check");<br />

checkItem.setSelection(true); // vorausgewählt<br />

56


3.4 Menüs und Toolbar-Leisten<br />

Das ist das Ergebnis, nachdem den Schritten aus Abschnitt 3.4.1.1 der Checkbox-Eintrag<br />

hin<strong>zu</strong>gefügt wurde:<br />

Abbildung 3.59: Hin<strong>zu</strong>fügen der Checkbox <strong>zu</strong>m Menü<br />

3.4.1.3 Radio-Taste als Menü-Eintrag<br />

Darf immer nur eine Selektion aus einer Gruppe von Einträgen aktiv sein, dann sollten<br />

Radio-Tasten verwendet werden. Diese entsprechen den Button-Widgets mit der KonstantenSWT.RADIO.<br />

MenuItem redItem = new MenuItem(editMenu, SWT.RADIO);<br />

redItem.setText("Red");<br />

redItem.setSelection(true); // vorausgewählt<br />

Hier sehen Sie die Ausgabe. Der Trennstrich wird im folgenden Beispiel hin<strong>zu</strong>gefügt.<br />

Abbildung 3.60: Hin<strong>zu</strong>fügen der Radio-Taste <strong>zu</strong>m Menü<br />

3.4.1.4 Trennstrich zwischen Menü-Einträgen<br />

Der letzte Code-Ausschnitt zeigt die Erzeugung eines Trennstriches. Das Ergebnis war<br />

bereits in Abbildung 3.60 <strong>zu</strong> sehen.<br />

new MenuItem(editMenu, SWT.SEPARATOR);<br />

3.4.1.5 Weitere Menü-Eigenschaften<br />

Menüs dürfen neben dem Text auch Bilder besitzen. Da<strong>zu</strong> besitzen Einträge die Methode<br />

setImage.<br />

3.4.1.6 Tastatursteuerung<br />

Um eine bequeme Bedienung der Menüfunktionen ohne Maus erzielen <strong>zu</strong> können, müssen<br />

die sogenannten Accelerator an den Menüeinträgen registriert werden. Da<strong>zu</strong> zeigt<br />

die folgende Abbildung noch einmal die in wichtigsten Begriffe für die Tastatursteuerung.<br />

57


3.4 Menüs und Toolbar-Leisten<br />

Accelerator<br />

keine Mnemonics<br />

bei Mac OS X (von<br />

Apple nicht empfohlen)<br />

Mnemonic<br />

Abbildung 3.61: Begriffe <strong>zu</strong>r Tastatursteuerung in Menüs<br />

Erklärung der beiden Begriffe:<br />

Mnemonic: Die direkte Bedienung des Menüs erfolgt unter Zuhilfenahme von Mnemonics.<br />

Ein Mnemonic steht für einen Buchstaben aus dem Text des Menüeintrags.<br />

Dieses Zeichen wird einfach dadurch festgelegt, dass vor dem entsprechenden<br />

Buchstaben im Text ein &-Zeichen gesetzt wird. Dieses wird von SWT nicht angezeigt.<br />

Beispiel:&New deklariert das ZeichenNals Mnemonic für diesen Eintrag.<br />

Accelerator: Um diese Schnellbedienung <strong>zu</strong> aktivieren, sind zwei Schritte erforderlich.<br />

Zum Einen muss die Tastenkombination mit einem Tabulatorzeichen dem Ausgabetext<br />

angehängt werden, <strong>zu</strong>m Anderen muss die Tastenkombination explizit als<br />

Accelerator festgelegt werden. Da<strong>zu</strong> dient der Methoden-Aufruf setAccelerator<br />

eines Menü-Eintrags. Beispiel:<br />

MenuItem newItem = new MenuItem(fileMenu, SWT.NONE);<br />

// Text + Accelerator-Text setzen<br />

newItem.setText("&New\tCtrl+N");<br />

// Accelerator einschalten<br />

newItem.setAccelerator(SWT.CTRL | ’N’);<br />

Da mit SWT plattformunabhängige Programme erstellt werden, ist das Problem <strong>zu</strong>beachten,<br />

dass sich die Tastenkombinationen auf den Plattformen unterscheiden. Wo unter<br />

Windows dieCTRL-Taste verwendet wird, setzt Mac OS auf die Apfel-Taste. Daher ist es<br />

in der Regel besser, plattformneutrale „Modifizierer“ (Konstanten aus der KlasseSWT) <strong>zu</strong><br />

verwenden:<br />

SWT.MOD1 statt SWT.CTRL: Unter Windows wird die CTRL-, unter MacOS X die<br />

Apfel-Taste genommen.<br />

SWT.MOD2 statt SWT.SHIFT: SHIFT-Taste sowohl unter Windows als auch unter<br />

Mac OS X<br />

SWT.MOD3 stattSWT.ALT:ALT-Taste unter Windows und Mac OS X<br />

Es gibt noch ein weiteres plattformspezifisches Verhalten. Wo ist es unter Windows erforderlich,<br />

sowohl den Text für den Accelerator mit\t getrennt an<strong>zu</strong>geben als auch den<br />

Code mitsetAccelerator <strong>zu</strong> setzen, wird unter Mac OS X dagegen der Text automatisch<br />

anhand des Tastatur-Codes gesetzt. Es ist dort aber <strong>zu</strong>lässig, wie unter Windows<br />

<strong>zu</strong> verfahren.<br />

Damit sind aus technischer Sicht die wichtigsten plattformspezifischen Merkmale unterschieden.<br />

Leider gibt es aber noch ein nicht-technisches Problem <strong>zu</strong> beachten. So geben<br />

Hersteller von Fenstersystemen häufig Entwurfsrichtlinien für die Reihenfolge und<br />

Positionen der Menüs und ihrer Einträge vor. Weiterhin unterscheiden sich auf den Plattformen<br />

in der Regel auch die Tastatur-Kürzel <strong>zu</strong>r schnelleren Bedienung. So führt normalerweise<br />

kein Weg daran vorbei, Menüstrukturen plattformabhängig <strong>zu</strong> bauen.<br />

58


3.4 Menüs und Toolbar-Leisten<br />

3.4.2 Popup-Menüs<br />

Popup-Menüs werden in der Regel bei einem „Rechtsklick“ auf eine Komponente angezeigt.<br />

Damit kann der Anwender Operationen ausführen, die diese Komponente direkt<br />

manipulieren. Die Erstellung eines solchen Menüs unterscheidet sich nur unwesentlich<br />

von der eines „normalen“ Menüs.<br />

1. Zuerst wird das Popup-Menü erzeugt und an dem Widget registrieren. Das Menü<br />

erhält das Widget auch als Vaterelement.<br />

Menu popupMenu = new Menu(component, SWT.CASCADE);<br />

component.setMenu(popupMenu);<br />

2. Jetzt lassen sich Einträge in das Menü einfügen:<br />

MenuItem copyItem = new MenuItem(popupMenu, SWT.NONE);<br />

copyItem.setText("Copy");<br />

3. Wie bei normalen Menüs dürfen auch Untermenüs eingehängt werden.<br />

Die Abbildung 3.62 zeigt ein Popup-Menü, das direkt an einem Fenster registriert wurde.<br />

Abbildung 3.62: Popup-Menü auf verschiedenen Plattformen<br />

3.4.3 System-Tray<br />

Das System-Tray ist ein spezielles Menü, das auf dem Desktop verankert wird. Mit seiner<br />

Hilfe kann der Anwender wichtige Funktionen einer Anwendung steuern, ohne deren<br />

Fenster manuell öffnen <strong>zu</strong> müssen. Auch diese Menü wird fast wie ein „normales“ erzeugt.<br />

1. Zuerst wird die Referenz auf das System-Tray vom Fenstersystem geholt. Es besteht<br />

die Möglichkeit, dass einzelne Fenstersysteme kein System-Tray unterstützen. Dieser<br />

Fall muss berücksichtigt werden. Anschließend wird für die eigene Anwendung<br />

ein einzelnes TrayItem-Element als Menü-Eintrag für das System-Tray erzeugt.<br />

DasTrayItem-Element sollte ein eigenes Bild erhalten, damit der <strong>zu</strong>gehörige Eintrag<br />

in der häufig recht kompakten Darstellung im System-Tray gut erkennbar ist.<br />

Tray tray = display.getSystemTray();<br />

if (tray != null) {<br />

TrayItem trayItem = new TrayItem(tray, SWT.NONE);<br />

trayItem.setImage(imageRegistry.get("duke"));<br />

2. Jetzt kann dem TrayItem ein Menü <strong>zu</strong>geordnet werden, dass bei Aktivierung angezeigt<br />

wird. Es muss sich um ein Popup-Menü handeln, weil es ein Untermenü des<br />

System-Tray ist.<br />

final Menu menu = new Menu(shell, SWT.POP_UP);<br />

59


3.4 Menüs und Toolbar-Leisten<br />

3. Danach lassen sich Einträge und weitere Untermenüs in das Menü einfügen:<br />

MenuItem item = new MenuItem(tray, SWT.NONE);<br />

item.setText("Higher");<br />

4. Das eigene Menü muss manuell bei einem Mausklick eingeblendet werden. Da<strong>zu</strong><br />

ist etwas Ereignisbehandlung erforderlich. Diese wird in Abschnitt 3.6 eingehend<br />

betrachtet.<br />

trayItem.addListener(SWT.MenuDetect, new Listener () {<br />

public void handleEvent (Event event) {<br />

menu.setVisible (true);<br />

}<br />

});<br />

Die Positionen und das Aussehen unterscheiden sich etwas auf den unterschiedlichen<br />

Plattformen, was die Abbildung 3.63 zeigt.<br />

Abbildung 3.63: Menü in einem System-Tray auf verschiedenen Plattformen<br />

3.4.4 Toolbars<br />

Toolbars werden eingesetzt, um dem Benutzer einen schnellen Zugriff auf häufig benötigte<br />

Funktionen <strong>zu</strong> bieten. Sie enthält in der Regel Tasten und Trennabstände, aber auch<br />

andere Komponenten sind möglich, wenn auch ungewöhnlich. Normalerweise werden<br />

Radio-Tasten, „normale“ Tasten, Checkboxen und Drop-Down-Listen eingesetzt. Gerade<br />

wenn Tasten mit sehr kleinen Bildern verwendet werden, sollten unbedingt auch Tooltip-<br />

Texte angegeben werden, da nicht jeder Benutzer sofort die Bedeutung eines kleines<br />

Bildchens erkennen kann.<br />

Die folgenden Schritte sind in der Regel <strong>zu</strong> durchlaufen, um eine Toolbar <strong>zu</strong> erzeugen:<br />

1. Zuerst wird die Toolbar als Container angelegt. Neben einer horizontalen wäre auch<br />

eine vertikale Ausrichtung denkbar.<br />

ToolBar bar = new ToolBar(shell, SWT.HORIZONTAL);<br />

2. Jetzt lassen sich Einträge <strong>zu</strong>r Leiste hin<strong>zu</strong>fügen. Hier wird eine „normale“ Taste<br />

eingesetzt, die statt eines Textes ein Bild besitzt. Da Bilder nicht immer sehr aussagekräftig<br />

sind, erhält die Taste auch einen Tooltip-Text.<br />

ToolItem item = new ToolItem(bar, SWT.PUSH);<br />

item.setImage(image);<br />

item.setToolTipText("Neu...");<br />

Mögliche Tasten-Typen sind:<br />

SWT.PUSH: „normale“ Taste<br />

60


3.4 Menüs und Toolbar-Leisten<br />

SWT.CHECK: Taste, die ihren Zustand speichern kann (Checkbox)<br />

SWT.RADIO: exklusive Auswahl aus einer Anzahl Möglichkeiten (Radio-Taste)<br />

SWT.SEPERATOR: Trennsymbol (z.B. Strich, größerer Abstand, . . . )<br />

SWT.DROP_DOWN: Taste für eine Auswahlliste. Die Liste muss manuell angelegt<br />

und angezeigt werden. Das ist recht umständlich und soll daher hier nicht<br />

gezeigt werden.<br />

Zu beachten ist noch, dass sich die Toolbar im Layout des Fensters befindet. Daher muss<br />

ein passender Layoutmanager verwendet werden, der so ein schmales Element an seinem<br />

Rand unterstützt. Das Beispiel ToolBarApplication verwendet ein einspaltiges<br />

GridLayout, in dem die Toolbar in der obersten Zeile sitzt.<br />

Und so sieht das Resultat aus:<br />

ToolBarApplication<br />

Abbildung 3.64: Toolbar auf verschiedenen Plattformen<br />

3.4.5 Coolbars<br />

Das Widget mit dem merkwürdigen Namen CoolBar ist eine flexiblere Toolbar. Da es<br />

nicht nur auf Tasten und Auswahllisten beschränkt ist, darf es beliebige Widgets beinhalten.<br />

Weiterhin kann der Anwender interaktiv die Einträge in der Coolbar verschieben. Die<br />

Schritte <strong>zu</strong>r Erstellung sind auch hier relativ einfach:<br />

1. Zuerst wird die Coolbar als Container angelegt. Neben einer horizontalen wäre auch<br />

eine vertikale Ausrichtung denkbar.<br />

CoolBar bar = new CoolBar(shell, SWT.HORIZONTAL);<br />

2. Anschließend lassen sich einzelne Einträge in Form vonCoolItems <strong>zu</strong>r Leiste hin<strong>zu</strong>fügen.<br />

Diese nehmen dann die eigentlichen Widgets auf. Dabei handelt es sich<br />

häufig um Toolbar-Elemente, aber auch andere Widgets sind denkbar. Leider muss<br />

die Größe eines Eintrags manuell berechnet werden, weil dieCoolItems da<strong>zu</strong> nicht<br />

in der Lage sind.<br />

CoolItem item = new CoolItem(bar, SWT.NONE);<br />

item.setControl(contentsOfItem);<br />

// manuell Größe des CoolItems berechnen<br />

Ein ausführliches Beispiel folgt nach dieser Aufzählung.<br />

Wie bei der Toolbar gilt auch hier, dass sich die Coolbar im Layout des Fensters befindet.<br />

Daher muss der Layout-Manager „passen“.<br />

61


3.4 Menüs und Toolbar-Leisten<br />

Coolbar<br />

Coolitem<br />

(veränderbar)<br />

Abbildung 3.65: Elemente einer Coolbar<br />

Das folgende Beispiel (CoolBarApplication) zeigt, wie eine Toolbar gefüllt und als<br />

Widget in die Coolbar eingefügt wird. Es handelt sich dabei um die erste Toolbar aus der<br />

Abbildung 3.65.<br />

private void initGUI(Shell shell) {<br />

// GridLayout mit einer Spalte: Zeile 0 für die Leiste<br />

shell.setLayout(new GridLayout(1, false));<br />

}<br />

// Horizontal angeordnete Leiste erzeugen.<br />

CoolBar bar = new CoolBar(shell, SWT.HORIZONTAL);<br />

// Größenänderung: Die Coolbar soll horizontal wachsen.<br />

GridData data = new GridData(SWT.FILL, SWT.DEFAULT,<br />

true, false);<br />

bar.setLayoutData(data);<br />

// Einen Eintrag mit drei Tasten hin<strong>zu</strong>fügen.<br />

CoolItem item = new CoolItem(bar, SWT.NONE);<br />

Composite element = createFileToolBar(bar);<br />

computeCoolItemSize(item, element);<br />

Jetzt wird die Toolbar erzeugt:<br />

// Drei Tasten <strong>zu</strong>m Eintrag hin<strong>zu</strong>fügen:<br />

// Da<strong>zu</strong> wird eine Toolbar mit den Tasten<br />

// angelegt und die Leiste als Control des CoolItems angegeben.<br />

// Prinzipiell könnte auch ein Composite mit eigenem<br />

// Layout verwendet werden.<br />

private Composite createFileToolBar(Composite parent) {<br />

ToolBar bar = new ToolBar(parent, SWT.HORIZONTAL);<br />

}<br />

ToolItem item = new ToolItem(bar, SWT.PUSH);<br />

item.setImage(imageRegistry.get("new"));<br />

item.setToolTipText("Neu...");<br />

// Weitere Tasten ...<br />

return bar;<br />

Abschließend fehlt noch die Methode <strong>zu</strong>r Größenberechnung des CoolItems anhand<br />

seines Inhalts.<br />

62


3.5 Basisklassen<br />

// Größe eines CoolItems anhand seines Inhalts eintragen.<br />

// Element ist in diesem Beispiel eine Toolbar,<br />

// deren Größe die Maße des CoolItems bestimmt.<br />

private void computeCoolItemSize(CoolItem item,<br />

Composite element) {<br />

}<br />

// ToolBar in das CoolItem als Element einfügen.<br />

item.setControl(element);<br />

// Größe des ToolBars berechnen.<br />

Point size = element.computeSize(SWT.DEFAULT, SWT.DEFAULT);<br />

// Größe des CoolItems so berechnen, dass ein<br />

// Element der Größe size aufgenommen werden kann.<br />

size = item.computeSize(size.x, size.y);<br />

// Berechnete Größe in das CoolItem eintragen.<br />

item.setSize(size);<br />

Und so sieht dann das fertige Ergebnis aus, wenn alle Einträge hin<strong>zu</strong>gefügt wurden:<br />

Abbildung 3.66: Beispiel einer Coolbar<br />

3.5 Basisklassen<br />

Dieser Abschnitt beschäftigt sich mit einem Teil der Klassenhiererachie des Widgets.<br />

Das ist erforderlich, um <strong>zu</strong> verstehen, welche Klassen bestimmte Aufgaben erfüllen. Die<br />

Abbildung 3.67 zeigt einen Teil der Klassenhierarchie.<br />

Widget<br />

Caret<br />

Menu<br />

ScrollBar<br />

Control<br />

Label Button<br />

Scrollable Slider<br />

Text<br />

List<br />

Composite<br />

Abbildung 3.67: Vereinfachte Klassenhierachie<br />

63


3.5 Basisklassen<br />

3.5.1 Widget<br />

Das Widget ist die Basisklasse aller Klassen, die Benutzerinteraktion <strong>zu</strong>lassen. Das<br />

heißt, dass alle Elemente, mit denen der Benutzer in irgendeiner Form interagieren kann,<br />

von Widget abstammen. Das Widget verwaltet darüber hinaus den Freigabe<strong>zu</strong>stand.<br />

Da<strong>zu</strong> ist es wichtig, sich daran <strong>zu</strong> erinnern, dass SWT Ressourcen und somit Widgets<br />

des darunterliegenden Fenstersystems verwendet. Diese Ressourcen können nicht vom<br />

Garbage Collector freigegeben werden, sondern müssen vom Entwickler verwaltet werden<br />

(siehe Abschnitt 3.1.4). In der Klasse Widget ist die Information abgelegt, ob das<br />

Widget bereits wieder manuell freigegen wurde. Dann würden viele Methodenaufrufe auf<br />

solch einem Widget mit einer Ausnahme abgebrochen werden. Weiterhin erlaubt ein Widget<br />

die Speicherung anwendungsspezifischer Informationen. Es handelt sich hierbei um<br />

eine Hashtabelle. Diese hilft dem Entwickler dabei, Daten direkt am Widget ab<strong>zu</strong>legen,<br />

die er später z.B. <strong>zu</strong>r Auswertung der Eingaben des Anwenders benötigt. So muss der<br />

Entwickler da<strong>zu</strong> keine eigene Verwaltung aufbauen. Die Verwendung ist sehr einfach:<br />

void setData(String key, Object value): Lege den Wert value unter<br />

dem Schlüsselkey ab.<br />

Object getData(String key): Liest den Wert aus, der <strong>zu</strong>m Schlüsselkey gehört.<br />

Das Widget führt auch eine erste Ereignisbehandlung mit zwei verschiedenen Typen von<br />

Ereignissen ein:<br />

DisposeEvent: Dieses wird ausgelöst, wenn das Widget manuell freigegeben wurde.<br />

So lässt sich überwachen, ab wann auf dieses Widget nicht mehr <strong>zu</strong>gegriffen<br />

werden kann.<br />

Untypisierte Ereignisse: Ein Ereignistyp wird durch eine Konstante beschrieben. Es<br />

gibt keine unterschiedlichen Klassen für die verschiedenen möglichen Ereignisse.<br />

Das Control, das von Widget erbt, besitzt dagegen für alle unterstützten Ereignisse<br />

spezielle Klassen und Methoden. Man spricht in diesem Fall von typisierten<br />

Ereignissen.<br />

Die Ereignisse und ihre Behandlung werden in Kapitel 3.6 eingeführt.<br />

3.5.2 Control<br />

Ein Control ist ein Widget, das ein Gegenstück auf Betriebssystemebene besitzt. Es<br />

verwaltet eine ganze Anzahl von Eigenschaften, die in den folgenden Unterabschnitten<br />

vorgestellt werden.<br />

3.5.2.1 Größen und Position<br />

ImControl werden die bevor<strong>zu</strong>gte sowie die aktuelle Größe der Komponente abgelegt.<br />

Weiterhin beinhaltet es die aktuelle Position bezogen auf sein Vaterelement. In Abbildung<br />

3.68 hat die Taste „Ok“ ein (<strong>zu</strong>r Hervorhebung mit einem Rahmen versehenes)<br />

Composite-Objekt als Vater. Die Taste wird relativ <strong>zu</strong>mComposite-Objekt, dieses wiederum<br />

relativ <strong>zu</strong> seinem Vater, dem Fenster, platziert.<br />

64


3.5 Basisklassen<br />

Abbildung 3.68: Relative Positionierung von Controls<br />

3.5.2.2 Farben<br />

Alle Control-Elemente unterstützen separate Vorder- und Hintergrundfarben. Diese<br />

lassen sich mitsetForeground bzw.setBackground setzen. Zusätzlich kann mit Hilfe<br />

der MethodesetBackgroundImage ein Bild als Hintergrund platziert werden.<br />

Abbildung 3.69: Hintergrundfarbe eines Controls<br />

3.5.2.3 Tooltips<br />

Gerade wenn einem Anwender nicht sofort auf den ersten Blick ersichtlich ist, was er mit<br />

einem Control machen kann, dann sollte dem Control ein erklärender Tooltip-Text<br />

mit setToolTipText eingetragen werden. Dieses betrifft besonders solche Controls,<br />

die lediglich ein Bild und keinen Text besitzen.<br />

setToolTipText<br />

Abbildung 3.70: Tooltip an einem Control<br />

3.5.2.4 Aktivierungs<strong>zu</strong>stand<br />

Controls erlauben die Ein- und Ausgabe von Werten. Ist <strong>zu</strong> einem Zeitpunkt eine Eingabe<br />

nicht erwünscht oder sinnvoll, dann sollte sie auch verhindert werden. Da<strong>zu</strong> kann ein<br />

Control gesperrt und später auch wieder freigegeben werden (MethodesetEnabled).<br />

Abbildung 3.71: Gesperrtes Control<br />

65


3.5 Basisklassen<br />

3.5.2.5 Weitere Eigenschaften<br />

Es gibt noch einige weitere interessante Eigenschaften:<br />

Die Sichtbarkeit kann mitsetVisible ein- oder ausgeschaltet werden.<br />

Soll das Control ein eigenes Popup-Menü erhalten, dann kann es mit setMenu<br />

eingetragen werden.<br />

JedesControl kann seinen Text in einem separaten Zeichensatz darstellen. Dieser<br />

wird mit Hilfe der MethodesetFont amControl registriert.<br />

Das Kapitel 3.2 hat verschiedene Layout-Manager eingeführt. Bei einigen von ihnen<br />

ist es erforderlich, <strong>zu</strong>sätzliche Layout-Eigenschaften am Control ab<strong>zu</strong>legen. Die<br />

MethodesetLayoutData übernimmt diese Aufgabe.<br />

Jedem Control kann ein individueller Mauszeigertyp <strong>zu</strong>gewiesen werden, um die<br />

Art der Eingabe an<strong>zu</strong>zeigen.<br />

Es ist möglich, die Reihenfolge fest<strong>zu</strong>legen, in derControls den Tastaturfokus erhalten.<br />

Weiterhin kann einemControl auch direkt der Fokus <strong>zu</strong>gewiesen werden.<br />

Controls erlauben das „Ziehen und Ablegen“ (Drag- und Drop) von Daten.<br />

Ein kompletter Dialog besteht aus einem Baum vonControls und Widgets. Es lässt<br />

sich innerhalb dieses Baums nicht nur vom Vater <strong>zu</strong> seinen Kindern, sondern auch<br />

von einem Kind <strong>zu</strong> seinem Vaterlement navigieren. Da<strong>zu</strong> enthält jedes Control<br />

eine Referenz auf sein Vater-Element.<br />

3.5.2.6 Ereignisse<br />

Die Basisklasse Widget verwaltet lediglich untypisierte Ereignisse. In der abgeleiteten<br />

Klasse Control dagegen werden typisierte Ereignisse unterstützt. Damit ist gemeint,<br />

dass es für jede Art von Ereignis eine eigene Ereignisklasse und separate Methoden<br />

gibt. Dieses vereinfacht die Ereignisbehandlung gerade in den Fällen, in denen der Entwickler<br />

lediglich an bestimmten Ereignistypen interessiert ist. Weiterhin ist der Zugriff<br />

auch typsicherer. Auswahl an Ereignissen:<br />

ControlEvent: Dieses Ereignis wird gemeldet, wenn das Control verschoben<br />

oder in seiner Größe verändert wurde.<br />

DragDetectEvent: Der Inhalt desControls soll per „Drag and Drop“ verschoben<br />

oder kopiert werden.<br />

FocusEvent: DasControl hat den Tastaturfokus erhalten oder verloren.<br />

HelpEvent: Es wurde eine Hilfe für dasControl angefordert.<br />

KeyEvent: Es wurde eine Taste der Tastatur gedrückt oder losgelassen.<br />

MenuDetectEvent: Es wurde ein Popup-Menü für die Komponente angefordert.<br />

MouseEvent: Es wurde eine Maustaste über der Komponente gedrückt.<br />

MouseMoveEvent: Der Mauszeiger wurde über der Komponente bewegt.<br />

PaintEvent: Die Komponente muss neu gezeichnet werden. Das Ereignis kann<br />

da<strong>zu</strong> verwendet werden, eigeneControls <strong>zu</strong> erstellen, die sich selbst zeichnen.<br />

TraverseEvent: Es wurde eine Taste gedrückt, um den Fokus auf eine andere<br />

Komponente <strong>zu</strong> setzen.<br />

VonControl abgeleitete Klassen bieten häufig noch weitere typisierte Ereignisse.<br />

66


3.6 Ereignisbehandlung<br />

3.5.3 Scrollable<br />

Einige Controls verwalten Inhalte, die für einen Dialog <strong>zu</strong> groß werden können. Dabei<br />

kann es sich beispielsweise um ein mehrzeiliges Texteingabefeld oder eine Zeichenfläche<br />

in einem Malprogramm handeln. Um den Inhalt trotzdem bearbeiten <strong>zu</strong> können,<br />

muss dieser gescrollt werden. Alle Controls, die ihren Inhalt verschieben („scrollen“)<br />

können, erben von der Basisklasse Scrollable. Diese verwaltet den <strong>zu</strong>r Verfügung<br />

stehenden Platz und stellt bei Bedarf die horizontalen und vertikalen Scrollbalken <strong>zu</strong> Verfügung,<br />

auf die mit den folgenden Methoden <strong>zu</strong>gegriffen werden kann:<br />

ScrollBar getHorizontalBar(): horizontalen Scrollbalken auslesen<br />

ScrollBar getVertikalBar(): vertikalen Scrollbalken auslesen<br />

3.5.4 Composite<br />

DasComposite ist schon aus Beispielen <strong>zu</strong> geschachtelten Layouts bekannt. Es ist die<br />

Basisklasse für alle Widgets, die weitere Widgets als Kindelemente besitzen können, die<br />

also als Container für andereControls oderWidgets dienen. EinComposite verwaltet<br />

die Kindelemente und sowie Layout-Manager <strong>zu</strong> deren Platzierung. Weiterhin kennt<br />

es die Reihenfolge, in der die Kindelemente beim Druck auf die Tabulatortaste besucht<br />

werden.<br />

3.6 Ereignisbehandlung<br />

Nachdem in den vorherigen Abschnitten immer wieder einmal auf die Ereignisbehandlung<br />

hingewiesen wurde, soll sie jetzt endlich richtig eingeführt werden. Mit dem Begriff<br />

der „Ereignisbehandlung“ ist hier gemeint, wie die Anwendung Ereignisse mitgeteilt bekommt.<br />

Da in Anwendungen ein aktives Abfragen von Benutzerinteraktionen (Polling)<br />

völlig indiskutabel ist, wird die Anwendung durch Ereignisse informiert, wenn der Anwender<br />

Aktionen einleiten möchte. Der Auslöser einer solchen Aktion kann beispielsweise<br />

ein Tastendruck, eine Mausbewegung usw. sein. Denkbar sind aber auch alternative Eingabegeräte<br />

wie Braille-Tastaturen für blinde Anwender.<br />

3.6.1 Beobachter-Muster<br />

Die Ereignisbehandlung basiert auf dem Beobachter-Entwurfsmuster. Statt „Beobachter“<br />

wird in deutschsprachiger Literatur auch manchmal der englischsprachige Begriff „Listener“<br />

eingesetzt.<br />

Das folgende Diagramm zeigt <strong>zu</strong>r Erinnerung noch einmal die Funktionsweise des Beobachtermusters<br />

ohne einen Be<strong>zu</strong>g <strong>zu</strong> SWT und JFace.<br />

67


3.6 Ereignisbehandlung<br />

EventObject<br />

-source: Object<br />

<br />

Listener<br />

0..*<br />

<br />

Subject<br />

+eventHappened<br />

+addAnEventListener<br />

+removeAnEventListener<br />

+fireAnEventHappened<br />

AnEvent<br />

ConcreteListener<br />

-state: Type<br />

+eventHappened<br />

ConcreteSubject<br />

+addAnEventListener<br />

+removeAnEventListener<br />

+fireAnEventHappened<br />

AnEvent event = new AnEvent(…);<br />

for each listener (listeners)<br />

listener.eventHappened(event);<br />

Abbildung 3.72: Allgemeines Bobachter-Muster<br />

Die Interaktion läuft immer wie folgt ab, wobei die Namen der Klassen und Schnittstellen<br />

in Diagramm 3.72 nicht den realen Gegebenheiten entsprechen:<br />

1. Je nach Aufgabe erzeugt ein Control sehr unterschiedliche Ereignisse. Beispiele<br />

sind das Verschieben des Mauszeigers oder das Drücken einer Taste. Diese Ereignisse<br />

werden durch unterschiedliche Klassen implementiert (AnEvent). Alle Klassen<br />

haben eine gemeinsame Basisklasse (EventObject) und eventuelle weitere<br />

Attribute und Methoden.<br />

2. Möchte man einen Beobachter für einen bestimmten Ereignistyp schreiben, dann<br />

muss dieser eine <strong>zu</strong>m Ereignistyp passende Schnittstelle implementieren. Der Name<br />

der Schnittstelle ist aus dem Ereignistyp ableitbar:EreignisnameListener<br />

3. Danach registrieren sich ein oder mehrere Beobachter (ConcreteListener) an<br />

den Objekten (ConcreteSubject), über deren Änderungen sie informiert werden<br />

wollen. Die Methode <strong>zu</strong>r Registrierung hat in SWT und JFace immer den festen<br />

NamensaufbauaddEreignisnameListener.<br />

4. Sollte der Beobachter nicht länger an der Benachrichtigung interessiert sein, dann<br />

kann er sich auch wieder abmelden.<br />

5. Tritt im Laufe der Zeit ein Ereignis auf, dann wird intern im Subjekt eine Methode<br />

(hier: fireAnEventHappened) aufgerufen. Diese durchläuft die Liste aller registrierten<br />

Beobachter und informiert sie, indem (in diesem Beispiel) die Methode<br />

eventHappened inConcreteListener aufgerufen wird.<br />

68


3.6 Ereignisbehandlung<br />

Der Ablauf lässt sich wie folgt <strong>zu</strong>sammenfassen:<br />

listener<br />

ConcreteListener<br />

subject<br />

ConcreteSubject<br />

1: addAnEventListener(this)<br />

event<br />

AnEvent<br />

2: create<br />

registrieren<br />

3: eventHappened()<br />

benachrichtigen<br />

Abbildung 3.73: Ablauf im Bobachter-Muster<br />

Teilweise ist in der Literatur auch die Bezeichnung „publisher-subscriber model“ für das<br />

Beobachter-Muster <strong>zu</strong> finden.<br />

3.6.2 Ablauf unter SWT<br />

Bevor die programmatische Verwendung der SWT-Ereignisse behandelt wird, soll im<br />

kommenden Abschnitt noch ein kurzer Blick das Zusammenspiel zwischen Fenstersystem<br />

und SWT bei der Ereignis<strong>zu</strong>stellung geworfen werden.<br />

3.6.2.1 Anbindung an das Fenstersystem<br />

SWT und JFace setzen auf dem <strong>zu</strong>grundeliegende Fenstersystem auf. Da der Ursprung<br />

der Ereignisse auch das Fenstersystem ist, lohnt es sich, einen Blick auf den „Weg“<br />

eines Ereignisses von seiner Entstehung bis <strong>zu</strong> einem Widget bzw. Control als Ziel<br />

<strong>zu</strong> werfen.<br />

Betriebssystem<br />

Ereignis-Queue<br />

Nachricht<br />

Display<br />

Top-Level-<br />

Shell<br />

Ereignis<br />

Widget<br />

Ereignis<br />

Listener-<br />

Interface<br />

Abbildung 3.74: Ereignis<strong>zu</strong>stellung unter SWT<br />

Aufruf<br />

Event-Handler-<br />

Methode<br />

69


3.6 Ereignisbehandlung<br />

Der interne Ablauf sieht so aus:<br />

1. Nachdem das Betriebs- bzw. Fenstersystem das Ereignis erkannt hat, wird es intern<br />

in einer Warteschlange („Ereignis-Queue“) gespeichert. Diese Pufferung kann<br />

erforderlich sein, weil eventuell schneller Ereignisse anfallen, als sie von einer Anwendung<br />

abgearbeitet werden können.<br />

2. Danach wird das Display-Objekt ermittelt, das <strong>zu</strong>r Anwendung gehört. Normalerweise<br />

besitzt jede SWT-Anwendung nur ein solches Objekt.<br />

3. Anhand des Display-Objektes kann das Fenster ermittelt werden, in dem das Ereignis<br />

ausgelöst wurde.<br />

4. In diesem Schritt wird innerhalb des Fensters das Widget gesucht, dem das Ereignis<br />

galt. Handelt es sich beispielsweise um ein Mausereignis, dann ist das Ziel der<br />

Interaktion das oberste Widget an der Position des Mauszeigers.<br />

5. Das Widget wird informiert, dass ein Ereignis eingetreten ist. Es informiert daraufhin<br />

alle Beobachter, die an ihm registriert sind. Das geschieht, indem die entsprechende<br />

Methode, die die Beobachter-Schnittstelle vorgegeben hat, aufgerufen wird.<br />

6. Der „Event-Handler“ ist der Beobachter. Er wird vom Widget informiert und kann<br />

seinerseits Aktionen durchführen.<br />

Hier wird begrifflich zwischen „Nachricht“ und „Ereignis“ unterschieden. Das ist relativ<br />

willkürlich und soll lediglich ausdrücken, dass der normale Entwickler die Nachrichten<br />

des Fenstersystems gar nicht <strong>zu</strong> Gesicht bekommt.<br />

3.6.2.2 Programmierung<br />

In SWT werden sehr viele unterschiedliche Arten von Ereignissen unterstützt. Das wohl<br />

am häufigsten verwendete Ereignis ist das SelectionEvent. Es soll daher hier exemplarisch<br />

für alle anderen Ereignisse näher betrachtet werden. So lösen unter Anderem<br />

Tasten („Buttons“) dieses Ereignis aus, nachdem sie gedrückt und wieder losgelassen<br />

wurden. Um diese Ereignisse ab<strong>zu</strong>fangen, müssen immer die folgenden Schritte durchlaufen<br />

werden:<br />

1. Definition der Beobachter-Klassen<br />

2. Erzeugen einer Instanz der Beobachter-Klasse<br />

3. Registrierung der Beobachter-Instanz an einem Widget<br />

Das ist nicht neu und wurde bereits im vorherigen Abschnitt über das allgemeine Beobachter-Muster<br />

so vorgestellt. Da Ereignisse in SWT einem festen Namensschema folgen,<br />

kann sehr leicht aus dem Namen des Ereignistyps auf die Namen der anderen beteiligten<br />

Klassen geschlossen werden. Im Falle desSelectionEvents sind das:<br />

Die Ereignisklasse, die <strong>zu</strong>sätzliche Informationen <strong>zu</strong>m Ereignis beinhaltet, heißt so<br />

wie der Ereignistyp:SelectionEvent<br />

Die Schnittstelle des Beobachters bekommt an den Typ des Ereignisses die Endung<br />

Listener angehängt. Somit heißt sieSelectionListener.<br />

70


3.6 Ereignisbehandlung<br />

Die Beobachter-Schnittstellen besitzen bei vielen Ereignistypen eine ganze Anzahl<br />

von Methoden, die aber nicht immer in jedem Szenario benötigt werden. Damit der<br />

Entwickler nicht alle unnötigen Methoden selbst (leer) implementieren muss, gibt<br />

es Adapter-Klassen. Diese implementieren die Beobachter-Schnittstelle mit leeren<br />

Methodenrümpfen. Der Name des Adapaters bekommt als EndungAdapter. Beim<br />

SelectionEvent heißt der demnachSelectionAdapter.<br />

Der Name der Methode des Controls, über die sich ein Beobachter anmeldet,<br />

beginnt mitadd, gefolgt vom Ereignistyp und endet mitListener. Für das Selektionsereignis<br />

lautet der Name alsoaddSelectionListener.<br />

Das nächste Beispiel zeigt, wie sich ein Beobachter in Form einer anonymen inneren<br />

Klasse an einer Taste registriert.<br />

Button okButton = new Button(shell, SWT.PUSH);<br />

okButton.setText("Weiter");<br />

// Als Beobachter an der Taste anmelden.<br />

okButton.addSelectionListener(new SelectionListener() {<br />

// die wichtige Methode<br />

@Override<br />

public void widgetSelected(SelectionEvent event) {<br />

System.out.println("widgetSelected: " + event.widget);<br />

}<br />

// Wird auf einigen Plattformen nie erzeugt (z.B.<br />

// Doppelklick auf einen Listeneintrag).<br />

@Override<br />

public void widgetDefaultSelected(SelectionEvent event) {<br />

}<br />

});<br />

Einfach ist es, wenn statt einer Schnittstelle der passende Adapter verwendet wird. In<br />

diesem Fall ist es nicht mehr erforderlich, die zweite, nicht benötigte Methode widget-<br />

DefaultSelected <strong>zu</strong> überschreiben.<br />

Button okButton = new Button(shell, SWT.PUSH);<br />

okButton.setText("Weiter");<br />

// Wieder als Beobachter anmelden.<br />

okButton.addSelectionListener(new SelectionAdapter() {<br />

// die wichtige Methode<br />

@Override<br />

public void widgetSelected(SelectionEvent event) {<br />

System.out.println("widgetSelected: " + event.widget);<br />

}<br />

});<br />

3.6.2.3 Unterscheidung der Ereignisquellen<br />

Nun kommt es in realen Anwendungen nicht selten vor, dass mehr als ein Widget ein<br />

Selection-Ereignis auslösen kann. Wie können die Quellen der Ereignisse unterschieden<br />

werden? Es ist ja durchaus interessant <strong>zu</strong> wissen, ob die Weiter- oder Abbruchtaste<br />

in einem Dialog gedrückt wurde. Für jede Quelle einen eigenen Beobachter <strong>zu</strong> nehmen,<br />

ist nicht nur sehr aufwändig sondern auch unflexibel. Die Lösung besteht darin, den Beobachter<br />

die Ereignisquelle herausfinden <strong>zu</strong> lassen. Generell für alle Ereignistypen ist<br />

71


3.6 Ereignisbehandlung<br />

das durch Auslesen der Quelle aus dem Ereignisobjekt mit Hilfe des öffentlichen Attributs<br />

widget der BasisklasseTypedEvent möglich. Alle Ereignisklassen, die typisierte<br />

Ereignisse repräsenatieren, erben direkt oder indirekt von TypedEvent. Das Attribut<br />

widget liefert eine Widget-Referenz auf den Auslöser des Ereignisses <strong>zu</strong>rück. Diese<br />

Referenz kann mit den bekannten Referenzen verglichen werden. Nun kann es aber vorkommen,<br />

dass die Referenzen auf die Oberflächen-Komponenten in der Dialogklasse<br />

privat sind und ein Zugriff somit nicht erlaubt ist. Da der Fall nicht selten vorkommt, gibt<br />

es eine weitere Lösung. Abschnitt 3.5.1 hat die Eigenschaften der Klasse Widget aufgeführt.<br />

Da<strong>zu</strong> gehört auch die Möglichkeit, benutzerspezifische Informationen an einem<br />

Widget ab<strong>zu</strong>legen. An jeder Komponente, die ein Ereignis auslösen kann, wird eine Information<br />

abgelegt, die später während der Ereignisbehandlung ausgelesen wird. Das<br />

Beispiel zeigt den Einsatz (DateiButtonEventApplication.java):<br />

final Button okButton = new Button(shell, SWT.PUSH);<br />

okButton.setText("Weiter");<br />

// Benutzerspezifische Information ablegen.<br />

// - "source" ist der Schlüssel.<br />

// - "ok" ist der <strong>zu</strong>gehörige Wert.<br />

okButton.setData("source", "ok");<br />

// Beobachter registrieren<br />

okButton.addSelectionListener(new SelectionAdapter() {<br />

@Override<br />

public void widgetSelected(SelectionEvent event) {<br />

// Variante 1: Auslesen der benutzerspezifischen<br />

// Information<br />

boolean wasOk = event.widget.getData("source").equals("ok");<br />

}<br />

});<br />

// Variante 2: Vergleich der Referenzen auf das Widget<br />

boolean wasOk = event.widget == okButton;<br />

In der Methoden widgetSelected können nun die Ereignisquellen sehr einfach unterschieden<br />

werden. Es ergibt sich in der Praxis häufig der folgende Aufbau, wobei der<br />

Name des Schlüssels natürlich nicht unbedingt "source" sein muss:<br />

public void widgetSelected(SelectionEvent event) {<br />

if (event.widget.getData("source").equals("command1")) {<br />

// Ausführung für Komponente 1<br />

}<br />

else if (event.widget.getData("source").equals("command2")) {<br />

// Ausführung für Komponente 2<br />

}<br />

// ...<br />

else if (event.widget.getData("source").equals("commandN")) {<br />

// Ausführung für Komponente N<br />

}<br />

}<br />

3.6.2.4 Entwurfsmöglichkeiten<br />

Wer aber behandelt die Ereignisse? Soll es die Klasse machen, die den Dialog erzeugt<br />

hat, oder aber ist es sinnvoller, eine separate Klasse <strong>zu</strong> verwenden?<br />

72


3.6 Ereignisbehandlung<br />

Eine zentrale Beobachterklasse empfängt die Ereignisse<br />

Zunächst wird die Dialogklasse so erweitert, dass sie von der Klasse Selection-<br />

Adapter erbt oder die Schnittstelle SelectionListener implementiert. Interessant<br />

ist auch hier wieder nur die Methode widgetSelected. Danach wird ein Objekt der<br />

Beobachter-Klasse erzeugt und an den Komponenten (hier Tasten) registriert:<br />

SelectionListener listener = new SelectionAdapter() {<br />

@Override<br />

public void widgetSelected(SelectionEvent event) {<br />

...<br />

}<br />

});<br />

okButton.addSelectionListener(listener);<br />

cancelButton.addSelectionListener(listener);<br />

Jedes Widget erhält seinen eigenen Beobachter<br />

Im Unterschied <strong>zu</strong>r Variante 1 wird für jedes Widgets, das ein Ereignis auslösen kann, eine<br />

eigene Beobachter-Klasse geschrieben. Ansonsten unterscheidet sich diese Variante<br />

nicht von der ersten.<br />

Vergleich der Ansätze<br />

In der Praxis gibt es weitere Möglichkeiten, die später auch noch vorgstellt werden.<br />

Wichtig in diesem Zusammenhang ist auch die Trennung von Anwendungslogik bzw.<br />

Geschäftslogik und Oberfläche. Weil gerade dieses Thema in der Praxis eine hohe Bedeutung<br />

erfährt, wird im <strong>Skript</strong> in Kapitel 4 eine separate Dialogsteuerung behandelt.<br />

Daher werden hier nur die beiden bereits bekannten Varianten diskutiert.<br />

1. Variante: Eine separate Klasse empfängt die Ereignisse:<br />

+ Die Lösung ist schnell erstellt.<br />

+ Es werden weniger Klassen benötigt.<br />

– Der Ansatz wird schnell sehr unübersichtlich, da eine Methode fast die komplette<br />

Ereignisbehandlung übernimmt.<br />

2. Variante: Jedes Widget erhält seinen eigenen Beobachter:<br />

+ Die Auftrennung ist bei vielen verschiedenen Ereignistypen häufig flexibler.<br />

+ Der Ereigniscode kann für verschiedene Klasse wiederverwendet werden.<br />

– Die Lösung ist teilweise aufwändiger <strong>zu</strong> erstellen.<br />

– Es werden mehr Klassen benötigt.<br />

3.6.2.5 Ereignisse bei Fenstern<br />

Dieser kleine Abschnitt zeigt exemplarisch die Verwendung der Ereignisses, die ein Fenster<br />

auslösen kann. Der Ereignistyp ist in diesem Fall dasShellEvent.. Somit heißen die<br />

BeobachterShellListener für die Schnittstelle undShellAdapter. Es gibt in diesem<br />

Beobachter mehrere Methoden:<br />

73


3.6 Ereignisbehandlung<br />

public void shellIconified(ShellEvent event): Das bisher geöffnete<br />

Fenster wurde <strong>zu</strong> einem Icon verkleinert.<br />

public void shellDeiconified(ShellEvent event): Das Fenster wurde<br />

aus einem Icon <strong>zu</strong> seiner vorherigen Größe wiederhergestellt.<br />

public void shellDeactivated(ShellEvent event): Ein offenes Fenster<br />

ist nicht länger aktiv. Es war bisher das oberste Fenster. Jetzt wurde aber ein anderes<br />

nach oben gebracht.<br />

public void shellActivated(ShellEvent event): Das Fenster ist aktiv.<br />

Es wird das oberste Fenster, das Eingaben erwartet.<br />

public void shellClosed(ShellEvent event): Das Fenster soll vom Anwender<br />

geschlossen werden. Diese Aktion lässt sich programmgesteuert verhindern,<br />

indem das AttributdoIt im Ereignisobjekt auffalse gesetzt wird. Dieses auf<br />

den ersten Blick merkwürdige Verhalten lässt sich gut an einem Beispiel begründen:<br />

Das Programm soll einen Anwender beim Schließen eines Fensters fragen, ob alle<br />

bisher ungesicherten Daten gespeichert oder verworfen werden sollen, oder ob das<br />

Programm das Fensters lieber doch nicht schließen soll. Für den letzten Fall kann<br />

das AttributdoIt verwendet werden.<br />

shell.addShellListener(new ShellAdapter() {<br />

@Override<br />

public void shellClosed(ShellEvent event) {<br />

if (!fensterschliessen()) {<br />

event.doit = false;<br />

}<br />

}<br />

});<br />

Das Attribut wird auch für andere Ereignistypen verwendet, die aber bisher noch<br />

nicht vorgestellt wurden. Die API-Dokumentation ist hier wie immer hilfreich.<br />

3.6.3 Ablauf unter JFace<br />

JFace basiert auf den SWT-Ereignissen, ergänzt aber eine Abstraktionsschicht. Die Idee<br />

dahinter stellt der kommende Abschnitt vor.<br />

3.6.3.1 Abstraktion von SWT mittels Aktionen<br />

Ein kleines Beispiel einer Anwendung soll die Abstraktion vorstellen: Das Programm besitzt<br />

ein Menü sowie eine Toolbar-Zeile. In beiden befindet sich jeweils ein Eintrag, über<br />

den der Anwender seine Daten speichern kann. Er kann also auf zwei Arten dieselbe Aktion<br />

auslösen. Unter SWT würde ein Entwickler den Menüeintrag sowie die Taste in der<br />

Toolbar-Leiste separat erzeugen und einfügen. Dann müsste er noch einen Beobachter<br />

schreiben und an beiden Elementen registrieren. In JFace dagegen wird eine Operation<br />

sowie dessen Beschreibungen in einer Einheit, der Aktion, gekapselt. Eine Aktion<br />

(Klasse Action) in dem Beispiel würde also aus dem Text wie z.B. „Sichern“, einem<br />

Icon sowie einer Methode bestehen, die den Beobachter darstellt. Zusätzlich kann einer<br />

Aktion auch eine Tastenkombination <strong>zu</strong>geordnet werden, über die der <strong>zu</strong>gehörige<br />

Beobachter aufgerufen wird. Damit ist die Aktion unabhängig davon, wie sie dargestellt<br />

wird. Aus ihrer Beschreibung werden nun Menüeinträge oder Tasten für Toolbar-Leisten<br />

74


3.6 Ereignisbehandlung<br />

erzeugt. Weiterhin wird damit automatisch der Beobachter registriert. Zusammengefasst<br />

lässt sich sagen, dass eine Aktion aus diesen Komponenten besteht:<br />

ein beschreibender Text, der z.B. als Beschriftung für Tasten verwendet werden kann<br />

ein Bild für Tasten, Toolbar-Tasten usw.<br />

Tastenkombinationen als Auslöser<br />

Beobachter-Code, der bei Aktivierung der Aktion ausgeführt wird.<br />

Anhand der Abbildung 3.75 ist ersichtlich, dass aus einer Aktion Widgets erzeugt werden.<br />

Umgekehrt rufen diese die Aktion als Beobachter auf, wenn ein Ereignis eingetreten ist.<br />

2. ruft auf<br />

1. beschreibt Darstellung<br />

Aktion<br />

1. beschreibt Darstellung<br />

Abbildung 3.75: Aktion für Menüeintrag und Toolbar-Taste<br />

Die Darstellung ist teilweise plattformabhängig. So kann es vorkommen, dass die Menü-<br />

Icons unter Gnome in dessen Standard-Einstellung ausgeblendet sind. In Abbildung 3.76<br />

ist das anhand der zweiten Zeile von Screenshots gut <strong>zu</strong> erkennen.<br />

Abbildung 3.76: Darstellungen aus einer Aktion<br />

3.6.3.2 Anbindung an das Fenstersystem<br />

JFace verbirgt einen Teil der Details, die unter SWT noch direkt erkannbar waren. So fällt<br />

dann die Zustellung eines Ereignisses durch das Fenstersystem auch etwas anders aus.<br />

Betriebssystem:<br />

Ereignis-Queue<br />

Nachricht<br />

verwaltet jeweils ein Menü,<br />

eine Toolbar, ...<br />

Display<br />

Application<br />

Window<br />

Action<br />

Contribution<br />

Aufruf<br />

Hauptfenster einer<br />

Anwendung<br />

run-Methode<br />

einer Aktion<br />

Abbildung 3.77: Ereignis<strong>zu</strong>stellung unter JFace<br />

75


3.6 Ereignisbehandlung<br />

ApplicationWindow ist eine Klasse, die das Shell-Objekt kapselt (siehe Abschnitt<br />

3.1.2). Dabei werden bestimmte Methoden vorgegeben, die ein Entwickler überschreiben<br />

kann, um seine Oberfläche <strong>zu</strong> erzeugen. Eine „Contribution“ ist für die Verteilung von<br />

Ereignisses <strong>zu</strong>ständig, die aus eine Aktion erzeugt wurden. Je Menü, Toolbar-Leiste usw.<br />

gibt es ein solches Objekt. Trifft bei ihm ein Ereignis ein, dann ruft es in der <strong>zu</strong>gehörigen<br />

Aktion dierun-Methode auf. Diese Methode ersetzt diewidgetSelected-Methode der<br />

SWT-Ereignisbehandlung und wird vom Entwickler überschrieben.<br />

3.6.3.3 Programmierung<br />

Aktionen abstrahieren nur vonSelectionEvents. Andere Ereignistypen sind nicht betroffen.<br />

Anhand des folgenden Beispiels wird gezeigt, wie prinzipiell mit Aktionen gearbeitet<br />

wird. Die Aktion soll ein neues Dokument erzeugen.<br />

Abbildung 3.78: Aktion <strong>zu</strong>r Dokumenterzeugung<br />

Aktionsklassen erzeugen<br />

Zunächst muss eine Klasse erstellt werden, die die Aktion darstellt. Im einfachsten Fall<br />

erbt sie von der Klasse Action. Im Konstrutor werden Methoden der Basisklasse aufgerufen,<br />

um Texte, das Bild und sowie die Tastenkombination <strong>zu</strong>r Aktivierung der Aktion<br />

<strong>zu</strong> übergeben (BeispielActionApplication).<br />

public class NewDocumentAction extends Action {<br />

public NewDocumentAction() {<br />

// Text, der die Aktion darstellt<br />

super("&Neu"); // Text, "N" ist Mnemonic<br />

// Tooltip-Text<br />

setToolTipText("Neues Dokument");<br />

// Tastenkombination <strong>zu</strong>r Aktivierung<br />

setAccelerator(SWT.MOD1 | ’N’);<br />

}<br />

// Bild, ActionApplication.NEW_ICON ist ein<br />

// String der Beispielanwendung mit einem<br />

// Dateinamen.<br />

setImageDescriptor(ImageDescriptor.createFromFile(<br />

this.getClass(),<br />

ActionApplication.NEW_ICON));<br />

// Aktion, die bei Selektion des Eintrags ausgeführt wird.<br />

@Override<br />

public void run() {<br />

// Im Beispiel wird eine Meldung in der Statuszeile<br />

76


3.6 Ereignisbehandlung<br />

}<br />

}<br />

// des Fensters ausgegeben.<br />

ActionApplication.getApplication().setStatus(<br />

"Neues Dokument erzeugt!");<br />

Aktionsobjekte erzeugen<br />

Nach der Erstellung der Aktionsklassen können diese in der eigentlichen Anwendung<br />

eingesetzt werden. Da<strong>zu</strong> werden Objekte der Klassen erzeugt.<br />

public class ActionApplication extends ApplicationWindow {<br />

// Verwendete Icons<br />

public static final String NEW_ICON = "/res/new_wiz.gif";<br />

public static final String SAVE_ICON = "/res/save_edit.gif";<br />

// Alle benötigten Aktionen<br />

private Action newDocumentAction;<br />

private Action saveDocumentAction;<br />

private Action exitAction;<br />

// Alle Aktionsobjekte erzeugen.<br />

public ActionApplication() {<br />

super(null);<br />

newDocumentAction = new NewDocumentAction();<br />

saveDocumentAction = new SaveDocumentAction();<br />

exitAction = new ExitAction();<br />

}<br />

// Die Methode wird gleich erstellt.<br />

createContents();<br />

//...<br />

Menüeintrag aus einer Aktion erzeugen<br />

Wie in Abschnitt 3.6.3.2 beschrieben, verwaltet eine sogenannte „Contribution“ die Aktionen<br />

eines Menüs oder einer Toolbar. Für Menüs wird hier<strong>zu</strong> die KlasseMenuManager<br />

verwendet. Der Manager wird in der überschriebenen Methode createMenuManager<br />

der FensterklasseApplicationWindow erzeugt, wobei für jedes Untermenü eine eigene<br />

„Contribution“ erzeugt werden muss.<br />

// ... Fortset<strong>zu</strong>ng der Klasse ActionApplication<br />

@Override<br />

protected MenuManager createMenuManager() {<br />

// Menüzeile (Contribution) erzeugen.<br />

MenuManager mainManager = new MenuManager();<br />

// Das Dateimenü bekommt auch eine Contribution.<br />

MenuManager fileMenuManager = new MenuManager("&Datei");<br />

// Aktionen <strong>zu</strong>m Dateimenü hin<strong>zu</strong>fügen,<br />

// erzeugt den jeweiligen Menüeintrag.<br />

fileMenuManager.add(newDocumentAction);<br />

fileMenuManager.add(saveDocumentAction);<br />

// Trennstrich<br />

fileMenuManager.add(new Separator());<br />

77


3.6 Ereignisbehandlung<br />

}<br />

fileMenuManager.add(exitAction);<br />

// Dateimenü in Menüzeile einhängen.<br />

mainManager.add(fileMenuManager);<br />

// Menüzeile (Contribution) <strong>zu</strong>rückgeben.<br />

return mainManager;<br />

// ...<br />

prechenden Abbildung 3.79: Menü aus Aktionen erzeugen<br />

Coolbar-Taste aus einer Aktion erzeugen<br />

Für Toolbar- und Coolbar-Tasten funktioniert der Mechanismus genauso wie für Menüeinträge.<br />

Hier liegen die „Contributions“ in Form der beiden KlasseCoolBarManager bzw.<br />

ToolBarManager vor.<br />

// ... Fortset<strong>zu</strong>ng der Klasse ActionApplication<br />

@Override<br />

protected CoolBarManager createCoolBarManager(int style) {<br />

// CoolBar-Leiste erzeugen.<br />

CoolBarManager coolManager = new CoolBarManager(style);<br />

}<br />

// ToolBar-Leiste erzeugen.<br />

ToolBarManager fileManager = new ToolBarManager(style);<br />

// Aktionen <strong>zu</strong>r ToolBar-Leiste hin<strong>zu</strong>fügen.<br />

fileManager.add(newDocumentAction);<br />

fileManager.add(saveDocumentAction);<br />

// ToolBar-Leiste <strong>zu</strong>r CoolBar-Leiste hin<strong>zu</strong>fügen.<br />

// Das funktioniert wie bei Menüs mit vorhandenen.<br />

// Untermenüs.<br />

coolManager.add(fileManager);<br />

return coolManager;<br />

// ...<br />

Abbildung 3.80: Coolbar aus Aktionen erzeugen<br />

78


3.7 Model-View-Controller mit JFace<br />

Anwendung<br />

In Anwendung müssen jetzt noch die Methoden der BasisklasseApplicationWindow<br />

aufgerufen werden, die sicherstellen dass das Menü sowie die Coolbar auch wirklich<br />

erstellt werden.<br />

// ... Fortset<strong>zu</strong>ng der Klasse ActionApplication<br />

private createContents() {<br />

// Bewirkt, dass createMenuManager aufgerufen wird.<br />

addMenuBar();<br />

// Bewirkt, dass createCoolBarManager aufgerufen wird.<br />

addCoolBar(SWT.HORIZONTAL);<br />

// Bewirkt, dass die Statuszeile erzeugt wird.<br />

addStatusLine();<br />

}<br />

// ...<br />

Abbildung 3.81: Ergebnis auf einigen Fenstersystemen<br />

Wie Ihnen vielleicht aufgefallen ist, müssen die Tastenkombinationen für die „Accelerator“<br />

nicht mehr dem Text, mit einem Tabulator getrennt, angehängt werden. Es reicht aus, die<br />

Tastencodes mitsetAccelerator ein<strong>zu</strong>tragen.<br />

3.7 Model-View-Controller mit JFace<br />

In den bisherigen Kapiteln hat die eigentliche Anwendung immer direkt auf die Widgets<br />

<strong>zu</strong>gegriffen. Allerdings hat sich in den vergangenen Jahren sehr schnell gezeigt, dass<br />

diese fehlende Trennung zwischen Darstellung und den dargestellten Daten problematisch<br />

ist und <strong>zu</strong> einem schwer wartbaren Programm führt. Deshalb wurde als einer der<br />

ersten Lösungsansätze das MVC-Entwurfsmuster eingeführt. Den Begriff „Entwurfsmuster“<br />

gab es damals allerdings in der Informatik noch nicht. MVC wurde <strong>zu</strong>erst in Smalltalk<br />

Ende der 80er des vorigen Jahrhunderts eingesetzt. Es handelt sich dabei um eine Anwendung<br />

des Beobachter-Musters, das aus drei Teilen besteht:<br />

1. Das Model enthält die Zustandsinformation eines Widgets. Da<strong>zu</strong> könnte bei einem<br />

Texteingabefeld beispielsweise der eingegebene Text gehören.<br />

2. Der View ist Beobachter des Zustands (des Modells), um diesen bei Änderungen<br />

sofort dar<strong>zu</strong>stellen.<br />

3. Der Controller legt das Verhalten des Widgets auf Benutzereingaben fest.<br />

79


3.7 Model-View-Controller mit JFace<br />

View<br />

Controller<br />

Model<br />

Abbildung 3.82: Struktur im MVC-Ansatz<br />

So sieht der Ablauf bei einer Eingabe des Benutzers aus:<br />

Der Controller verändert das Modell.<br />

Weiterhin informiert er die Sicht bei solchen Benutzereingaben, die nicht das Modell<br />

verändern. Da<strong>zu</strong> gehört beispielsweise das Verschieben eines Textcursors.<br />

Das Modell ändert seinen Zustand und informiert seinerseits die Sicht.<br />

Die Sicht holt sich die Daten aus dem Modell und stellt sie dar.<br />

Wichtig beim MVC-Ansatz ist, dass das Modell weder Controller noch Sicht „kennt“. Der<br />

große Vorteil dieses Ansatzes besteht darin, dass die Implementierungen der Komponenten<br />

leicht ausgetauscht werden können, oder dass mehrere Sichten das Modell auf<br />

unterschiedliche Arten darstellen können.<br />

Diagramm 3.83 zeigt den Ablauf mit Pseudomethoden <strong>zu</strong>r MVC-Kommunikation mit einer<br />

Komponente, wenn der Benutzer ein Ereignis ausgelöst hat.<br />

view<br />

Component<br />

model<br />

Datamodel<br />

controller<br />

Controller<br />

1: registrieren<br />

2: Ereignis senden<br />

event<br />

AnEvent<br />

2.1.1 erzeugen<br />

2.1: Modellereignis<br />

erzeugen<br />

2.1.2: Zustandswechsel senden<br />

Abbildung 3.83: Ablauf im MVC<br />

Die nächsten Abschnitte zeigen die Verwendung von Tabellen mit und ohne Trennung<br />

von Ansicht und Modell. SWT selbst bietet keine Trennung. Erst mit JFace ist eine solche<br />

Trennung durch die Programmierschnittstelle vorgegeben. Neben Tabellen unterstützen<br />

eine ganze Anzahl weitere Widgets die Trennung. Da<strong>zu</strong> gehören mehrzeilige Texteingabefelder,<br />

Listen und Bäume.<br />

80


3.7 Model-View-Controller mit JFace<br />

3.7.1 Tabelle ohne MVC<br />

Abbildung 3.84 zeigt, wie sich eine SWT-Tabelle <strong>zu</strong>sammensetzt.<br />

TableItem<br />

Table<br />

TableColumn<br />

Abbildung 3.84: Wichtige Klassen für eine Tabelle<br />

Eine Tabelle der Klasse Table nur mit SWT <strong>zu</strong> erzeugen bedeutet, dass die komplette<br />

Tabellenstruktur manuell gebaut werden muss und dass ein direkter Lese- und Schreib<strong>zu</strong>griff<br />

auf die einzelnen Zellen erforderlich ist. Wie funktioniert so eine SWT-Tabelle?<br />

Die KlasseTable stellt eine Tabelle dar, die einzelne Spalten (TableColumn) und Zeilen<br />

in Form der Klasse TableItem visualisiert. Damit der Anwender später nicht nur<br />

Text eingeben kann, besteht die Möglichkeit, die Eingaben in einzelne Zellen durch verschiedene<br />

Editoren vor<strong>zu</strong>nehmen. Da<strong>zu</strong> wird die Klasse TableEditor eingesetzt. Die<br />

Vorlesung behandelt nur einen kleinen Aspekt der KlasseTable. Sie finden aber sowohl<br />

in der API-Dokumentation einen guten Einstieg als auch in [JavaBsp] Beispiele für eigene<br />

Experimente.<br />

3.7.1.1 Bau einer Tabelle<br />

Um eine Tabelle <strong>zu</strong> erzeugen, werden normalerweise die folgenden Schritte durchlaufen.<br />

Eine Eingabe über Editoren ist hier nicht vorgesehen.<br />

1. Erzeugen der Tabelle.<br />

table = new Table(shell, SWT.MULTI | SWT.CHECK<br />

| SWT.FULL_SELECTION);<br />

SWT_CHECK bewirkt, dass die erste Spalte <strong>zu</strong>sätzlich eine Checkbox erhält. Mit<br />

SWT.MULTI sind Mehrfachauswahlen, mitSWT.SINGLE nur Einfachauswahlen von<br />

Einträgen erlaubt. Und schließlich weistSWT.FULL_SELECTION die Tabelle an, immer<br />

die komplette Zeile bei einer Auswahl <strong>zu</strong> markieren.<br />

2. Das Neuzeichnen der Tabelle sollte während ihrer Erzeugung verhindern. Das ist<br />

wichtig, weil ansonsten die Darstellung der Tabelle flackern kann. Außerdem würde<br />

ein Neuzeichnen nach jedem Schritt den Aufbau deutlich verlangsamen.<br />

table.setRedraw(false);<br />

81


3.7 Model-View-Controller mit JFace<br />

3. Jede Spalte muss einzeln angelegt werden. Die Tabelle ist immer das Vaterelement.<br />

In der folgenden Abbildung ist die Spalte rot markiert, die durch den anschließenden<br />

Code-Schnippsel erzeugt wird.<br />

TableColumn tcol = new TableColumn(table, SWT.LEFT);<br />

tcol.setText("Auswahl");<br />

Mit SWT.LEFT wird der Inhalt linksbündig ausgerichtet. Erlaubt sind auch die AngabenSWT.CENTER<br />

undSWT.RIGHT. Die MethodesetText trägt den Spaltentitel<br />

ein.<br />

4. Jede Zeile muss mit ihren Daten angelegt werden. Auch hier ist das Vaterelement<br />

die Tabelle. Die nächste Abbildung zeigt rot markiert die Zeile, die durch den Code-<br />

Ausschnitt darunter erzeugt wird.<br />

TableItem item = new TableItem(table, SWT.NONE);<br />

item.setText(new String[]{"Zeilenwert 0", "Zeilenwert 1",<br />

"Zeilenwert 2"});<br />

Die einzige erlaubte Konstante istSWT.NONE, weil eine Tabellenzeile keine eigenen<br />

Attribute besitzt. Die Methode setText erhält ein eindimensionales Array mit den<br />

Werten einer kompletten Zeile.<br />

5. SWT kann die Spaltenbreiten automatisch anhand des Inhaltes berechnen. Da<strong>zu</strong><br />

muss auf jedem Spaltenobjekt die pack-Methode aufgerufen werden. Später bei<br />

der Einführung virtueller Tabellen werden Sie sehen, wie die Spaltenbreiten manuell<br />

vergeben werden können.<br />

table.getColumn(colIndex).pack();<br />

6. Zum Schluss muss das Neuzeichnen der Tabelle wieder <strong>zu</strong>gelassen werden.<br />

table.setRedraw(true);<br />

82


3.7 Model-View-Controller mit JFace<br />

3.7.1.2 Beispiel<br />

Die Beispielanwendung (KlasseTableSWTApplication) enthält den fast vollständigen<br />

Code, um die Tabelle aus Abbildung 3.84 <strong>zu</strong> erzeugen.<br />

public class TableSWTApplication {<br />

// ...<br />

// Tabellendaten<br />

private String[][] data;<br />

// Spaltenüberschriften<br />

private static final String[] COL_HEADERS =<br />

{ "Auswahl", "Spalte 1", "Spalte 2" };<br />

// Tabelle bei der Konstruktion der Oberfläche anlegen<br />

private void createGUI() {<br />

// ...<br />

// Tabelle erzeugen: Mehrfachselektion, Checkbox in der<br />

// ersten Spalte, Selektion erstreckt sich immer über<br />

// alle Spalten<br />

table = new Table(shell, SWT.MULTI | SWT.CHECK<br />

| SWT.FULL_SELECTION);<br />

// Spaltenüberschriften sollen sichtbar sein.<br />

table.setHeaderVisible(true);<br />

// Trennlinien zwischen den Zellen werden angezeigt.<br />

table.setLinesVisible(true);<br />

// Neuzeichnen während der Konstruktion der Tabelle<br />

// verhindern, damit es schneller geht.<br />

table.setRedraw(false);<br />

// Tabellenspalten erzeugen und Überschriften eintragen.<br />

for (int col = 0; col < COL_HEADERS.length; col++) {<br />

TableColoumn col = new TableColumn(table, SWT.LEFT);<br />

col.setText(COL_HEADERS[ col ]);<br />

}<br />

// Zeilen in die Tabelle einfügen und mit Daten füllen.<br />

for (int rowIndex = 0; rowIndex < data.length; rowIndex++) {<br />

TableItem item = new TableItem(table, SWT.NONE);<br />

item.setText(data[ rowIndex ]);<br />

}<br />

// Spaltenbreiten berechnen lassen. Ansonsten sind nicht<br />

// alle Inhalte lesbar.<br />

for (int col = 0; col < table.getColumnCount(); col++) {<br />

table.getColumn(col).pack();<br />

}<br />

}<br />

// Neuzeichnen der Tabelle wieder <strong>zu</strong>lassen.<br />

table.setRedraw(true);<br />

// ...<br />

}<br />

// ...<br />

83


3.7 Model-View-Controller mit JFace<br />

3.7.1.3 Tabellen mit sehr vielen Zeilen<br />

Was passiert, wenn die Tabelle sehr viele Einträge besitzt (z.B. mehr als 1000)? Die Einträge<br />

sind aber niemals alle gleichzeitig sichtbar. Trotzdem werden <strong>zu</strong>nächst einmal alle<br />

Zeilenobjekte (TableItem) erzeugt. Das bedeutet, dass sowohl die Konstruktion sehr<br />

lange Zeit benötigt, als auch, dass Speicherplatz verschwendet wird. SWT bietet als Lösung<br />

des Problems virtuelle Tabellen, die ihre Daten dann dynamisch durch Ereignisse<br />

anfordert, wenn diese Daten sichtbar werden sollen. Die Konstruktion einer solchen Tabelle<br />

unterscheidet sich doch deutlich von der einer statisch erzeugten.<br />

1. Zuerst muss die Tabelle angelegt werden. Zum Kennzeichnen, dass die Daten dynamisch<br />

<strong>zu</strong>r Laufzeit bereitgestellt werden, muss die Konstante SWT.VIRTUAL übergeben<br />

werden.<br />

table = new Table(shell, SWT.MULTI | SWT.CHECK<br />

| SWT.FULL_SELECTION | SWT.VIRTUAL);<br />

2. Anschließend werden die Spalten mit ihren Überschriften angelegt. Weil die Tabelle<br />

immer nur die sichtbaren Daten dynamisch anfordert, kann sie nicht selbst ermitteln,<br />

wieviel Platz jede einzelne Spalte benötigt. Somit kann die Tabelle auch nicht<br />

automatisch die Spaltenbreiten festlegen. Das muss der Entwickler manuell durch<br />

Aufruf der MethodesetWidth auf jeder einzelnen Spalte durchführen.<br />

TableColumn tcol = new TableColumn(table, SWT.LEFT);<br />

tcol.setText("Auswahl");<br />

tcol.setWidth(200);<br />

3. Wichtig ist noch, dass ein SetData-Listener an der Tabelle registriert wird. Über<br />

ihn meldet die Tabelle immer dann ein Ereignis, wenn sie neue Daten anzeigen<br />

muss. Im Beobachter werden die neuen Daten in die Tabelle bzw. dasTableItem-<br />

Objekt eingetragen. Das Ereignisobjekt hält in seinem öffentlichen Attribut index<br />

die Nummer der Zeile, für die neue Daten benötigt werden.<br />

table.addListener(SWT.SetData, new Listener() {<br />

public void handleEvent(Event event) {<br />

// "data" ist in diesem Beispiel ein Array mit den<br />

// Tabellendaten. Das Array wird intern von der<br />

// Anwendung verwaltet. data[event.index] liefert<br />

// ein eindimensionales Array mit den Zellwerten<br />

// für alle Spalten einer Zeile.<br />

((TableItem) event.item).setText(data[event.index]);<br />

}<br />

});<br />

Das BeispielVirtualTableSWTApplication enthält den vollständigen Quelltext, um<br />

eine virtuelle Tabelle für Abbildung 3.84 <strong>zu</strong> erzeugen.<br />

3.7.1.4 Weitere Eigenschaften<br />

SWT-Tabellen sind mächtiger, als die vorhergehenden Kapitel vermuten lassen. So können<br />

Spalten durch Klick in den Tabellenkopf sortiert werden. Weiterhin können die Ausgabe<br />

der Werte in den Zellen sowie die Eingabemöglichkeit durch den Anwender vielfältig<br />

verändert werden. Das Beispiel VirtualEditableTableSWTApplication zeigt die<br />

prinzipielle Funktionsweise. Da dieses aber relativ umständlich ist und JFace hier einfachere<br />

Möglichkeiten bietet, soll an dieser Stelle lediglich kurz auf das Sortieren der<br />

Einträge verwiesen werden:<br />

84


3.7 Model-View-Controller mit JFace<br />

1. Zunächst muss ein Beobachter an der Tabelle registriert werden, der auf Klicks in<br />

die Spaltenköpfe reagiert.<br />

2. Wird der Beobachter aufgerufen, dann muss er die Tabellendaten selbst sortieren.<br />

3. Anschließend löscht er den kompletten Tabelleninhalt und fügt die sortieren Daten<br />

wieder in die Tabelle ein. Alternativ ist es häufig sinnvoller, statt alle TableItem-<br />

Objekte <strong>zu</strong> löschen und erneut an<strong>zu</strong>legen, diese nur mit den sortierten Daten neu<br />

<strong>zu</strong> befüllen.<br />

Damit sind aber leider nicht alle Probleme gelöst. So findet hier keine echte Trennung<br />

von Daten und ihrer Darstellung statt. Genau diese bietet aber JFace, so dass in den<br />

kommenden Abschnitten auf den dort vorhandenen MVC-Ansatz eingegangen wird.<br />

3.7.2 Tabelle mit MVC<br />

JFace bietet bei einigen Komponenten, die komplexere Datenstrukturen visualisieren,<br />

einen sauberen MVC-Ansatz:<br />

Für die SWT-KlasseTree steht einTreeViewer <strong>zu</strong>r Verfügung.<br />

Die SWT-TabelleTable wird von einemTableViewer gekapselt.<br />

Für die SWT-ListeList stelltListViewer den MVC-Ansatz <strong>zu</strong>r Verfügung.<br />

Die AuswahllisteCombo aus SWT wird vomComboViewer gekapselt.<br />

Die erforderlichen JFace-Klassen sind alle im Paketorg.eclipse.jface.viewers <strong>zu</strong><br />

finden. Da die Arbeit mit allen Viewer-Klassen relativ ähnlich ist, soll hier nur das Prinzip<br />

anhand der Tabelle gezeigt werden. Alle Beispiele funktionieren mit Eclipse ab Version<br />

3.3. Für ältere Versionen gibt es auch entsprechende Lösungen, die aber nicht unbedingt<br />

mehr <strong>zu</strong> empfehlen sind. Das folgende vereinfachte Klassendiagramm zeigt die wichtigsten<br />

Klassen für den TableViewer. Die nicht mehr relevanten älteren Klassen fehlen.<br />

Mit blauer Schrift sind die Klassen gekennzeichnet, die vom Benutzer in seiner eigenen<br />

Anwendung erstellt werden. Die Namen sind nur beispielhaft <strong>zu</strong> sehen.<br />

Table<br />

TableColumn<br />

SWT<br />

stellt dar<br />

1<br />

stellt dar<br />

1<br />

TableViewer<br />

1 gehört <strong>zu</strong><br />

TableViewerColumn<br />

1<br />

eingeben<br />

<br />

EditingSupport<br />

holt Daten über<br />

1<br />

<br />

IContentProvider<br />

<br />

CellLabelProvider<br />

ColumnLabelProvider<br />

stellt dar<br />

1<br />

MyEditingSupport<br />

verwendet<br />

1<br />

<br />

CellEditor<br />

<br />

IStructuredContent<br />

Provider<br />

MyContentProvider<br />

MyLabelProvider<br />

Darstellung<br />

TextCellEditor<br />

Eingabe<br />

ModellDaten<br />

Modell<br />

Abbildung 3.85: Wichtige Klassen für eine JFace-Tabelle<br />

85


3.7 Model-View-Controller mit JFace<br />

Gut <strong>zu</strong> erkennen sind die unterschiedlichen Klassen für das Modell, die Ansicht und die<br />

Benutzereingabe. Die grau hinterlegte Bereich „SWT“ beinhaltet einen Teil der Klassen,<br />

die JFace von SWT nutzt.<br />

3.7.2.1 Modell<br />

Dieser Abschnitt beschäftigt sich mit dem Modell sowie dessen Anbindung an die Tabelle.<br />

Table<br />

TableColumn<br />

SWT<br />

stellt dar<br />

1<br />

stellt dar<br />

1<br />

TableViewer<br />

1 gehört <strong>zu</strong><br />

TableViewerColumn<br />

1<br />

eingeben<br />

<br />

EditingSupport<br />

holt Daten über<br />

1<br />

<br />

IContentProvider<br />

<br />

CellLabelProvider<br />

ColumnLabelProvider<br />

stellt dar<br />

1<br />

MyEditingSupport<br />

verwendet<br />

1<br />

<br />

CellEditor<br />

<br />

IStructuredContent<br />

Provider<br />

MyContentProvider<br />

MyLabelProvider<br />

Darstellung<br />

TextCellEditor<br />

Eingabe<br />

ModelData<br />

Modell<br />

Abbildung 3.86: Modellklassen für die JFace-Tabelle<br />

Um ein Modell für eine Tabelle <strong>zu</strong> erstellen, ist keine direkte Abhängigkeit von einer<br />

JFace-Klasse erforderlich. Im einfachsten Fall besteht das Modell aus einerArrayList<br />

mit Objekten. Jedes Objekt repräsentiert eine Zeile der Tabelle. Durch Filter kann die<br />

Anzeige bestimmer Zeilen (Objekte) unterdrückt werden. Eine separate Sortierung erlaubt<br />

es, die Reihenfolge in der Ansicht anhand von Kriterien vertauschen <strong>zu</strong> lassen.<br />

Denkbar ist beispielsweise, den Tabelleninhalt anhand des Nachnamens einer Person<br />

<strong>zu</strong> sortieren. Dabei wird nicht der Modellinhalt verändert. Es handelt sich lediglich um<br />

eine Darstellung der Zeilen, die nicht der Reihenfolge im Modell entspricht.<br />

Die folgende Beispiel zeigt eine einfache KlassePerson mit dem Namen und dem Alter<br />

einer Person.<br />

public class Person {<br />

private String name;<br />

private int age;<br />

// ...<br />

}<br />

Das Modell enthält eine ArrayList mit Person-Objekten, die wie in Abbildung 3.87<br />

dargestellt werden soll.<br />

86


3.7 Model-View-Controller mit JFace<br />

public class Person {<br />

private String name;<br />

private int age;<br />

// ...<br />

}<br />

ArrayList<br />

Abbildung 3.87: JFace-Tabelle für eine Liste von Personen<br />

Mit diesem Modell kann die Tabelle noch gar nichts anfangen. Um die Daten aus dem Modell<br />

in die Tabelle <strong>zu</strong> kopieren, wird die Schnittstelle IStructuredContentProvider<br />

benötigt. Der Entwickler stellt eine modellspezifische Klasse bereit, die diese Schnittstelle<br />

implementiert und die Modelldaten so aufbereitet, dass die Tabelle sie darstellen<br />

kann. Da<strong>zu</strong> müssen die Modelldaten in ein Array mit Objekten konvertiert werden.<br />

Das ist im Falle einer ArrayList recht einfach, weil diese mit der Methode toArray<br />

schon die erforderliche Konvertierung besitzt. Darüber hinaus informiert die Tabelle den<br />

IStructuredContentProvider über eine komplette Änderungen der Daten in der<br />

Tabelle. Abbildung 3.88 zeigt den Ablauf der Interaktion.<br />

application<br />

viewer<br />

TableViewer<br />

provider<br />

IStructuredContentProvider<br />

1: setInput(dataModell)<br />

1.1: inputChanged(dataModell)<br />

2: getElements(dataModell)<br />

liefert eine Array-<br />

Darstellung des<br />

Datenmodells<br />

dataModell.<br />

Abbildung 3.88: Interaktion zwischen JFace-Tabelle und Modell<br />

Die Anwendung trägt die eigentlichen Modelldaten mitsetInput in die Tabelle ein. Diese<br />

speichert die Daten und informiert den IStructuredContentProvider darüber,<br />

dass sich die Daten geändert haben. Er kann jetzt auf die Datenänderung reagieren,<br />

falls das erforderlich sein sollte. Wenn die Tabelle die Daten <strong>zu</strong> einem späteren Zeitpunkt<br />

anzeigen möchte, dann kann sie mit den beisetInput gespeicherten Daten aber<br />

nicht direkt etwas anfangen. So fordert sie eine Arrayrepräsentation der gepsicherten Daten<br />

durch Aufruf der MethodegetElements an. DerIStructuredContentProvider<br />

agiert also im einfachsten Fall als Konverter für die Modelldaten.<br />

Ein kleines Beispiel soll die Anwendung des Prinzips zeigen. Sie finden den Quelltext<br />

in den PaketentableViewer.person.viewer undtableViewer.person.common<br />

des Eclipse-Projektes <strong>zu</strong>r Vorlesung. Das Beispiel verwaltet die Personenobjekte aus<br />

der Einführung in diesem Kapitel. Da<strong>zu</strong> implementiert PersonContentProvider die<br />

SchnittstelleIStructuredContentProvider mit ihren drei Methoden:<br />

getElements konvertiert die Modelldaten in ein Array mit Objekten. Jeder Eintrag<br />

des Arrays entspricht einer Tabellenzeile.<br />

In dispose kann der Konverter eventuell angelegte Ressourcen wieder freigeben,<br />

bevor er selbst freigegeben wird.<br />

87


3.7 Model-View-Controller mit JFace<br />

inputChanged wird von dem TableViewer aufgerufen, wenn neue Daten in die<br />

Tabelle eingetragen werden.<br />

Im Beispiel ist lediglich die KonvertierungsmethodegetElements von Interesse, da hier<br />

keine Ressourcen verwaltet und auch keine Änderungen gespeichert werden.<br />

public class PersonContentProvider<br />

implements IStructuredContentProvider {<br />

@Override<br />

public Object[] getElements(Object inputElement) {<br />

return ((List) inputElement).toArray();<br />

}<br />

}<br />

@Override<br />

public void dispose() {<br />

// keine Ressourcen angelegt<br />

}<br />

@Override<br />

public void inputChanged(Viewer viewer, Object oldInput,<br />

Object newInput) {<br />

// interessiert uns hier nicht<br />

}<br />

Jetzt kann der Konverter verwendet werden. Da<strong>zu</strong> wird eine ArrayList mit einigen<br />

Personenobjekten als unser Modell gefüllt. Danach wird der TableViewer im Layout<br />

angelegt. Er wiederum erzeugt intern die benötigteTable aus dem SWT. Abschließend<br />

erhält derTableViewer den Konverter übergeben.<br />

public class TableJFacePersonApplication<br />

extends ApplicationWindow {<br />

}<br />

private TableViewer tableViewer;<br />

private ArrayList personModel<br />

= new ArrayList();<br />

protected Control createContents(Composite parent) {<br />

personModel.add(new Person("Name 1", 42));<br />

personModel.add(new Person("Name 2", 66));<br />

}<br />

tableViewer = new TableViewer(parent, SWT.FULL_SELECTION);<br />

tableViewer.setContentProvider(new PersonContentProvider());<br />

// usw.<br />

Abbildung 3.89 zeigt, dass diese Schritte noch nicht ausreichen.<br />

88


3.7 Model-View-Controller mit JFace<br />

Fertig? Nein:<br />

Abbildung 3.89: Ausgabe der JFace-Tabelle (noch unvollständig)<br />

Die Tabelle ist nicht <strong>zu</strong> sehen, weil JFace nicht automatisch die Spalten erzeugen kann.<br />

Dass muss dann doch wie bei SWT manuell geschehen. In JFace dient hier<strong>zu</strong> die Klasse<br />

TableViewerColumn, deren Objekte jeweils eine Spalte darstellen. Die Spalten<br />

erhalten als ersten Parameter wie immer das Vaterobjekt, was eigentlich immer der<br />

TableViewer ist. Der zweite Parameter bestimmt die Ausrichtung des Spalteninhaltes<br />

(rechts, links, zentriert).<br />

Table table = tableViewer.getTable();<br />

// Fortset<strong>zu</strong>ng der Methode createContents<br />

// aus dem vorherigen Quellcode.<br />

TableViewerColumn column;<br />

column = new TableViewerColumn(tableViewer, SWT.LEFT);<br />

column.getColumn().setText("Name");<br />

column.getColumn().setWidth(200);<br />

column = new TableViewerColumn(tableViewer, SWT.RIGHT);<br />

column.getColumn().setText("Age");<br />

column.getColumn().setWidth(80);<br />

table.setHeaderVisible(true); // sichtbare Spaltentitel<br />

table.setLinesVisible(true); // sichtbare Linien<br />

Auch das ist noch nicht ausreichend. Die Tabelle erhält durch den Konverter zwar ein<br />

Array aller Modellobjekte, kann aber nicht selbständig entscheiden, wie die Attribute der<br />

einzelnen Objekte in den Zeilen dargestellt werden sollen.<br />

3.7.2.2 View<br />

Nachdem der vorherige Abschnitt beschrieben hat, wie die Tabelle erzeugt wird und die<br />

wie das Modell die Daten bereitstellt, soll jetzt die Darstellung der Modelldaten untersucht<br />

werden.<br />

89


3.7 Model-View-Controller mit JFace<br />

Table<br />

TableColumn<br />

SWT<br />

stellt dar<br />

1<br />

stellt dar<br />

1<br />

TableViewer<br />

1 gehört <strong>zu</strong><br />

TableViewerColumn<br />

1<br />

eingeben<br />

<br />

EditingSupport<br />

holt Daten über<br />

1<br />

<br />

IContentProvider<br />

<br />

CellLabelProvider<br />

ColumnLabelProvider<br />

stellt dar<br />

1<br />

MyEditingSupport<br />

verwendet<br />

1<br />

<br />

CellEditor<br />

<br />

IStructuredContent<br />

Provider<br />

MyContentProvider<br />

MyLabelProvider<br />

Darstellung<br />

TextCellEditor<br />

Eingabe<br />

ModelData<br />

Modell<br />

Abbildung 3.90: Ansichtsklassen für die JFace-Tabelle<br />

Die Idee hier ist auch nicht sonderlich kompliziert. Für jede Spalte muss ein Formatierer<br />

bereitgestellt werden, der aus einem (oder mehreren) Attributen der Modellklasse einen<br />

String erzeugt, der dann in genau dieser Zelle angezeigt wird. Man kann sich leicht vorstellen,<br />

dass beispielsweise Datumswerte, die im Modell alsDate-Objekte abgelegt sind,<br />

ganz unterschiedlich angezeigt werden können: Soll es in einem kurzen oder langen Format<br />

dargestellt werden bzw. welche länderspezifische Einstellungen ist vorhanden?<br />

Auch hier wird wieder nur ein Ansatz betrachtet, der ab Eclipse 3.3 funktioniert. Für ältere<br />

Versionen existieren ebenso Lösungen, die aber durch neuere überflüssig geworden<br />

sind. Jeder Formatierer ist eine Klasse, die von CellLabelProvider erbt. Die Klasse<br />

besitzt einige Methoden, die überschrieben werden können.<br />

getFont liefert den Zeichensatz <strong>zu</strong>rück, mit dem die Werte in dieser Spalte dargestellt<br />

werden sollen.<br />

getBackground gibt die Hintergrundfarbe für die Spaltenwerte <strong>zu</strong>rück.<br />

getForeground bestimmt die Vordergrundfarbe für die Spaltenwerte.<br />

getImage liefert als Ergebnis ein Bildobjekt <strong>zu</strong>r Darstellung der Werte.<br />

getText ist sicherlich die wichtigste Methode. Sie gibt den fertig formatierten String<br />

<strong>zu</strong>rück, der den Wert eines oder mehrerer Attribute des Modells darstellt.<br />

Alle Methoden bekommen das Modellobjekt als einzigen Parameter übergeben. Es müssen<br />

nur die Methoden überschrieben werden, die für die Anwendung relevant sind. Soll<br />

beispielsweise kein Bild verwendet werden, dann muss getImage nicht überschrieben.<br />

Das gilt auch für die Farben und den Zeichensatz. Diese Methoden sind nur dann erforderlich,<br />

wenn die Vorgaben abgewandelt werden sollen.<br />

Der Entwickler erstellt für jede Spalte einen eigenen Formatierer und registriert ihn an<br />

dieser. Das Beispiel mit den Personen lässt sich so fortsetzen, wobei statt separater<br />

Klassen auch anonyme innere Klassen möglich sind.<br />

Der Name der Person wird aus dem übergebenen Objekt entnommen. Da<strong>zu</strong> muss<br />

das Datenobjekt in ein Person-Objekt „gecastet“ werden, so dass das Namensattribut<br />

ausgelesen werden kann.<br />

90


3.7 Model-View-Controller mit JFace<br />

// für den Namen einer Person<br />

public class PersonNameLabelProvider<br />

extends ColumnLabelProvider {<br />

public String getText(Object element) {<br />

return ((Person) element).getName();<br />

}<br />

}<br />

Das Alter der Person ist ein ganzzahliger Wert. Er muss in einen String umgewandelt<br />

werden.<br />

// für das Alter einer Person<br />

public class PersonAgeLabelProvider<br />

extends ColumnLabelProvider {<br />

public String getText(Object element) {<br />

return String.valueOf(((Person) element).getAge());<br />

}<br />

}<br />

Jetzt müssen die beiden Formatierer noch an den Spalten registriert werden.column ist<br />

eine Spalte der KlasseTableViewerColumn aus dem Codefragment direkt nach Abbildung<br />

3.89. Wichtig ist, nach dem Registrieren noch die Modelldaten demTableViewer<br />

<strong>zu</strong> übergeben.<br />

// ...<br />

// An den Spalten registrieren, hier exemplarisch<br />

// für die Namensspalte:<br />

column.setLabelProvider(new PersonNameLabelProvider());<br />

// Danach müssen die Modelldaten dem TableViewer<br />

// übergeben werden.<br />

tableViewer.setInput(personModel);<br />

Jetzt sieht die Darstellung schon ganz gut aus (Abbildung 3.91).<br />

Abbildung 3.91: Ausgabe der JFace-Tabelle mit Formatierer<br />

Dynamische Spaltenbreiten<br />

Was passiert, wenn der Anwender die Größe des Dialogs verändert? Die Spaltenbreiten<br />

der Tabelle passen sich nicht an, so dass neben der Tabelle im Falle der Vergrößerung<br />

ungenutzter Platz entsteht. Abbildung 3.92 zeigt das Problem.<br />

91


Dynamische Spaltenbreiten?<br />

3.7 Model-View-Controller mit JFace<br />

Abbildung 3.92: Fehlende dynamische Spaltenanpassung bei einer Tabelle<br />

Zur Lösung des Problems besitzt JFace den speziell für diese Tabellen entworfenen<br />

Layout-Manager TableColumnLayout. Es ist in der Lage, die Spalten einer Tabelle<br />

automatisch an den <strong>zu</strong>r Verfügung stehenden Platz an<strong>zu</strong>passen. Da<strong>zu</strong> muss allerdings<br />

die Tabelle das alleinige Widget in einem Composite mit TableColumnLayout<br />

sein. Dann werden die Spaltenbreiten entweder absolut in Pixeln durch Objekte der<br />

Klasse ColumnPixelData oder prozentual bezogen auf den <strong>zu</strong>r Verfügung stehenden<br />

Platz durch Objekte der Klasse ColumnWeightData festgelegt. Es können auch<br />

minimale Spaltenbreiten erzwungen werden, damit bestimmte Inhalte immer sichtbar<br />

sind. Die erforderlichen Attribute <strong>zu</strong>r Steuerung des Verhaltens lassen sich entweder<br />

direkt setzen oder aber schon im Konstruktor der jeweiligen Klasse übergeben. Die API-<br />

Dokumentation zeigt die Details.<br />

Die Implementierung TableJFacePersonResizableColumnsApplication erweitert<br />

den aktuellen Stand der Personenanwendung TableJFacePersonApplication<br />

so, dass Spalten prozentuale Breiten <strong>zu</strong>gewiesen bekommen und dass sich die Spalten<br />

dynamisch anpassen.<br />

Zunächst muss die Tabelle jetzt in einem separatenComposite-Objekt erzeugt werden,<br />

das die Tabelle mittelsTableColumnLayout anordnet.<br />

Composite tableComp = new Composite(parent, SWT.NONE);<br />

TableColumnLayout tcl = new TableColumnLayout();<br />

tableComp.setLayout(tcl);<br />

tableViewer = new TableViewer(tableComp, SWT.FULL_SELECTION);<br />

Die Spaltenbreiten werden direkt dem Layout und nicht den Spalten selbst übergeben.<br />

In diesem Beispiel sollen relative Spaltenbreiten verwendet werden, die sich bei dynamischer<br />

Breitenänderung automatisch anpassen. Eine Minimalbreite wird nicht erzwungen.<br />

// SWT-Tabelle auslesen<br />

Table table = tableViewer.getTable();<br />

// erste Spalte erzeugen (linksbündig)<br />

TableViewerColumn column = new TableViewerColumn(tableViewer,<br />

SWT.LEFT);<br />

// Spaltentitel eintragen<br />

column.getColumn().setText("Name");<br />

// Die Spalte belegt immer 70% der Platzes.<br />

// Eine Größenanpassung ist erlaubt.<br />

tcl.setColumnData(column.getColumn(),<br />

new ColumnWeightData(70, true));<br />

Jetzt simmt das Verhalten (Abbildung 3.93).<br />

92


3.7 Model-View-Controller mit JFace<br />

Abbildung 3.93: Korrekte dynamische Spaltenanpassung bei einer Tabelle<br />

Sortierung<br />

In vielen Anwendungen wie beispielsweise dem Explorer unter Windows oder dem Dateimanager<br />

und LinuX kann der Anwender die Tabellendaten anhand der Werte einer<br />

Spalte sortiert anzeigen, indem er auf den entsprechenden Spaltentitel klickt (siehe Abbildung<br />

3.94).<br />

Klick auf Namen-Header<br />

Klick auf Alter-Header<br />

Abbildung 3.94: Sortierung einer Tabelle<br />

Solch ein Verhalten lässt sich auch für JFace-Tabellen implementieren. Da<strong>zu</strong> muss der<br />

Entwickler der Tabelle im Wesentlichen vorgeben, wie einzelne Werte in den Spalten verglichen<br />

werden sollen. Da Spalten ja ganz unterschiedliche Werte darstellen, kann JFace<br />

das nicht automatisch für alle möglichen Datentypen selbst durchführen. Der Ablauf sieht<br />

so aus:<br />

Ein „Sortierer“ (eigentlich handelt es sich eher um einen Vergleicher) erbt von der<br />

BasisklasseViewerSorter und überschreibt derencompare-Methode. Diese Methode<br />

wird von der Tabelle immer aufgerufen, um paarweise zwei Spaltenwerte miteinander<br />

<strong>zu</strong> vergleichen. Der „Sortierer“ muss darüber hinaus in der Lage sein, sowohl<br />

aufsteigende als auch absteigende Sortierungen <strong>zu</strong> unterstützen. Das erwartete<br />

Verhalten besteht nämlich darin, bei jedem Klick auf denselben Spaltentitel die<br />

Sortierreihenfolge um<strong>zu</strong>kehren.<br />

Der „Sortierer“ wird amTableViewer registriert.<br />

Schließlich fehlt noch die Auslösung der Sortierung bzw. die Umkehr der Sortierreihenfolge.<br />

Da<strong>zu</strong> wird ein Beobachter (einSelectionListener) an jedem Tabellentitel<br />

registriert. Dieser teilt dem „Sortierer“ mit, anhand welcher Spalte sortiert werden<br />

soll und aktualisiert anschließend die Tabellendarstellung. Der Sortierer muss<br />

93


3.7 Model-View-Controller mit JFace<br />

intern unterscheiden, ob direkt hintereinander anhand derselben Spalte sortiert wird.<br />

Dann muss er jedes Mal die Reihenfolge umdrehen.<br />

Prinzipiell kann für jede Spalte ein eigener „Sortierer“ erstellt werden. Im folgenden Beispiel<br />

dagegen kann der „Sortierer“ für alle Spalten verwendet werden, weil das Modell<br />

nicht sehr komplex ist.<br />

Die KlassePersonSorter stellt in diesem Beispiel den „Sortierer“ dar.<br />

public class PersonSorter extends ViewerSorter {<br />

// mögliche Sortiereihenfolgen: aufsteigend<br />

// und absteigend<br />

public enum Direction { ASCENDING, DESCENDING };<br />

// Aktuelle Spalte, anhand derer sortiert wurde.<br />

// Bleibt bei zwei Aufrufen die Spalte gleich,<br />

// dann ändert sich die Sortierreihenfolge.<br />

private int sortColumn;<br />

// Aktuelle Sortierreihenfolge<br />

private Direction sortDirection = Direction.DESCENDING;<br />

// Diese Methode wird bei jedem Klick in einen Spaltentitel<br />

// vom Beobachter aufgerufen. Wenn sich die Spalte ändert,<br />

// wird immer aufsteigend sortiert. Ansonsten wird die<br />

// Sortierreihenfolge umgedreht.<br />

public void doSort(int column) {<br />

if (column == sortColumn) {<br />

sortDirection = (sortDirection == Direction.ASCENDING)<br />

? Direction.DESCENDING<br />

: Direction.ASCENDING;<br />

}<br />

else {<br />

sortColumn = column;<br />

sortDirection = Direction.ASCENDING;<br />

}<br />

}<br />

// Diese Methode wird von der Tabelle aufgerufen, um<br />

// paarweise zwei Werte einer Spalte miteinander <strong>zu</strong><br />

// vergleichen. Die Methode verhält sich bezüglich der<br />

// Rückgabewerte exakt so wie die entsprechende Methode<br />

// der Java-Klasse java.util.Comparator.<br />

public int compare(Viewer viewer, Object object1,<br />

Object object2) {<br />

int result = 0;<br />

Person person1 = (Person) object1;<br />

Person person2 = (Person) object2;<br />

switch (sortColumn) {<br />

case PersonIndices.NAME_INDEX:<br />

result = getComparator().compare(person1.getName(),<br />

person2.getName());<br />

break;<br />

case PersonIndices.AGE_INDEX:<br />

result = person1.getAge() - person2.getAge();<br />

break;<br />

}<br />

result = sortDirection == Direction.DESCENDING<br />

? result : -result;<br />

94


3.7 Model-View-Controller mit JFace<br />

}<br />

}<br />

return result;<br />

Der „Sortierer“ kann in seiner Standardimplementierung auch Objekte <strong>zu</strong> Gruppen (Kategorien)<br />

<strong>zu</strong>sammenfassen und innerhalb der Gruppen sortieren. Nähere Informationen<br />

liefert die API-Dokumentation.<br />

Jetzt muss der „Sortierer“ amTableViewer registriert werden.<br />

final PersonSorter sorter = new PersonSorter();<br />

tableViewer.setSorter(sorter)<br />

Abschließend darf der Beobachter nicht fehlen, damit die Klicks auf die Spaltentitel auch<br />

erfasst werden. Das Beispiel zeigt nur die Anmeldung an der Namens-Spalte.<br />

column.getColumn().addSelectionListener(<br />

new SelectionAdapter() {<br />

@Override<br />

public void widgetSelected(SelectionEvent e) {<br />

// Zu sortierende Spalte in den "Sortierer" eintragen.<br />

sorter.doSort(PersonIndices.NAME_INDEX);<br />

// Tabelle zwingen, sich neu <strong>zu</strong> zeichnen.<br />

tableViewer.refresh();<br />

}<br />

});<br />

Filterung<br />

Enthalten Tabellen sehr viele Zeilen, dann möchte der Anwender eventuell irrelevante<br />

Informationen ausblenden. Genau da<strong>zu</strong> dient der Filter. Im Windows-Explorer wird diese<br />

Eigenschaft unter Anderem da<strong>zu</strong> benutzt, um versteckte Dateien <strong>zu</strong> verbergen. Der<br />

prinzipielle Ablauf <strong>zu</strong>r Filterung sieht so aus:<br />

Der Entwickler schreibt eine eigene Klasse, die von der abstrakten Basisklasse<br />

ViewerFilter erbt.<br />

In dieser Klasse überschreiben er die Methodeselect, die bestimmt, ob ein Objekt<br />

(z.B. eine Person) angezeigt werden soll oder nicht. Diese Methode wird von der<br />

Tabelle für jede Zeile aufgerufen.<br />

JFace-Tabellen unterstützen die gleichzeitige Verwendung beliebig vieler Filter. Der<br />

Entwickler kann also alle Filter am TableViewer mit setFilters registrieren.<br />

Die Tabelle befragt für jede Zeile alle Filter, ob das entsprechende Objekt angezeigt<br />

werden soll oder nicht.<br />

Virtuelle Tabellen<br />

Ebenso wie SWT unterstützt auch JFace virtuelle Tabellen, um große Datenmengen effizient<br />

handhaben <strong>zu</strong> können. Der bisher in Abschnitt 3.7.2.1 vorgestellte Ansatz mit dem<br />

IStructuredContentProvider funktioniert dafür aber nicht mehr. Statt dessen kann<br />

derILazyContentProvider eingesetzt werden, der nicht mehr alle Modelldaten, sondern<br />

einzelne Zeilen <strong>zu</strong>rückliefert. Um hier auch ein eventuell zeitaufwändiges Sortieren<br />

und Filtern <strong>zu</strong> ermöglichen, kann der DeferredContentProvider bei solchen Tabellen<br />

das Sortieren und Filtern in einem Hintergrundthread vornehmen.<br />

95


3.7 Model-View-Controller mit JFace<br />

Gestaltung der Zellenwerte<br />

Möchte der Entwickler das Aussehen der Zellinhalte noch viel freier gestalten, als es<br />

der CellLabelProvider <strong>zu</strong>lässt, dann muss er den StyledCellLabelProvider<br />

einsetzen und das Zeichnen des Zelleninhaltes selbst übernehmen. Eine weitere Möglichkeit<br />

besteht darin, andere Widgets <strong>zu</strong>r Darstellung der Zellinhalte <strong>zu</strong> verwenden. Das<br />

ist etwas komplizierter und soll hier nicht besprochen werden.<br />

3.7.2.3 Eingabe<br />

JFace verwendet intern einen Controller, der Modell und Ansicht steuert. Daher finden<br />

Sie an dieser Stelle auch keinen Abschnitt <strong>zu</strong>m Controller. Statt dessen wird diskutiert,<br />

wie JFace-Tabellen auch Benutzereingaben unterstützen.<br />

Table<br />

TableColumn<br />

SWT<br />

stellt dar<br />

1<br />

stellt dar<br />

1<br />

TableViewer<br />

1 gehört <strong>zu</strong><br />

TableViewerColumn<br />

1<br />

eingeben<br />

<br />

EditingSupport<br />

holt Daten über<br />

1<br />

<br />

IContentProvider<br />

<br />

CellLabelProvider<br />

ColumnLabelProvider<br />

stellt dar<br />

1<br />

MyEditingSupport<br />

verwendet<br />

1<br />

<br />

CellEditor<br />

<br />

IStructuredContent<br />

Provider<br />

MyContentProvider<br />

MyLabelProvider<br />

Darstellung<br />

TextCellEditor<br />

Eingabe<br />

ModelData<br />

Modell<br />

Abbildung 3.95: Eingabe in eine JFace-Tabelle<br />

Die einfachste Art und Weise, einen Editor an<strong>zu</strong>geben, besteht darin, einen vorhandenen<br />

Editor aus JFace <strong>zu</strong> verwenden. Für die wichtigsten Eingabearten sind bereits Editoren<br />

vordefiniert:<br />

TextCellEditor: Er erlaubt die einzeilige Eingabe in ein Textfeld. Mit einer geeigneten<br />

Konvertierung kann hier von Zahlen über normale Bezeichner und Datumswerte<br />

nahe<strong>zu</strong> jeder Datentyp im Modell abgebildet werden.<br />

CheckboxCellEditor: Damit kann der Anwender einen Boole’schen Wert auswählen.<br />

Der Datentyp im Modell ist dabei in der Regel einboolean.<br />

ComboBoxCellEditor: Mit der Combobox lässt sich ein Wert aus einer Menge von<br />

Vorgaben durch ein CCombo-Widget auswählen. Im Gegensatz <strong>zu</strong> einem Textfeld<br />

kann also die Wahlfreiheit eingeschränkt werden.<br />

ColorCellEditor: Der Anwender wählt eine Farbe durch einen Dialog aus. Im<br />

Modell kann so einColor-Objekt manipuliert werden.<br />

DialogCellEditor: Sind komplexere Eingaben erforderlich, dann kann ein eigener<br />

Dialog dem Anwender diese ermöglichen. So lassen sich beispielsweise <strong>zu</strong>sammengesetzte<br />

Modell-Attribute durch mehrere Felder im Dialog erfassen. Die Anzeige<br />

96


3.7 Model-View-Controller mit JFace<br />

des Wertes in der Tabelle übernimmt normalerweise ein Label. Durch Druck auf eine<br />

<strong>zu</strong>sätzliche Taste innerhalb der Zelle wird der Auswahldialog gestartet.<br />

Weitere Editoren können durch Überschreiben von CellEditor selbst implementiert<br />

werden.<br />

Die Arbeit mit einem Editor skizziert die Abbildung 3.96.<br />

viewer<br />

TableViewer<br />

editor<br />

ComboBoxCellEditor<br />

editingSupport<br />

EditingSupport<br />

Eingabe<br />

beendet<br />

1.1: canEdit(object)<br />

1.2: getCellEditor()<br />

1.3: getValue(object)<br />

1.4: doSetValue(value)<br />

2.1: doGetValue()<br />

Wert in den Editor<br />

eintragen<br />

Wert aus dem<br />

Editor auslesen<br />

Darf das Attribut, für<br />

das der Editor registriert<br />

ist, in dem Objekt<br />

object verändert<br />

werden?<br />

CellEditor für den<br />

Wert auslesen.<br />

Attributwert aus dem<br />

Objekt object lesen<br />

2.2: setValue(object, value) In das Attribut des<br />

Objektes object, für<br />

das der Editor registriert,<br />

den neuen Wert value<br />

eintragen<br />

Abbildung 3.96: Interaktion mit einem Editor in einer JFace-Tabelle<br />

Die Klasse EditingSupport ist hier die zentrale Klasse. Sie hat vier wesentliche Aufgaben:<br />

1. Sie teilt der Tabelle mit, ob ein Attribut überhaupt bearbeitet werden kann. Vor der<br />

eigentlichen Interaktion ruft die Tabelle daher die Methode canEdit auf. Nur wenn<br />

eine Bearbeitung <strong>zu</strong>gelassen ist, dann sind die folgenden Punkte relevant.<br />

2. EditingSupport stellt der Tabelle den eigentlichen Editor <strong>zu</strong>r Verfügung. Da<strong>zu</strong><br />

ruft die Tabelle die MethodegetCellEditor auf und übergibt das <strong>zu</strong> bearbeitende<br />

Modellobjekt.<br />

3. Damit die Tabelle den Editor mit dem richtigen Modellwert starten kann, benötigt sie<br />

diesen Wert. Er wird vom EditingSupport bereitgestellt, indem seine Methode<br />

getValue aufgerufen wird.<br />

4. Nach Beendigung der Eingabe durch den Anwender muss der neue Wert wiederum<br />

in das Modellobjekt eingetragen werden. Hier<strong>zu</strong> ruft die Tabelle die Methode<br />

setValue der KlasseEditingSupport auf.<br />

Somit sieht der typische Interaktionsablauf mit demEditingSupport anhand des Diagramms<br />

3.96 so aus:<br />

1.1 Die Tabelle fragt imEditingSupport nach, ob der Anwender den Wert überhaupt<br />

verändern darf.<br />

1.2 Ist die Eingabe <strong>zu</strong>lässig, dann fordert die Tabelle den eigentlichen Editor an, mit<br />

dessen Hilfe der Anwender seine Daten verändern kann.<br />

97


3.7 Model-View-Controller mit JFace<br />

1.3 Anschließend fragt die Tabelle denEditingSupport nach dem <strong>zu</strong> bearbeitenden<br />

Attribut und übergibt da<strong>zu</strong> das komplette Modellobjekt (die Zeile).<br />

1.4 Den erhaltenen Wert trägt die Tabelle in den Editor ein und startet die Bearbeitung<br />

durch den Anwender.<br />

2.1 Ist die Bearbeitung abgeschlossen, dann liest die Tabelle den neuen Wert aus dem<br />

Editor aus.<br />

2.2 Jetzt muss nur noch der neue Wert wieder in das Modellobjekt kommen. Da<strong>zu</strong> verwendet<br />

die Tabelle wiederum den EditingSupport, der den neuen Wert sowie<br />

das Modellobjekt übergeben bekommt und den Wert in das Objekt einträgt.<br />

Mein Lesen der Erklärungen fällt vielleicht auf, dass nirgendwo eine Möglichkeit beschrieben<br />

wurde, wie derEditingSupport herausfinden kann, welche Spalte und damit welches<br />

Attribut bearbeitet werden soll. Das ist ziemlich einfach <strong>zu</strong> beantworten: Die API<br />

sieht die Unterscheidung nicht vor. Im einfachsten Fall wird daher für jede Spalte ein<br />

eigenerEditingSupport geschrieben. Damit ist die Zuordnung wieder eindeutig.<br />

Dieser Abschnitt soll noch mit einem kleinen Beispiel abgeschlossen werden. Zunächst<br />

bekommt die Person aus den vorherigen Beispielen ein <strong>zu</strong>sätzliches Attribut, damit später<br />

auch eine Combobox <strong>zu</strong>r Eingabe verwendet werden kann. Das Attribut nimmt den<br />

Beschäftigungs<strong>zu</strong>stand der Person in Form eines Aufzähltyps auf.<br />

public class Person {<br />

public enum Status { STUDYING, WORKING, RETIRED, DEAD };<br />

private String name;<br />

private int age;<br />

private Status status;<br />

// usw. (Getter, Setter)<br />

}<br />

Abbildung 3.97 zeigt das angestrebte Ergebnis.<br />

Abbildung 3.97: Anzeige und Eingabe in eine JFace-Tabelle<br />

Der ersteEditingSupport handhabt die Eingabe des Namens durch ein Textfeld. Somit<br />

wird als EditorTextCellEditor eingesetzt.<br />

public class PersonNameEditingSupport extends EditingSupport {<br />

// Zur Bearbeitung des Namens wird ein Text-Widget verwendet.<br />

private TextCellEditor cellEditor;<br />

public PersonNameEditingSupport(TableViewer viewer) {<br />

super(viewer);<br />

// Das Vaterelement des Editors ist die Tabelle.<br />

98


3.7 Model-View-Controller mit JFace<br />

}<br />

}<br />

cellEditor = new TextCellEditor(viewer.getTable());<br />

// Der Name kann immer bearbeitet werden.<br />

protected boolean canEdit(Object element) {<br />

return true;<br />

}<br />

// Rückgabe des eigentlichen Editors.<br />

protected CellEditor getCellEditor(Object element) {<br />

return cellEditor;<br />

}<br />

// Das Namensattribut des Objektes auslesen.<br />

protected Object getValue(Object element) {<br />

return ((Person) element).getName();<br />

}<br />

// Den neuen Wert in das Namensattribut eintragen und<br />

// den TableViewer veranlassen, die Zeile mit dieser<br />

// Person neu <strong>zu</strong> zeichnen.<br />

protected void setValue(Object element, Object value) {<br />

((Person) element).setName((String) value);<br />

getViewer().update(element, null);<br />

}<br />

Jetzt muss der Editing-Support noch an der Spalte registrieren, für die er die Steuerung<br />

der Eingaben übernimmt.<br />

tableColumn.setEditingSupport(<br />

new PersonNameEditingSupport(tableViewer));<br />

Der Beschäftigungsstand wird durch ein Label angezeigt. Möchte der Anwenden diesen<br />

ändern, dann erscheint als Editor eine Combobox (KlasseComboBoxCellEditor), die<br />

alle Auswahlmöglichkeiten des Aufzähltyps anbietet. Wichtig ist hier <strong>zu</strong> wissen, dass die<br />

Tabelle als eingegebenen Wert immer den ausgewählten Index der Combobox übergibt.<br />

public class PersonStatusEditingSupport extends EditingSupport {<br />

// Zur Bearbeitung des Status wird intern<br />

// ein CCombo-Widget verwendet.<br />

private ComboBoxCellEditor cellEditor;<br />

public PersonStatusEditingSupport(TableViewer viewer) {<br />

super(viewer);<br />

// Der Benutzer darf keinen Wert manuell in<br />

// die Combobox eingeben. Es ist lediglich eine<br />

// Auswahl aus den vorhandenen Werten möglich.<br />

cellEditor = new ComboBoxCellEditor(viewer.getTable(),<br />

person.getStatusValuesAsStringArray(),<br />

SWT.READ_ONLY);<br />

}<br />

// Auch der Status kann immer bearbeitet werden.<br />

protected boolean canEdit(Object element) {<br />

return true;<br />

}<br />

// Rückgabe des eigentlichen Editors.<br />

protected CellEditor getCellEditor(Object element) {<br />

99


3.8 Weitere Container<br />

}<br />

return cellEditor;<br />

// Den Wert des Statusattributes auslesen und<br />

// als Index innerhalb der Combobox <strong>zu</strong>rueck geben.<br />

protected Object getValue(Object element) {<br />

return ((Person) element).getStatus().ordinal();<br />

}<br />

}<br />

// Den neuen Wert in das Statusattribut eintragen und<br />

// den TableViewer veranlassen, die Zeile mit dieser<br />

// Person neu <strong>zu</strong> zeichnen. Die Methode setStatus<br />

// der Personenklasse nimmt die Konvertierung<br />

// des Index aus der Combobox in den Aufzählwert vor.<br />

protected void setValue(Object element, Object value) {<br />

((Person) element).setStatus((Integer) value);<br />

getViewer().update(element, null);<br />

}<br />

Der EditingSupport muss noch an der Spalte registriert werden. Das vollständige<br />

Beispiel, das auch die Eingabe des Altersattributes unterstützt, ist in den beiden Paketen<br />

tableViewer.person.editing undtableViewer.person.common des Quelltextes<br />

<strong>zu</strong>r Vorlesung <strong>zu</strong> finden.<br />

3.7.3 Attributierter Text ohne MVC<br />

Dieses Kapitel folgt, wenn alle anderen, die für die Vorlesung relevant sind, fertiggestellt<br />

sind.<br />

3.7.4 Attributierter Text mit MVC<br />

Dieses Kapitel folgt, wenn alle anderen, die für die Vorlesung relevant sind, fertiggestellt<br />

sind.<br />

3.8 Weitere Container<br />

100


Anbindung an die Geschäftslogik<br />

4


Deklarative Beschreibungen<br />

5


Eclipse Rich Client Platform<br />

6


7<br />

Multithreading in Java<br />

Java besitzt eine sehr mächtige und umfangreiche API <strong>zu</strong>r Erstellung multithreadingfähiger<br />

Programme. In Java 5 wurde diese API nochmals erweitert. In den folgenden<br />

Abschnitten werden nur die wichtigsten Grundfunktionalitäten vorgestellt. Die Pakete<br />

java.util.concurrent und deren Unterpakete werden hier aus Platzgründen ebenso<br />

wenig betrachtet wie viele andere Eigenschaften von Threads.<br />

7.1 Threads erzeugen und starten<br />

Ein Thread ist ein Objekt der KlasseThread. Im Wesentlichen wird ein Thread auf zwei<br />

verschiedene Arten erzeugt:<br />

1. Eine Klasse erbt vonThread und überschreibt die Methode run. Diese implementiert<br />

die (Endlos-)Schleife des Threads und wird beim Start aufgerufen.<br />

2. Eine Klasse implementiert die SchnittstelleRunnable und überschreibt ebenso die<br />

Methoderun. Ein Objekt der Klasse wird dem Konstruktor vonThread übergeben.<br />

Beispiel:<br />

public class MyThread extends Thread {<br />

public void run() {<br />

while (true) {<br />

// Implementierung<br />

}<br />

}<br />

}<br />

Der Thread wird schließlich mitstart gestartet.<br />

Beispiel:<br />

MyThread mThr = new MyThread();<br />

mThr.start();<br />

Die Thread-Priorität liegt zwischen MIN_PRIORITY (0) und MAX_PRIORITY (10). Im<br />

Standardfall beträgt sieNORM_PRIORITY (5).


7.2 Threads beenden und löschen<br />

7.2 Threads beenden und löschen<br />

Die einzige in aktuellen Java-Versionen unterstützte Möglichkeit, einen Thread <strong>zu</strong> beenden,<br />

besteht darin, dass der Thread seine run-Methode selbst verlässt. Ein Problem tritt<br />

auf, wenn der Thread durch einen anderen terminiert werden soll. Als Ausweg kann einem<br />

Thread ein Unterbrechungssignal gesendet werden. Der ausführende Thread testet<br />

zyklisch auf das Vorhandensein des Signals und beendet sich selbst.<br />

Beispiel:<br />

public class MyThread extends Thread {<br />

public void run() {<br />

while (true) {<br />

if (interrrupted()) {<br />

return; // beenden<br />

}<br />

// ...<br />

}<br />

}<br />

}<br />

Dem Thread wird mitinterrupt das Signal <strong>zu</strong>r Unterbrechung geschickt.<br />

Beispiel:<br />

MyThread mThr = new MyThread();<br />

mThr.start();<br />

// ...<br />

mThr.interrupt();<br />

Ein Thread läuft häufig nicht permanent sondern legt sich für eine gewisse Zeit „schlafen“.<br />

Aus diesem Schlaf<strong>zu</strong>stand wird er ebenso mitinterrupt geweckt. Um zwischen einem<br />

„normalen“ Aufwecken aus dem Schlaf<strong>zu</strong>stand und einem Signal <strong>zu</strong>r Beendigung der<br />

Arbeit unterscheiden <strong>zu</strong> können, lässt sich der Thread sehr einfach durch Hin<strong>zu</strong>fügen<br />

einer Boole’schen Variablen erweitern.<br />

Beispiel (Thread):<br />

public class MyThread extends Thread {<br />

private boolean terminated = false;<br />

public void run() {<br />

while (!terminated) {<br />

// Aufwecken aus dem Schlaf<strong>zu</strong>stand<br />

try {<br />

sleep(2000);<br />

}<br />

catch (InterruptedException ex) {<br />

if (!terminated) {<br />

// Arbeit nach dem Wecken<br />

}<br />

}<br />

// Arbeiten<br />

// Test auf Aufwecken während der Arbeit<br />

if (interrupted() && terminated) {<br />

return;<br />

}<br />

// Arbeiten<br />

}<br />

}<br />

105


7.3 Zugriff auf gemeinsam genutzte Ressourcen<br />

}<br />

public void terminate() {<br />

terminated = true;<br />

interrupt();<br />

}<br />

Der Aufruf derterminate-Methode teilt dem Thread mit, dass er sich beenden soll. Dabei<br />

ist <strong>zu</strong> beachten, dass ein aktiver Thread unter Umständen erst nach einer gewissen<br />

Zeit das Stopsignal sieht. Es ist daher sinnvoll, die Variable terminated in regelmäßigen<br />

Abständen <strong>zu</strong> überprüfen.<br />

7.3 Zugriff auf gemeinsam genutzte Ressourcen<br />

In Java dürfen verschiedene Thread nur unter sehr genau definierten Bedingungen gemeinsam<br />

auf Variablen <strong>zu</strong>greifen. Sicher ist immer der synchronisierte Fall, wie er in<br />

Abschnitt 7.4 vorgestellt wird. Allerdings kostet die Synchronisation sehr viel Zeit. Glücklicherweise<br />

gibt es bestimmte Szenarien, in denen sie nicht erforderlich ist. Beispielsweise<br />

ist der Zugriff auf Refenrenzen sowie alle primitiven Datentypen außer long und<br />

double atomar. Das heißt, dass während eines solchen Zugriffs kein Kontextwechsel<br />

erfolgt. Damit könnte die Diskussion eigentlich beendet sein. Problem treten aber besonders<br />

auf Prozessoren mit mehreren Kernen auf. Es zwar garantiert, dass die Zugriffe<br />

unteilbar sind. Es ist aber nicht garantiert, dass ein Thread, der nach dem Schreiben<br />

lesend auf die Variable <strong>zu</strong>greift, die Änderung überhaupt sehen wird. Der Quelltext unterstellt<br />

die sogenannte „Sequential Consistency“. Damit ist gemeint, dass ein Thread die<br />

Änderungen eines anderen Threads, die dieser vorher vorgenommen hat, sehen kann.<br />

Solch ein Modell wird von der virtuellen Maschine aber gar nicht unterstützt. Das hängt<br />

damit <strong>zu</strong>sammen, dass jeder Thread seinen eigenen Speicher besitzt. Dieser kann sich<br />

im Falle von Mehrkern-Prozessoren durchaus auch auf verschiedenen Prozessorkernen<br />

befinden. Aus Geschwindigkeitsgründen werden diese separaten Speicher nicht nach jedem<br />

Zugriff untereinander abgeglichen. Der lokale Speicher wird nur dann automatisch<br />

mit dem Hauptspeicher synchronisert, wenn sich der Thread beendet. Das ist in der Regel<br />

aber viel <strong>zu</strong> spät. Für ein manuelles Zurückschreiben der Änderungen reicht es aus,<br />

eine gemeinsam genutzte Variable alsvolatile <strong>zu</strong> kennzeichnen.<br />

volatile int sharedMemory = 42;<br />

Beschreibt ein Thread solch eine Variable, dann wird sein gesamter Zustand inklusive<br />

der alsvolatile deklarierten Variablen in den Hauptspeicher <strong>zu</strong>rück geschrieben. Liest<br />

ein Thread dagegen solch eine Variable, wird <strong>zu</strong>nächst der Inhalt der Variablen aus dem<br />

Hautpspeicher erneut gelesen, weil er sich inzwischen ja geändert haben kann. Dabei<br />

wird aber nicht nur der Inhalt dieser einen Variable gelesen, sondern es wird der gesamte<br />

Arbeitsspeicher des Threads aufgefrischt. Der Aufwand beim Zugriff auf volatile-<br />

Variablen ist also nicht <strong>zu</strong>vernachlässigen. Es ist aber deutlich geringer als bei manueller<br />

Synchronisation (siehe Abschnitt 7.4).<br />

Statt Variablen, die als volatile deklariert werden, können seit Java 5 auch Klassen<br />

aus dem Paket java.util.concurrent.atomic verwendet werden. Diese kapseln<br />

primitive Daten mit thread-sicherem Zugriff und bieten darüber hinaus auch weitere Operationen<br />

auf den Daten an (z.B.getAndIncrement).<br />

106


7.4 Synchronisation von Threads<br />

Bei Konstanten ist der Fall einfacher: Bei ihrer Initialisierung werden die Daten im Hauptspeicher<br />

aktualisiert, und alle Thread können dann problemlos darauf <strong>zu</strong>greifen, weil der<br />

erste Zugriff ein Auffrischen des thread-eigenen Speichers bewirkt.<br />

7.4 Synchronisation von Threads<br />

Zur einfachen Synchronisation von Threads existieren in der Basisklasse aller Klassen<br />

Object mehrere Methoden. Das Objekt, auf dem die Methoden aufgerufen werden,<br />

dient dabei als Monitor. Alle folgenden Methoden sollten nur innerhalb synchronisierter<br />

Blöcke oder Methoden, die den Monitor als Synchronisationsobjekt verwenden, aufgerufen<br />

werden. Synchronisierte Methoden und Blöcke werden in den Folgeabschnitten<br />

beschrieben. Wird die Synchronisation verwendet, dann ist sichergestellt, dass die beteiligten<br />

Threads auch auf Mehrkern-Prozessoren immer mit den richtigen Speicherwerten<br />

arbeiten, weil jede manuelle Synchronisation automatisch ein Rückschreiben und eine<br />

Auffrischung der Thread-Daten erzwingt.<br />

wait: Beim Aufruf der Methode wird der gerade aktive Thread an diesem Synchronisationsobjekt<br />

blockiert. Optional ist die Angabe einer maximalen Wartezeit. Da<br />

diese Methode nur innerhalb eines synchronisierten Abschnitts (Block oder Methode)<br />

aufgerufen werden soll, wird beim Blockieren der Abschnitt wieder freigegeben.<br />

Sobald der Thread deblockiert wird, wird er wieder Eigentümer des Abschnitts und<br />

blockiert ihn somit. wait löst eine InterruptedException aus, wenn der wartende<br />

Thread ein Unterbrechungssignal während seines Wartens empfangen hat.<br />

notify: Ein am Monitor wartender Thread wird deblockiert. Im Falle mehrerer an<br />

diesem Monitor wartender Threads hängt es von der Implementierung der virtuellen<br />

Maschine ab, welcher Thread deblockiert wird.<br />

notifyAll: Alle am Monitor wartenden Threads werden wieder freigegeben. Die<br />

Threads bekommen der Reihe nach den synchronisierten Abschnitt <strong>zu</strong>gewiesen, so<br />

dass sichergestellt ist, dass sich immer nur ein Thread in dem kritischen Abschnitt<br />

befindet.<br />

In Java 5 wurden weitere Klassen <strong>zu</strong>r Synchronisation eingeführt. Diese befinden sich<br />

im Paketjava.util.concurrent und dessen Unterpaketen.<br />

7.4.1 Synchronisierte Methoden<br />

Sie stellen sicher, dass nur ein Thread <strong>zu</strong> einem Zeitpunkt die Methode betreten kann.<br />

Beispiel für synchronisierte Methoden:<br />

public class Timer {<br />

// ...<br />

public synchronized void set(long time) {<br />

/* ... */<br />

}<br />

public synchronized void set(Date date) {<br />

/* ... */<br />

}<br />

}<br />

107


7.5 Threads als Daemons<br />

Befinden sich mehrere Methoden mit dem Merkmal synchronized in einer Klasse, so<br />

ist sichergestellt, dass immer nur eine dieser Methoden <strong>zu</strong> einem Zeitpunkt aufgerufen<br />

werden kann. Die Methode verwendet das Objekt, auf dem sie aufgerufen werden, als<br />

Monitor.<br />

Dieset-Methode aus dem vorherigen Beispiel entspricht somit:<br />

public void set(long time){<br />

synchronized (this) {<br />

/* ... */<br />

}<br />

Solche synchronisierten Blöcke sind im Folgeabschnitt beschrieben.<br />

Wird eine statische Methode alssynchronized markiert, so wird als Monitor das Klassenobjekt<br />

der Klasse verwendet (Klassejava.lang.Class). So ist sichergestellt, dass<br />

nur eine synchronisierte, statische Methode der Klasse <strong>zu</strong> einem Zeitpunkt ausgeführt<br />

werden kann,<br />

7.4.2 Synchronisierte Blöcke<br />

Es handelt sich um Monitore, die einen gegenseitigen Ausschluss mehrerer Threads in<br />

diesem Block bewirken. Die Synchronisation findet immer an einem Objekt statt, wobei<br />

das Objekt als der Monitor agiert.<br />

Beispiel, in demobject eine Referenz auf das Monitorobjekt ist:<br />

synchronized (object) {<br />

// Kritischer Abschnitt<br />

}<br />

7.5 Threads als Daemons<br />

Ein Thread wird durch den Aufruf der Methode setDaemon als so genannter Daemon<br />

(„Dämon“) markiert. Dieses hat eine Auswirkung auf die Lebensdauer der Anwendung.<br />

Normalerweise beendet die JVM (Java Virtual Machine) das laufende Programm erst<br />

dann automatisch, wenn alle Threads beendet wurden. Die Ausnahme sind die Daemons,<br />

die hier<strong>zu</strong> nicht berücksichtigt werden. So bietet es sich an, gewisse Teilaufgaben,<br />

die als Hintergrundthreads implementiert sind, als Daemons <strong>zu</strong> markieren. Sie haben<br />

keinen Einfluss auf die Lebensdauer des Programmes und müssen daher auch nicht<br />

manuell beendet werden. Im Zusammenhang mit Swing stellt sich die Frage der Beendigung<br />

häufig nicht, da der Event-Thread ohnehin nicht als Daemon läuft und so eine<br />

direkte Beendigung der Anwendung durch das Verlassen dermain-Methode verhindert.<br />

108


8<br />

Abbildungsverzeichnis<br />

2.1 Kommunikation bei einem Thin-Client . . . . . . . . . . . . . . . . . . . 7<br />

2.2 Kommunikation bei einem Rich Thin Client . . . . . . . . . . . . . . . . . 8<br />

2.3 Kommunikation bei einem Rich Fat Client . . . . . . . . . . . . . . . . . 9<br />

2.4 Architektur einer Fat-Client-Anwendung . . . . . . . . . . . . . . . . . . 11<br />

2.5 Architekturschichten anhand eines Beispiels . . . . . . . . . . . . . . . . 12<br />

3.1 Struktur der Oberfläche . . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />

3.2 Beispiel unter Windows 7, MacOS X und Linux (GTK) . . . . . . . . . . 16<br />

3.3 Struktur einer SWT-Anwendung . . . . . . . . . . . . . . . . . . . . . . . 17<br />

3.4 Zielplattform einstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

3.5 Zielplattform einrichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

3.6 Abhängigkeit von Dateien der Zielplattform . . . . . . . . . . . . . . . . 20<br />

3.7 Struktur einer JFace-Anwendung . . . . . . . . . . . . . . . . . . . . . . 21<br />

3.8 Windows 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

3.9 LinuX, GNOME 2.28.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

3.10 Windows XP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

3.11 MacOS X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

3.12 Englische Texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

3.13 Deutsche Texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

3.14 Vergrößerter Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

3.15 Verkleinerter Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

3.16 HierarchieButton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

3.17 HierarchieLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

3.18 Anordnung im Fill-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

3.19 Horizontale Anordnung im Fill-Layout . . . . . . . . . . . . . . . . . . . . 28<br />

3.20 Manuelle Verkleinerung im Fill-Layout . . . . . . . . . . . . . . . . . . . 28<br />

3.21 Vertikale Anordnung im Fill-Layout . . . . . . . . . . . . . . . . . . . . . 28<br />

3.22 Anordnung im Row-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

3.23 Horizontale Anordnung im Row-Layout . . . . . . . . . . . . . . . . . . . 29<br />

3.24 Manueller Verkleinerung im Row-Layout (ohne Umbruch) . . . . . . . . 30<br />

3.25 Manueller Verkleinerung im Row-Layout (mit Umbruch) . . . . . . . . . . 30<br />

3.26 Vertikale Anordnung im Row-Layout . . . . . . . . . . . . . . . . . . . . 30<br />

3.27 Row-Layout mit veränderter Widget-Breite . . . . . . . . . . . . . . . . . 32


Abbildungsverzeichnis<br />

3.28 Anordnung im Grid-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

3.29 Beispieldialog für das Grid-Layout . . . . . . . . . . . . . . . . . . . . . 35<br />

3.30 Rasterhilfslinien auf dem Dialog . . . . . . . . . . . . . . . . . . . . . . . 36<br />

3.31 Platzierung des Labels im Grid-Layout . . . . . . . . . . . . . . . . . . . 36<br />

3.32 Platzierung des Textfeldes im Grid-Layout . . . . . . . . . . . . . . . . . 37<br />

3.33 Platzierung einer Taste im Grid-Layout . . . . . . . . . . . . . . . . . . . 37<br />

3.34 Platzierung einer Taste im Grid-Layout ohne Streckung . . . . . . . . . . 38<br />

3.35 Verschachtelung von Containern mit jeweils eigenen Layouts . . . . . . 38<br />

3.36 Beispiel für ineinander geschachtelte Layouts . . . . . . . . . . . . . . . 38<br />

3.37 Größenänderungen bei ineinander geschachtelten Layouts . . . . . . . 39<br />

3.38 Anordnung im Form-Layout . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />

3.39 Beispiel 1 für die Anordnung im Form-Layout . . . . . . . . . . . . . . . 42<br />

3.40 Beispiel 2 für die Anordnung im Form-Layout . . . . . . . . . . . . . . . 42<br />

3.41 Beispiel 3 für die Anordnung im Form-Layout . . . . . . . . . . . . . . . 43<br />

3.42 Automatische Randerkennung im Form-Layout . . . . . . . . . . . . . . 43<br />

3.43 Keine automatische Randerkennung im Form-Layout . . . . . . . . . . . 43<br />

3.44 Screenshot des Beispiels . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

3.45 Einfügen des Titels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

3.46 Einfügen der Abbruch-Taste . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3.47 Einfügen der Ok-Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3.48 Einfügen des Bildes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />

3.49 Einfügen des Textfeldes . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />

3.50 Dialog mit Form-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

3.51 Einfügen des Titels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.52 Einfügen des Textfeldes . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.53 Einfügen der Ok-Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.54 Einige Widgets aus dem Nebula-Projekt . . . . . . . . . . . . . . . . . . 53<br />

3.55 Einige Widgets aus der RCPToolbox . . . . . . . . . . . . . . . . . . . . 54<br />

3.56 Komponenten in einem Menü . . . . . . . . . . . . . . . . . . . . . . . . 54<br />

3.57 Radio-Tasten in einem Menü . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

3.58 Ausgabe des ersten Menü-Beispiels . . . . . . . . . . . . . . . . . . . . 56<br />

3.59 Hin<strong>zu</strong>fügen der Checkbox <strong>zu</strong>m Menü . . . . . . . . . . . . . . . . . . . . 57<br />

3.60 Hin<strong>zu</strong>fügen der Radio-Taste <strong>zu</strong>m Menü . . . . . . . . . . . . . . . . . . 57<br />

3.61 Begriffe <strong>zu</strong>r Tastatursteuerung in Menüs . . . . . . . . . . . . . . . . . . 58<br />

3.62 Popup-Menü auf verschiedenen Plattformen . . . . . . . . . . . . . . . . 59<br />

3.63 Menü in einem System-Tray auf verschiedenen Plattformen . . . . . . . 60<br />

3.64 Toolbar auf verschiedenen Plattformen . . . . . . . . . . . . . . . . . . . 61<br />

3.65 Elemente einer Coolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . 62<br />

3.66 Beispiel einer Coolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

3.67 Vereinfachte Klassenhierachie . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

3.68 Relative Positionierung von Controls . . . . . . . . . . . . . . . . . . . . 65<br />

3.69 Hintergrundfarbe eines Controls . . . . . . . . . . . . . . . . . . . . . . . 65<br />

3.70 Tooltip an einem Control . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

3.71 Gesperrtes Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

3.72 Allgemeines Bobachter-Muster . . . . . . . . . . . . . . . . . . . . . . . 68<br />

3.73 Ablauf im Bobachter-Muster . . . . . . . . . . . . . . . . . . . . . . . . . 69<br />

3.74 Ereignis<strong>zu</strong>stellung unter SWT . . . . . . . . . . . . . . . . . . . . . . . . 69<br />

110


Abbildungsverzeichnis<br />

3.75 Aktion für Menüeintrag und Toolbar-Taste . . . . . . . . . . . . . . . . . 75<br />

3.76 Darstellungen aus einer Aktion . . . . . . . . . . . . . . . . . . . . . . . 75<br />

3.77 Ereignis<strong>zu</strong>stellung unter JFace . . . . . . . . . . . . . . . . . . . . . . . 75<br />

3.78 Aktion <strong>zu</strong>r Dokumenterzeugung . . . . . . . . . . . . . . . . . . . . . . . 76<br />

3.79 Menü aus Aktionen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . 78<br />

3.80 Coolbar aus Aktionen erzeugen . . . . . . . . . . . . . . . . . . . . . . . 78<br />

3.81 Ergebnis auf einigen Fenstersystemen . . . . . . . . . . . . . . . . . . . 79<br />

3.82 Struktur im MVC-Ansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />

3.83 Ablauf im MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />

3.84 Wichtige Klassen für eine Tabelle . . . . . . . . . . . . . . . . . . . . . . 81<br />

3.85 Wichtige Klassen für eine JFace-Tabelle . . . . . . . . . . . . . . . . . . 85<br />

3.86 Modellklassen für die JFace-Tabelle . . . . . . . . . . . . . . . . . . . . 86<br />

3.87 JFace-Tabelle für eine Liste von Personen . . . . . . . . . . . . . . . . . 87<br />

3.88 Interaktion zwischen JFace-Tabelle und Modell . . . . . . . . . . . . . . 87<br />

3.89 Ausgabe der JFace-Tabelle (noch unvollständig) . . . . . . . . . . . . . 89<br />

3.90 Ansichtsklassen für die JFace-Tabelle . . . . . . . . . . . . . . . . . . . 90<br />

3.91 Ausgabe der JFace-Tabelle mit Formatierer . . . . . . . . . . . . . . . . 91<br />

3.92 Fehlende dynamische Spaltenanpassung bei einer Tabelle . . . . . . . 92<br />

3.93 Korrekte dynamische Spaltenanpassung bei einer Tabelle . . . . . . . . 93<br />

3.94 Sortierung einer Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 93<br />

3.95 Eingabe in eine JFace-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . 96<br />

3.96 Interaktion mit einem Editor in einer JFace-Tabelle . . . . . . . . . . . . 97<br />

3.97 Anzeige und Eingabe in eine JFace-Tabelle . . . . . . . . . . . . . . . . 98<br />

111


9<br />

Literaturverzeichnis<br />

[Dau07] Daum B.: Rich-Client-Entwicklung mit Eclipse 3.3, dpunkt-Verlag, 2007<br />

[ISO3166]<br />

[ISO639]<br />

[JavaBsp]<br />

[JFaceBsp]<br />

[Layouts]<br />

ISO-Norm 3166 (Ländercodes),<br />

http://www.iso.org/iso/country_codes/<br />

iso_3166_code_lists/<br />

english_country_names_and_code_elements.htm<br />

ISO-Norm 631 (Sprachcodes),<br />

http://www.loc.gov/standards/iso639-2/php/<br />

code_list.php<br />

Java-Quelltextbeispiele (alles Mögliche):http://www.java2s.com<br />

Code-Beispiele <strong>zu</strong> JFace<br />

http://wiki.eclipse.org/index.php/JFaceSnippets<br />

http://www.eclipse.org/articles/article.php?<br />

file=Article-Understanding-Layouts/index.html<br />

[Mar06] Marinilli M.: Professional Java User Interfaces, Wiley & Sons, 2006<br />

[McA10]<br />

McAffer J., Lemieux J. M.: Eclipse Rich Client Platform, Addison-Wesley<br />

Longman, 2010<br />

[RCPOnline] Online-Version der ersten Seiten eines neuen RCP-Bucheshttp://www.<br />

ralfebert.de/rcpbuch/<br />

[Sca05] Scarpino M, et.al.: SWT/JFace in Action, Manning Publications Co., 2005<br />

[SWT]<br />

[SWTBsp]<br />

[SurrPa]<br />

[UIGuide]<br />

Tutorials und Artikel <strong>zu</strong> SWThttp://www.eclipse.org/swt/<br />

Code-Beispiele <strong>zu</strong> SWThttp://www.eclipse.org/swt/snippets/<br />

Behandlung von Zeichenketten bei Internationalisierung<br />

http://java.sun.com/mailers/techtips/corejava/2006/<br />

tt0822.html<br />

Design-Richtlinien für grafische Oberflächen mit SWT und JFace<br />

http://www.eclipse.org/articles/Article-UI-Guidelines/<br />

Index.html<br />

[War07] Warner R., Harris R.: The Definite Guide to SWT and JFace, Apress, 2007


Abbildungsverzeichnis<br />

[Wue08]<br />

Wütherich G., Hartmann N., Kolb B., Lübken M.: Die OSGi Service Platform,<br />

dpunkt-Verlag, 2008<br />

113


10<br />

Stichwortverzeichnis<br />

Symbole<br />

.NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9<br />

Action . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74, 76<br />

run . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />

ApplicationWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />

Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

addSelectionListener .. . . . . . . . . . . . . . . . . . 71<br />

CCombo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51, 96<br />

CLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

Canvas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

CellConstraints .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48<br />

CellEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97<br />

CellLabelProvider . . . . . . . . . . . . . . . . . . . . . . . . 90, 96<br />

CheckboxCellEditor .. . . . . . . . . . . . . . . . . . . . . . . . . . 96<br />

ColorCellEditor .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96<br />

ColumnPixelData .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

ColumnWeightData.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .92<br />

ComboBoxCellEditor ....................... 96, 99<br />

ComboViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

Combo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51<br />

Composite. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .38, 53, 67<br />

ControlEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64<br />

addSelectionListener .. . . . . . . . . . . . . . . . . . 71<br />

setBackgroundImage .. . . . . . . . . . . . . . . . . . . . . 65<br />

setBackground.. . . . . . . . . . . . . . . . . . . . . . . . . . . .65<br />

setEnabled .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

setFont . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

setForeground.. . . . . . . . . . . . . . . . . . . . . . . . . . . .65<br />

setLayoutData.. . . . . . . . . . . . . . . . . . . . . . . . . . . .66<br />

setMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

setToolTipText . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

setVisible .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

Ereignis .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

CoolBarManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78<br />

CoolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />

CoolItem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61<br />

Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51, 52<br />

DeferredContentProvider .. . . . . . . . . . . . . . . . . . . 95<br />

DialogCellEditor.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .96<br />

DragDetectEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

EditingSupport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97<br />

Event<br />

index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .84<br />

FillLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

FocusEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

FormAttachment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />

FormData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40<br />

FormLayout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39, 47<br />

GridDataFactory .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

GridData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34<br />

GridLayoutFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

GridLayout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

Group.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52<br />

HelpEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

ILazyContentProvider .. . . . . . . . . . . . . . . . . . . . . . . 95<br />

IStructuredContentProvider .. . . . . . . . . . . 87, 95<br />

KeyEvent.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .66<br />

Label.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52<br />

Link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

ListViewer .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52, 85<br />

MenuDetectEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

MenuItem.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .55<br />

MenuManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77<br />

Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

MouseEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

MouseMoveEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

Object<br />

notifyAll .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

notify .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

wait. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

PaintEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

PanelBuilder .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48<br />

ProgressBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

RowData .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

RowLayoutFactory.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .30<br />

RowLayout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

Scale.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52<br />

ScrollBar .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

Scrollable .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

getHorizontalBar.. . . . . . . . . . . . . . . . . . . . . . . .67<br />

getVertikalBar . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

SelectionAdapter.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .71<br />

SelectionEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70<br />

SelectionListener . . . . . . . . . . . . . . . . . . . . . . . . 70, 73<br />

widgetDefaultSelected .. . . . . . . . . . . . . . . . .71<br />

widgetSelected.. . . . . . . . . . . . . . . . . . . . . . .71, 72<br />

ShellAdapter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />

ShellEvent .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />

doIt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />

ShellListener.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .73<br />

shellActivated . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />

shellClosed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />

shellDeactivated.. . . . . . . . . . . . . . . . . . . . . . . .74<br />

shellDeiconified.. . . . . . . . . . . . . . . . . . . . . . . .74<br />

shellIconified . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />

Shell.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .76<br />

Slider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

Spinner .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

StyledCellLabelProvider .. . . . . . . . . . . . . . . . . . . 96<br />

StyledText .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

TableColumnLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

ColumnPixelData .. . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

ColumnWeightData.. . . . . . . . . . . . . . . . . . . . . . . .92


Stichwortverzeichnis<br />

TableColumn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81<br />

setRedraw .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />

setText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />

setWidth. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .84<br />

TableEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81<br />

TableItem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81, 82<br />

setText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />

TableViewerColumn . . . . . . . . . . . . . . . . . . . . . . . . 89, 91<br />

TableViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85, 88, 91<br />

Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 81, 84, 85<br />

virtuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84<br />

TextCellEditor.. . . . . . . . . . . . . . . . . . . . . . . . . . . .96, 98<br />

Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

run . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

ToolBarManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78<br />

ToolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />

TraverseEvent.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .66<br />

TrayItem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .59<br />

Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

TreeViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 85<br />

TypedEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72<br />

widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72<br />

ViewerFilter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95<br />

ViewerSorter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93<br />

Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64<br />

Ereignis .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />

synchronized .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />

volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106<br />

A<br />

Accelerator .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55, 57<br />

Anwendungskern .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

B<br />

Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53<br />

Benut<strong>zu</strong>ngsschnittstelle.. . . . . . . . . . . . . . . . . . . . . . . . . . .11<br />

Beobachter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79<br />

Beobachter-Muster .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

Betriebssystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

Block<br />

synchronisierter .. . . . . . . . . . . . . . . . . . . . . . . 107, 108<br />

Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

C<br />

C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9, 10<br />

C#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10<br />

Control ............................................ 64<br />

Aktivierung ................................... 65<br />

Farben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

Größe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64<br />

Position . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64<br />

Tooltip ........................................65<br />

Controller .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80<br />

Coolbar.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61<br />

D<br />

Datumseingabe.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51<br />

Dialogsteuerung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11, 12<br />

E<br />

Eclipse RCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

Ereignis<br />

SelectionAdapter.. . . . . . . . . . . . . . . . . . . . . . . .73<br />

SelectionEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . 70<br />

Beobachter-Muster .. . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

Fenster .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />

Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

Ereignisbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

F<br />

Fat Client<br />

plattformabhängig.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .9<br />

plattformunabhängig .. . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

Fenstersystem.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10<br />

Fill-Layout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

merginHeight .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

merginWidth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

spacing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

Attribute .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

Form-Layout.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .39<br />

FormAttachment . . . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />

FormData.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40<br />

Form-Layout (JGoodies) .. . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

CellConstraints .. . . . . . . . . . . . . . . . . . . . . . . . . 48<br />

PanelBuilder .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48<br />

Dialog Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

Eigenschaften .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

G<br />

Geschäftslogik.....................................11<br />

Geschachtelte Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

Grid-Layout.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32<br />

GridDataFactory .. . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

GridData.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34<br />

GridLayoutFactory .. . . . . . . . . . . . . . . . . . . . . . 34<br />

H<br />

Hardware.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10<br />

I<br />

IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

J<br />

Java Development Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

Java ME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10<br />

Java Runtime Environment.. . . . . . . . . . . . . . . . . . . . . . . . .5<br />

Java Web Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

JDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

JFace<br />

Tabellen-Modell.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86<br />

JRE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

K<br />

Kalender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

Klassifikation.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9, 12<br />

L<br />

Layout<br />

absolutes.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25<br />

Fill-.. . . . . . . . . . . . . . . . . . . . . . . . .siehe Fill-Layout 27<br />

Form- . . . . . . . . . . . . . . . . . . . . siehe Form-Layout 39<br />

Form- (JGoodies) siehe Form-Layout (JGoodies)<br />

47<br />

Größenänderungen.. . . . . . . . . . . . . . . . . . . . . . . . . .26<br />

Grid- .. . . . . . . . . . . . . . . . . . . . . siehe Grid-Layout 32<br />

Internationalisierung .. . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

Komponentengröße . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

Management .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

Row- .. . . . . . . . . . . . . . . . . . . . . siehe Row-Layout 29<br />

Layout-Manager .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />

Layouts<br />

Geschachtelte . . siehe Geschachtelte Layouts 38<br />

Listenauswahl .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

115


Stichwortverzeichnis<br />

M<br />

Menü . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54<br />

Accelerator .. . . . . . . . . . . siehe Accelerator 55, 58<br />

Bilder.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .57<br />

Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56<br />

Eintrag .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

Mnemonic . . . . . . . . . . . . . . siehe Mnemonic 55, 58<br />

Popup- .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

Radio-Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

System-Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

Tastatursteuerung .. . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

Trennstrich.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .55, 57<br />

Methode<br />

synchronized .. . . . . . . . . . . . . . . . . . . . . . . . . . . .108<br />

synchronisierte .. . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

Mnemonic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55, 57<br />

Modell .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80<br />

Monitor.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

freigeben.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

warten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

Wartezeit.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

Multithreading .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

MVC<br />

Controller .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79, 80<br />

Modell .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80<br />

View.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79<br />

N<br />

NetBeans Platform.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8<br />

O<br />

Objekt<br />

Monitor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108<br />

Synchronisation .. . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />

P<br />

Passworteingabe .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

Polling .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .67<br />

Popup-Menü.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .59<br />

Q<br />

QT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9, 14<br />

R<br />

Rich Client.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8<br />

Rich Fat Client .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

Rich Thin Client.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8<br />

Row-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

RowData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

Attribute .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />

Runnable.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .104<br />

S<br />

Schieberegler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

Sequential Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . 106<br />

Smart Client .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9, 14<br />

SWT. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9<br />

Symbian OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

Synchronisationsobjekt .. . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

Synchronisierter Abschnitt.. . . . . . . . . . . . . . . . . . . . . . .107<br />

System-Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

T<br />

Tabelle.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53, 81<br />

Ansicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89<br />

Eingabe .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96<br />

Filterung.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95<br />

JFace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

Modell .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86<br />

Sortierung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93<br />

View..........................................89<br />

virtuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84<br />

Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51<br />

Texteingabefeld<br />

einzeiliges .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />

Thin Client .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

Thread .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104, 106<br />

MAX_PRIORITY .. . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

NORM_PRIORITY . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

PN_PRIORITY .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

atomare Zugriffe.. . . . . . . . . . . . . . . . . . . . . . . . . . . .106<br />

beenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

blockieren .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

Daemon.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108<br />

erzeugen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .104<br />

freigeben.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107<br />

gegenseitiger Ausschluss . . . . . . . . . . . . . . . . . . . 108<br />

gemeinsame Ressourcen . . . . . . . . . . . . . . . . . . . 106<br />

Priorität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

starten .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

synchronisieren .. . . . . . . . . . . . . . . . . . . . . . . . . . . . 107<br />

unterbrechen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

Toolbar .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />

V<br />

View.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79<br />

W<br />

Widget .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11, 16, 64<br />

Windows CE/Mobile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

Windows-Forms .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />

X<br />

XAML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9<br />

Z<br />

Zeiteingabe.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52<br />

116

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!