Skript zu Benutzungsoberflächen
Skript zu Benutzungsoberflächen
Skript zu Benutzungsoberflächen
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