Skript zu Benutzungsoberflächen

Transcrição

Skript zu Benutzungsoberflächen
Benutzungsoberflächen
Skript V0.3 vom 11.10.2010
Prof. Dr.-Ing. Holger Vogelsang
SWT/JFace und RCP
Don’t
panic
Inhaltsverzeichnis
1 Einleitung
1.1 Organisation der Vorlesung . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Vorbereitungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Kassifikation von Anwendungen
2.1 Einteilung nach Architekturen . . . . . . . .
2.2 Einteilung nach Zielplattformen . . . . . . .
2.3 Übersicht . . . . . . . . . . . . . . . . . . .
2.4 Architekturen von Fat-Client-Anwendungen
2.5 Interaktionseigenschaften . . . . . . . . . .
3 SWT und JFace
3.1 Grundlagen . . . . . . . . . . . .
3.2 Layout-Management . . . . . . .
3.3 SWT-Widgets . . . . . . . . . . .
3.4 Menüs und Toolbar-Leisten . . .
3.5 Basisklassen . . . . . . . . . . .
3.6 Ereignisbehandlung . . . . . . .
3.7 Model-View-Controller mit JFace
3.8 Weitere Container . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
.
.
.
.
.
7
7
9
10
10
12
.
.
.
.
.
.
.
.
14
16
25
50
54
63
67
79
100
4 Anbindung an die Geschäftslogik
101
5 Deklarative Beschreibungen
102
6 Eclipse Rich Client Platform
103
7 Multithreading in Java
7.1 Threads erzeugen und starten . . . . . . . .
7.2 Threads beenden und löschen . . . . . . . .
7.3 Zugriff auf gemeinsam genutzte Ressourcen
7.4 Synchronisation von Threads . . . . . . . . .
7.5 Threads als Daemons . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
104
104
105
106
107
108
Inhaltsverzeichnis
8 Abbildungsverzeichnis
109
9 Literaturverzeichnis
112
10 Stichwortverzeichnis
114
1
Einleitung
Diese Veranstaltung behandelt grafische Benutzungsoberflächen auf Basis der Eclipse
Rich Client Platform. Ergonomische Aspekte werden nicht betrachtet, da es zu diesem
Thema eine eigenständige Vorlesung gibt.
1.1 Organisation der Vorlesung
Neben der Klausur kann auch eine Programmieraufgabe gelöst werden. Durch Abgabe
einer korrekten Lösung erhalten Sie Bonuspunkte für die Klausur. Nähere Informationen
sind auf der Webseite zur Vorlesung im Ilias unter dem FAQ zu finden.
1.2 Vorbereitungen
1.2.1
Installation des JDK unter Windows
Zur reinen Ausführung von Java-Programmen genügt das JRE (Java Runtime Environment). Darin fehlen aber beispielsweise wichtige Werkzeuge wie der Compiler. Hier in
der Vorlesung sollen eigene Programme erstellt werden. Daher muss das JDK (Java
Development Kit, manchmal auch SDK genannt) verwendet werden.
1.2.2
Integrierte Entwicklungsumgebungen
Die Anwendung des „nackten“ JDKs ist nicht sehr komfortabel. Daher bietet sich die Verwendung einer integrierten Entwicklungumgebung an. Da in der Vorlesung die Eclipse
Rich Client Platform eingesetzt wird, bietet es sich an, Eclipse auch als IDE zu verwenden. Bitte wählen Sie auf den Pool-Rechnern als Projektverzeichnis ein Verzeichnis auf
dem Netzlaufwerk. Zur lokalen Installation auf einem privaten Rechner laden Sie von
http://www.eclipse.org/downloads die Version „Eclipse for RCP and RAP Developers“ herunter. Sie beinhaltet bereits alle notwendigen Plugins für die Vorlesung. Die
ZIP-Datei wird einfach in ein eigenes Verzeichnis extrahiert. Damit Sie später die Oberflächen nicht manuell erstellen müssen, bietet sich die Verwendung eines GUI-Editors an.
Eclipse liefert mit dem VEP (Visual Editor Plugin) eine eigene Implementierung, die aber
nicht immer im neuesten Eclipse funktionsfähig ist. Statt dessen kann für private Zwecke
der Jigloo GUI-Editor (http://www.cloudgarden.com/jigloo/) oder noch besser
1.2 Vorbereitungen
der WindowBuilder Pro (http://code.google.com/webtoolkit/tools/wbpro/
index.html) von Google als Eclipse-Plugin bezogen werden. Auf den Seiten befinden
sich auch die Installationsanleitungen.
1.2.3
Eclipse Rich Client Platform
Im Laufe der Vorlesung wird sich zeigen, dass die Eclipse Rich Client Platform am besten
separat installiert wird. Dazu wird unter http://download.eclipse.org/eclipse/
downloads/ die Eclipse-Version ausgewählt (am besten „Latest Release“) und auf der
folgenden Seite das „Platform SDK“ für die verwendete Betriebssystem-Version. Bitte
achten Sie darauf, dass inzwischen neben 32-Bit- auch 64-Bit-Betriebssysteme unterstützt werden. Wählen Sie Variante, die zu Ihrer installierten Java-Version passt. Die
Plattform wird ebenso wie die IDE in ein Verzeichnis entpackt. Es darf sich dabei nicht
um dasselbe Verzeichnis wie die IDE handeln.
1.2.4
Aufbau dieses Skriptes
Das Skript beinhaltet eine Einführung in alle wichtigen Themen, die zur Lösung der
Aufgabe notwendig sind. Weiterhin betrachtet es viele Beispiele ausführlicher als die
PowerPoint-Unterlagen der Vorlesung. So wird beispielsweise im Skript deutlich mehr
Bezug auf das verwendete API genommen.
1. Einleitungskapitel: Dieses lesen Sie jetzt gerade.
2. Klassifikation von Anwendungen: Wie unterscheiden sich Anwendungen, bezogen
auf die Benutzungsoberfläche?
3. Einführung in SWT: Hier finden Sie eine Einführung in SWT, die alle wichtigen Konzepte vorstellt. Parallel dazu sollten immer sowohl die JDK-Dokumentation als auch
die Online-Hilfe aus Eclipse griffbereit sein.
4. Einführung in JFace: JFace setzt auf SWT auf und bietet eine höhere Abstraktion
von den einzelnen Fensterelementen. So wird beispielsweise das MVC-Muster unterstützt.
5. Anbindung an die Geschäftslogik: Dieses Kapitel beschreibt die Trennung von Anwendungslogik und Benutzungsoberfläche.
6. Deklarative Beschreibungen: Hier wird gezeigt, wie sich Oberflächen deklarativ mit
XML-Dialekten beschrieben lassen.
7. Rich Client Platform: Es wird die Idee des Frameworks vorgestellt.
Am Ende des Dokumentes befindet sich in Kapitel 7 noch eine Einführung in das Multithreading unter Java. Abgeschlossen wird das Skript mit einem Abbildungs-, Literatursowie einem Stichwortverzeichnis.
6
2
Kassifikation von Anwendungen
Anwendungen lassen sich sehr unterschiedlich in Kategorien einteilen. Da wir uns mit
grafischen Benutzungsoberflächen befassen, erfolgt hier zunächst die Einordnung anhand der Architekturmerkmale für die Oberflächenkonstruktuktion.
2.1 Einteilung nach Architekturen
In der Literatur sind die verwendeten Begriffe nicht so einheitlich zu finden, wie es die
Aufstellung hier vermuten lässt.
2.1.1
Thin Client
Es handelt sich dabei beispielsweise um eine Ajax-Anwendung, die in einem Browser
ausgeführt wird. Charakteristisch sind:
Start über eine URL
Eine lokale Installation auf dem Client ist nicht erforderlich, da ein halbwegs aktueller
Browser immer vorhanden sein sollte.
Die Geschäftslogik befindet sich überwiegend auf dem Server.
Die Kommunikation erfolgt nach dem Request/Response-Prinzip. Somit kann nur
der Client den Server kontaktieren. Inzwischen existieren auch neuere Ansätze (z.B.
mit Apache Comet), die es erlauben, den Client auch vom Server aus zu kontaktieren.
Browser
(HTML, JavaScript)
Server
(Application-Server,
Datenbank, …)
Abbildung 2.1: Kommunikation bei einem Thin-Client
2.1 Einteilung nach Architekturen
2.1.2
Rich Thin Client
Die Anwendung läuft wie bei einem Thin Client im Browser ab. Allerdings wird hier nicht
mehr auf die reinen Browser-Techniken gesetzt. Statt dessen führt ein Plugin im Browser
die Anwendung aus. Dabei kann es sich beispielsweise um Java-Applets oder FlashProgramme handeln. So stehen wesentlich mächtigere Sprachmittel als bei reinen Thin
Clients zur Verfügung. Eigenschaften:
Start über eine URL
Eine lokale Installation auf dem Client ist nicht erforderlich. Das Plugin kann automatisch durch den Browser nachinstalliert werden
Die Geschäftslogik befindet sich überwiegend auf dem Server.
Die Kommunikation erfolgt auch hier in der Regel nach dem Request/ResponsePrinzip.
Browser
(HTML, JavaScript,
Flash, Applet)
Server
(Application-Server,
Datenbank, …)
Abbildung 2.2: Kommunikation bei einem Rich Thin Client
2.1.3
Rich Fat Client
Ein Rich Fat Client (oder auch kurz Rich Client) ist eine Anwendung, die großenteils auf
dem Client abläuft. Sie basiert auf einer Plattform wie z.B. Eclipse RCP oder NetBeans
Platform. Merkmale:
Es ist eine lokale Installation auf dem Client erforderlich. Java bietet mit Java Web
Start einen eleganten Mechanismus, die Java-Anwendung durch einen Browser
nachzuinstallieren. Damit ist nur noch die manuelle Einrichtung eines JRE auf dem
Client erforderlich.
Die Plattform kann zusätzliche Module vom Server nachladen. Weiterhin bieten viele Plattformen auch einen automatischen Update-Mechanismus, indem sie in bestimmten Abständen den Server nach neuen Modulversionen befragen und diese
herunterladen.
Die Geschäftslogik kann sich auf Client und Server befinden.
Die Kommunikation zwischen Client und Server findet entweder nach dem Request/Response-Prinzip oder aber wirklich bidirektional statt.
8
2.2 Einteilung nach Zielplattformen
Client
(Eclipse RCP, …)
Server
(Application-Server,
Datenbank, …)
Abbildung 2.3: Kommunikation bei einem Rich Fat Client
2.1.4
Smart Client
Der Begriff des Smart Clients ist häufig im Microsoft-Umfeld zu finden. Er bezieht sich fast
immer auf Anwendungen, die auf .NET, ASP.NET, XAML usw. basieren. Eigenschaften:
Der Start der Anwendung erfolgt aus dem Browser heraus über eine URL. Somit ist
keine lokale Installation erforderlich.
Die Geschäftslogik kann sich auf Client und Server befinden.
2.1.5
Fat Client (plattformunabhängig)
Hierbei handelt es sich um eine „klassische“ Desktop-Anwendung. Sie wird beispielsweise in Java mit Swing oder SWT bzw. in C++ mit QT usw. erstellt. Wichtig ist also,
dass sowohl die verwendeten Bibliotheken als auch der eigentliche Quelltext plattformunabhängig sind. Damit ist die Anwendung auf binärer Ebene zwar nicht direkt auf allen
unterstützten Plattformen ausführbar, aus dem Quelltext lässt sich aber durch einfaches
Übersetzen auf einer anderen Zielplattform ein lauffähiges Programm erzeugen. Charakteristisch sind:
Der Start erfolgt lokal auf dem Client. Dazu ist auch eine lokale Installation erforderlich.
Die Geschäftslogik ist überwiegend auf dem Client, teilweise aber auch auf dem
Server vorhanden.
2.1.6
Fat Client (plattformabhängig)
Prinzipiell gelten dieselben Aussagen wie für den plattformunabhängigen Fat Client. Als
Besonderheit kommt aber hinzu, dass plattformabhängige Bibliotheken verwendet werden. So könnte die Benutzungsoberfläche mit Hilfe der MFC-Klassen von Microsoft erstellt worden sein, für die es keine entsprechende Implementierung auf anderen Systemen gibt.
2.2 Einteilung nach Zielplattformen
Neben den klassischen Desktop-PCs und Laptops gibt es eine Reihe weiterer Hardwareplattformen, die eine Anwendung unterstützen kann.
Mobile Geräte: Diese besitzen unterschiedliche Betriebssysteme wie Windows CE/Mobile, Symbian OS, Google Android, iOS usw.
9
2.4 Architekturen von Fat-Client-Anwendungen
Unterschiedliche Leistungen: Nicht alle Geräte haben genügend Speicher oder ausreichend leistungsfähige Prozessoren. Auch die Größen der Displays können sich
signifikant unterscheiden: Normale mobile Telefone, Smartphones, PDAs, TabletPCs, usw.
Unterschiedliche Programmierumgebungen: Es gibt nicht auf allen Geräten einheitliche Entwicklungssprachen und -umgebungen: Java ME, C++, C#, usw.
2.3 Übersicht
Die folgende Tabelle stellt die Vor- und Nachteile der verschiedenen Architekturen einander gegenüber. Die Einteilung ist nicht vollständig.
Tabelle 2.1: Architekturvergleich
Thin
Client
(Ajax)
Rich Thin
Client
(Browser
+ Plugin)
Rich Fat
Client
(Java)
Microsoft
Smart
Client
Java Fat
Client
Windows
oder
LinuX Fat
Client
++
++ / −1
Administration
++
++ / −
2
−−
−
−−
−
−
−
−
−
Wartung (SW)
−
++
++
++
++
++
Interaktion
−
++
++
++
++
++
Performance
−
−
++
++
++
++
GUI-Konsistenz
−
−
++
++
++
++
Plattformunabh.
++
+
++
−−
++
−−
−
−
++ / −−3
−
++ / −−3
++ / −−3
Server
Server
Client und
Server
Client und
Server
überwiegend auf
dem Client
überwiegend auf
dem Client
Installation
Arbeit bei
Serverausfall
Applikationslogik
2.4 Architekturen von Fat-Client-Anwendungen
Eine Fat-Client-Anwendung besteht typischerweise aus mehreren Schichten, von denen
hier nur einige betrachtet werden sollen:
Hardware: Diese Schicht beinhaltet die eigentliche Ein- und Ausgabehardware, über
die mit der Anwendung kommuniziert wird.
Betriebssystem: Das Betriebssystem greift über Gerätetreiber auf die Hardware zu
und abtrahiert gleichzeitig von ihr.
Das Fenstersystem wird häufig als Bestandteil des Betriebssystems angesehen.
Es nutzt die Abstraktionen von der Hardware, um den Anwendungen Fenster zur
Darstellung von Inhalten sowie Interaktionen mit den Fenstern anzubieten.
1
Eventuell notwendige Installation einer Laufzeitumgebung
2
Eventuell notwendige Einrichtung einer Laufzeitumgebung
3
Hängt von der Konzeption ab
10
2.4 Architekturen von Fat-Client-Anwendungen
Benutzungsschnittstelle (Struktur): Sie ist für die Darstellung und Verwaltung des
Inhalts in den Fenstern verantwortlich. Sie übernimmt auch die Anordnung der einzelnen Fensterelemente (z.B. Widgets) untereinander.
Dialogsteuerung, Ein- und Ausgabe von Daten: Diese Schicht kontrolliert die Aktionen des Benutzers in Bezug auf deren Plausibilität. Sie prüft beispielsweise, ob
gewisse Eingaben sinnvoll und korrekt sind. Weiterhin steuert sie die Benutzungsschnittstelle und die Geschäftslogik in der übergeordneten Schicht, indem sie auch
die Daten in der Oberfläche und Geschäftslogik synchronisiert und Ereignisse weitermeldet.
Anwendungskern (Geschäftslogik): Hier befindet sich die eigentliche Funktionalität der Anwendung. Sie wird teilweise von der Dialogsteuerung aufgerufen, wenn
Ereignisse eingetreten sind.
Anwendungskern
(Geschäftslogik)
Vorlesungsinhalt
Dialogsteuerung,
Ein-/Ausgabe
von Daten
Benutzungsschnittstelle
(Struktur)
Fenstersystem
(Präsentation)
Betriebssystem
Tastaturtreiber
Maustreiber
Grafiktreiber
Hardware
Abbildung 2.4: Architektur einer Fat-Client-Anwendung
Die Vorlesung beschäftigt sich im Wesentlichen mit der Dialogsteuerung sowie der Struktur der Benutzungsschnittstelle. Zusätzlich wird mit der Eclipse Rich Client Platform ein
Framework betrachtet, das ein Gerüst für eine Fat-Client-Anwendung vorgibt.
11
2.5 Interaktionseigenschaften
Das folgende Beispiel soll die einzelnen Schichten verdeutlichen:
Struktur
(Widgets und
deren Anordnung)
Geschäftslogik
(Datum kleiner als
aktuelles Datum)
Ein- und Ausgabe
(Daten aus dem
Dialog)
Präsentation
(Farben,
Zeichensätze, ...)
Dialogsteuerung
(entsperrt, wenn die
Eingaben in Ordnung sind,
löst Aktion aus)
public class Customer {
private String
firstName;
private String
lastName;
private int
customerNumber;
private boolean
retired;
private FamilyStatus familyStatus;
private Date
customerSince;
// ...
}
Abbildung 2.5: Architekturschichten anhand eines Beispiels
Durch die Struktur wird beschrieben, wie die einzelnen Widgets untereinander platziert sind und wie sich sich bei einer Größenänderung des Dialogs verhalten sollen.
Das Aussehen der Widgets und der Fenster wird von der Präsentationsschicht des
Fenstersystems übernommen. So kann ein einheitliches Erscheinungsbild aller Anwendungen in einem Fenstersystem erzielt werden. Die Eclipse Rich Client Platform
zusammen mit SWT und JFace erlaubt, wie andere Toolkits auch, das Aussehen und
Verhalten in gewissen Grenzen zu ändern.
Die Dialogsteuerung kann beispielsweise die OK-Taste erst dann freigeben, wenn
alle Eingaben in Ordung sind. Weiterhin löst sie durch einen Druck auf die Taste
Aktionen aus.
Die Dialogsteuerung kann nicht immer alle Eingaben selbst validieren, weil ihr dazu
häufig Informationen fehlen, die nur in der Geschäftslogik zu finden sind. In dem
Beispiel muss das eingegebene Datum bestimmte Bedingungen erfüllen, die von
der Geschäftslogik festgelegt sind.
Die im Dialog dargestellten oder veränderten Daten müssen der Geschäftslogik zur
Verfügung gestellt werden bzw. stammen von ihr. Diese uni- oder bidirektionale Kommunikation wird von der Ein- und Ausgabeschicht übernommen. Im diesem Beispiel
beinhaltet eine Java-Klasse die Daten des Dialogs.
2.5 Interaktionseigenschaften
Ein Benutzer kann mit der Anwendung auf vielfältige Varianten kommunizieren, wenn
diese das unterstützt:
1. Menü-Auswahl: Steuerung der Anwendung erfolgt durch Menü-Kommandos.
12
2.5 Interaktionseigenschaften
2. Formular-basiert: Die Daten werden in Formulare eingegeben. Diese Variante ist
besonders gut für einfach strukturierte Daten geeignet.
3. Direkte Manipulation: Die Bearbeitung von Objekten erfolgt auf einer Arbeitsfläche durch Drag-und-Drop, kontextsensitive Menüs usw. Beispiele sind Drag-und
Drop für Dateioperationen, UML-Editoren und Zeichenprogramme. Dafür gibt es bereits eine ganze Anzahl von Java-Bibliotheken wie Naked Objects http://www.
nakedobjects.org/ und Eclipse GEF http://www.eclipse.org/gef/.
4. Sprachgesteuerte Interaktionen (natürliche Sprache, anwendungsspezifische Sprache): Dazu gehören Anweisungen auf der Kommandozeile, in Anwendungen eingebettete Skriptsprachen sowie die Erkennung gesprochener Befehle.
In dieser Vorlesung spielen nur die Punkte 1 und 2 sowie etwas von Punkt 3 und 4 eine
Rolle.
13
3
SWT und JFace
Dieses Kapitel beschäftigt sich mit dem Standard Widget Toolkit („SWT“) sowie dem darauf aufsetzenden JFace, die beide zur Gestaltung der Benutzungsoberflächen verwendet
werden.
Anwendungskern
(Geschäftslogik)
Vorlesungsinhalt
Dialogsteuerung,
Ein-/Ausgabe
von Daten
Benutzungsschnittstelle
(Struktur)
Fenstersystem
(Präsentation)
Betriebssystem
Tastaturtreiber
Maustreiber
Grafiktreiber
Hardware
Abbildung 3.1: Struktur der Oberfläche
Warum werden hier aber gerade SWT und JFace verwendet? Es gibt ja auch Swing,
das plattforumabhängig und im JDK schon enthalten ist, QT, Windows-Forms, und viele
andere Toolkits. Zur Beantwortung der Frage ist zunächst ein Blick auf die Eigenschaften
von SWT und JFace erforderlich:
SWT wurde als Basis für die Entwicklung von Eclipse geschaffen.
3.1 Grundlagen
SWT verwendet denselben Ansatz wie das „Abstract Window Toolkit“ (AWT). Dabei
werden native Dialogelemente des Fenstersystems verwendet, soweit diese denn
vorhanden sind. Ansonsten zeichnet SWT sie selbst. Somit gibt es innerhalb von
SWT eine Bibliothek, die plattformabhängig ist. Die Programmierschnittstelle selbst
ist plattformunabhängig.
JFace bildet eine Abstraktionsschicht von SWT, indem es für viele Elemente den
MVC-Ansatz anbietet und teilweise die Ereignisbehandlung vereinfacht. Darüber
hinaus besitzt es eine Registry für Zeichensätze, Farben und Bilder sowie vordefinierte Dialoge und „Wizards“.
Die folgende Aufstellung fasst die Vor- und Nachteile von SWT und JFace zusammen.
+ Native Dialogelemente sind häufig schneller als selbst gezeichnete. Sie passen sich
auch der Darstellung des Fenstersystems an. Somit sieht eine SWT-Anwendung
genauso aus wie jede andere Anwendung des Fenstersystems.
+ SWT hat innerhalb der Eclipse Rich Client Platform inzwischen einen Einzug in die
Industrie gehalten.
+ Viele allgemeine Konzepte für Benutzungsoberflächen lassen sich anhand von SWT
und JFace beispielhaft aufzeigen.
+ Es gibt hervorragende Entwicklungsumgebungen und Modellierungswerkzeuge, die
teilweise kostenlos verfügbar sind.
+ Mit Hilfe der Rich Ajax Platform („RAP“) lassen sich aus bestehenden Fat-Clients
relativ einfach Web-Anwendungen (Rich Thin Clients) erzeugen.
+ Es sind viele Erweiterungen verfügbar (z.B. GEF für grafische Editoren, . . . ).
- Die Programmierschnittstelle des SWT ist teilweise nicht sehr objekt-orientiert ausgelegt. Insbesondere die häufige Verwendung von Konstanten statt zusätzlicher
Klassen ist störend.
- Es sind nicht so viele mächtige Dialogelemente wie in Swing vorhanden.
- Teilweise ist keine Garbage-Collection möglich. Als Folge daraus müssen Betriebssystemressourcen manuell freigegeben werden.
- Es werden keine Applets unterstützt. Das ist kein großer Mangel, da Applets in letzter Zeit immer seltener eingesetzt werden.
Da SWT plattformabhängig ist, werden nicht automatisch alle Java-Plattformen unterstützt. Allerdings wurde SWT auf praktisch alle relevanten Fenstersysteme portiert:
Windows 32 und 64 Bit: XP, Vista, 7
Linux 32 und 64 Bit: x86 (GTK), x86 (Motif), PPC (GTK), S390 (GTK)
Solaris: x86 (GTK), Sparc (GTK)
AIX: PPC (Motif)
HP-UX: ia64 (Motif)
Mac OS X: Carbon, Cocoa
Verschiedene eingebettete Geräte (im Rahmen von eRCP)
Anmerkung: Sie finden die Quelltexte aller Beispiele als Download auf der Homepage. In
jedem Beispiel ist der Name der Quelltextdatei im Archiv angegeben.
15
3.1 Grundlagen
3.1 Grundlagen
Dieser Abschnitt beschreibt die SWT-Grundlagen. Dazu gehören die Platzierung von
GUI-Elementen („Widgets“) durch so genannte Layout-Manager sowie die allgemeine
Verwendung der Widgets.
3.1.1
Die erste SWT-Anwendung
Um einen ersten Eindruck vom Aufbau einer reinen SWT-Anwendung zu vermitteln, zeigt
das folgende Beispiel ein kleines Programm, das nur einen Text in einer Taste darstellt
(Quelltextdatei FirstSWTApplication.java). Die Seitenzahlen dienen nur der späteren Erklärung des Quelltextes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FirstSWTApplication {
private Display display;
private Shell shell;
public FirstSWTApplication(Display display) {
this.display = display;
shell = new Shell(display);
shell.setText("Titeltext");
shell.setSize(200, 100);
}
private void createGUI() {
Button button = new Button(shell, SWT.PUSH);
button.setText("Das ist ein Text");
button.setBounds(shell.getClientArea());
shell.pack();
}
public void run() {
createGUI();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
public static void main(String[] args) {
new FirstSWTApplication(Display.getDefault()).run();
}
Damit ergeben sich die folgenden Bildschirmausgaben:
Abbildung 3.2: Beispiel unter Windows 7, MacOS X und Linux (GTK)
16
3.1 Grundlagen
Erste Erklärungen zum Quelltext:
Zeile 3: Das Display-Objekt repräsentiert das Fenstersystem, in dem das Programm läuft.
Zeile 4: Eine Shell ist ein „Top-Level“-Fenster des Fenstersystems. Später werden
auch andere Elemente wie Dialoge vorgestellt.
Zeil 14: Mit new Button wird eine Taste in das Fenster eingefügt. Wie die genaue Positionierung erfolgt, wird später erläutert. Wichtig zu wissen ist an dieser
Stelle nur, dass keine Positionen angegeben werden, weil ein sogenannter LayoutManager die Platzierung übernimmt. An diesem Widget ist auch erkennbar, das
Widgets in SWT immer erzeugt werden, indem deren Vaterelement als Parameter übergeben wird. In diesem Beispiel handelt es sich beim Vaterelement um das
Fenster selbst (Parameter shell).
Zeile 14: Durch Konstenten wie SWT.PUSH werden sehr häufig Eigenschaften eines
Widgets bestimmt. Diese Taste ist eine normale Taste, die nach dem Drücken sofort
wieder in ihren Ausgangszustand zurückkehrt.
Zeile 15: button.setText trägt die Beschriftung der Taste ein.
Zeile 16: button.setBounds legt fest, dass die Taste so groß wie der Inhaltsbereich des Fensters werden soll.
Zeile 17: Mit shell.pack wird die Layout-Berechnung gestarten. Die Fenstergröße
ermittelt sich aus der Größen und Positionen der Widgets innerhalb des Fensters.
Wie das genau funktioniert, wird später erläutert.
Zeile 9: shell.setText trägt die Titelzeile des Fensters ein.
Zeile 10: shell.setSize setzt manuell eine Fenstergröße. Normalerweise wird
diese aus dem Inhalt des Fenster berechnet.
Die Zeilen 23 bis 27 schließlich sehen zunächst einmal einfach nur merkwürdig aus.
Sie beinhalten die Ereignisschleife. Solange das Fenster nach dem Öffnen nicht
geschlossen ist, werden Ereignisse zugestellt. Danach wartet die Anwendung auf
weitere Ereignisse (Zeile 25). Nach dem Schließen des Fenster beendet sich die
Schleife und alle Ressourcen der Anwendung werden durch display.dispose()
freigegeben. Eine genauere Betrachtung der manuellen Freigabe erfolgt in Abschnitt
3.1.4.
Eine SWT-Anwendung hat generell diese Schichtenstruktur:
Widget
Widget
Widget
Shell-Klasse
Display-Klasse
Betriebssystemspezifische Klassen
Betriebssystem
Abbildung 3.3: Struktur einer SWT-Anwendung
17
3.1 Grundlagen
3.1.1.1
Erforderliche Dateien
Um das Beispiel übersetzen und ausführen zu können, ist eine jar-Datei erforderlich.
Sie finden diese entweder direkt im Verzeichnis plugins Ihrer Eclipse-Installation oder
im selben Verzeichnis Ihrer Eclipse-Zielplattform.
Mit org.eclipse.swt.Windowsystem.OS.Arch_Version.jar ist vorerst nur eine
Datei erforderlich. Dabei stehen die Abkürzungen für:
Windowsystem: Name des Fenstersystems
OS: Name des Betriebssystems
Arch: Name der Prozessorarchitektur
Version: Versionsnummer von SWT
Die genauen Bezeichnungen für Eclipse 3.5 sehen auf den unterschiedlichen Plattformen
so aus:
org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar für Windows 7, 32
Bit Intel-Architektur, SWT 3.5
org.eclipse.swt.gtk.linux.x86_3.5.2.v3557f.jar für Linux, GTK, 32
Bit Intel-Architektur, SWT 3.5
org.eclipse.swt.cocoa.macosx.x86_64_3.5.2.v3557f.jar für Mac OS
X, Cocoa, 64 Bit Intel-Architektur, SWT 3.5
Die wichtigsten darin enthaltenen Pakete, die für die ersten Beispiele benötigt werden,
sind org.eclipse.swt und org.eclipse.swt.widget. Später werden noch weitere jar-Dateien und Pakete hinzukommen.
3.1.1.2
Installation der jar-Dateien
Die zur Übersetzung und Ausführung verwendeten jar-Dateien lassen sich auf mehreren Wegen in das eigene Projekt einbinden. In Bezug auf die spätere Vorstellung der
Eclipse Rich Client Platform ist es aber am sinnvollsten, die Dateien nicht direkt in das
eigene Projekt zu kopieren. Es gibt mehrere Möglichkeiten:
In der Installation der Eclipse-IDE sind alle jar-Dateien vorhanden. Diese lässt sich
als Plattform, gegen die entwickelt wird, eintragen. Das hat aber den großen Nachteil, dass die eigene Anwendung von der Version der IDE abhängig ist. Weiterhin
sind in der IDE jar-Dateien vorhanden, die hauptsächlich für die IDE erforderlich
sind. Diese Dateien könnten versehentlich auch in eigenen Projekten verwendet
werden.
Besser ist es, das RCP-SDK von http://download.eclipse.org/eclipse/
downloads/ herunterzuladen und in ein separates Verzeichnis auszupacken. Danach wird das SDK als Zielplattform eingetragen.
Statt also die Dateien direkt in das eigene Projekt zu kopieren, ist es sinnvoller, mit einer
sogenannten Zielplattform zu arbeiten. In dieser sind alle jar-Dateien vorhanden, die in
den eigenen Projekten Verwendung finden sollen. Die Zielplatform kann in den globalen
Eigenschaften der IDE eingestellt werden, wie Abbildung 3.4 zeigt.
18
3.1 Grundlagen
Die Ziel-Plattform:
Abbildung 3.4: Zielplattform einstellen
Mit „Add...“ lassen sich neue Zielplattformen wie z.B. das entpackte RCP-SDK hinzufügen (Abbildung 3.5).
Abbildung 3.5: Zielplattform einrichten
Eine der Plattformen wird schließlich als aktiv markiert, indem der Haken vor deren
Namen gesetzt wird. Jetzt können Abhängigkeiten zu einzelnen jar-Dateien der Zielplattform im eigenen Projekt eingestellt werden. Dazu dient die vordefinierte Variable
ECLIPSE_HOME, die auf die aktuelle Zielplattform verweist. Abhängigkeiten sollten immer relativ zu dieser Variablen angegeben werden, damit die Plattform austauschbar
bleibt (Abbildung 3.6). Eclipse hat manchmal Probleme, nach einem Wechsel der Zielplattform die Paket- und Klassennamen in den Projekten korrekt aufzulösen. Ein Neustart
der IDE bewirkt hier Wunder.
19
3.1 Grundlagen
über Variable
einbinden
Abbildung 3.6: Abhängigkeit von Dateien der Zielplattform
3.1.2
Die erste JFace-Anwendung
Wie bereits erwähnt, ist JFace eine Klassenbibliothek, die auf SWT aufsetzt und bestimmte Interaktionen vereinfacht und Abstraktion wie z.B. den MVC-Ansatz bietet. Auf
die direkte Verwendung von SWT-Klassen kann dennoch nicht verzichtet werden.
Der folgende Quelltext zeigt eine Umsetzung des ersten SWT-Beispiels aus Abschnitt
3.1.1 mit den Mitteln von JFace (Quelltextdatei FirstJFaceApplication.java). Die
Seitenzahlen dienen auch hier nur der späteren Erklärung des Quelltextes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class FirstJFaceApplication
extends ApplicationWindow {
public FirstJFaceApplication() {
super(null); // kein Vater-Fenster
}
// Erzeugen der Oberfläche
@Override
protected Control createContents(Composite parent) {
Button button = new Button(parent, SWT.PUSH);
button.setText("Das ist ein Text");
button.setBounds(parent.getClientArea());
parent.pack();
return parent;
}
public void run() {
setBlockOnOpen(true);
open();
Display.getDefault().dispose();
}
}
public static void main(String[] args) {
new FirstJFaceApplication().run();
}
Schon auf den ersten Blick ist erkennbar, dass das Grundgerüst der Anwendung anders
ausfällt und die Ereignisschleife nicht mehr direkt sichtbar ist. Erklärungen:
Zeile 10: Die Methode createContents wird intern automatisch von der Basisklasse ApplicationWindow aufgerufen, um die Oberfläche zu erzeugen. Als Er20
3.1 Grundlagen
gebnis liefert sie die Vater-Komponente zurück, die alle anderen Komponenten beinhaltet. Es gibt weitere Methoden mit dem Namensaufbau createXYZ, die auch automatisch aufgerufen werden. Sie dienen beispielsweise der Erzeugung von Menüs.
Zeile 19: setBlockOnOpen: Mit dem Parameter true kehrt der Aufruf der openMethode erst nach dem Schließen des Fensters zurück.
Zeile 20: open: Das Fenster wird angezeigt. Der Aufruf kehrt in diesem Beispiel
erst nach dem Schließen des Fensters zurück. Hier verbirgt sich dann auch die
Ereignisschleife.
Zeile 21: dispose: Alle Ressourcen des Fensters sowie die Ressourcen seiner
Kindelemente werden wieder freigeben. Unterbleibt der Aufruf, dann kann Java manche Objekt nicht per Garbage-Collector freigeben. Weiterhin kann es passieren,
dass dem Fenstersystem irgendwann die Ressourcen ausgehen. Die Freigabeproblematik wird gleich noch genauer besprochen.
Eine JFace-Anwendung hat generell diese Schichtenstruktur:
Widget
Widget
Widget
Composite-Klasse
ApplicationWindow-Klasse
Shell
von der ApplicationWindowKlasse erzeugt
Display-Klasse
Betriebssystemspezifische Klassen
Betriebssystem
Abbildung 3.7: Struktur einer JFace-Anwendung
Im Unterschied zum reinen SWT wird hier durch die Basisklasse ApplicationWindow
eine Struktur der Anwendung vorgegeben.
3.1.2.1
Erforderliche Dateien
Zur Übersetzung und Ausführung sind einige plattformunabhängige jar-Dateien von
JFace erforderlich. Auch diese sollten aus der Zielplattform stammen:
org.eclipse.jface_Version.jar: Allgemeine JFace-Klassen
org.eclipse.jface.text_Version.jar: Klassen zur Behandlung von Textdokumenten
org.eclipse.equinox.common_Version.jar: Klassen für das zugrunde liegende OSGi-Framework. OSGi wird später im Rahmen der Rich Client Platform
eingeführt.
org.eclipse.core.commands_Version.jar: Klassen, die hauptsächlich von
anderen JFace-Klassen verwendet werden
21
3.1 Grundlagen
org.eclipse.core.runtime_Version.jar: Klassen für die Eclipse-Laufzeitumgebung. Auch diese wird mit der Rich Client Platform später betrachtet.
org.eclipse.osgi_Version.jar: Weitere Klassen für das OSGi-Framework
Zusätzlich sind für das Text-Beispiel später diese beidenjar-Dateien notwendig, die nicht
Bestandteil des RCP-SDK sind. Sie können aus der Installation der Eclipse-IDE kopiert
werden.
org.eclipse.text_Version.jar
com.ibm.icu.text_Version.jar
Diese Dateien sollten auch wirklich nur dann kopiert werden, wenn deren Funktionalität
erforderlich ist. Sie werden hier im Skript eingesetzt, um einen „mächtigen“ Texteditor mit
Syntaxhervorhebung zu bauen. Die Dateien enthalten Teile des Editors aus der IDE.
3.1.3
Lauffähige Anwendung
Obwohl SWT eine plattformabhängige Bibliothek benötigt, lässt sich aus einem solchen
Projekt eine eigenständig lauffähige Anwendung erzeugen. Dazu muss das EclipseProjekt für Windows und LinuX lediglich als lauffähige jar-Anwendung mit allen Bibliotheken und Ressourcen exportiert werden. Für MacOS X ist es etwas komplizierter.
Unter der URL http://www.eclipse.org/swt/macosx/ ist eine schrittweise Beschreibung vorhanden.
3.1.4
Freigabe von Ressourcen
Im Zusammenhang mit SWT tritt ein Problem auf, mit dem in Java so normalerweise
nicht gerechnet wird. Werden Ressourcen wie Objekte mit new auf dem Heap angelegt, dann kann der Garbage-Collector diese wieder freigeben, sobald keine Referenz
mehr darauf verweist.1 Das funktioniert bei Anwendungen mit SWT so nicht immer. Der
Grund ist darin zu sehen, dass SWT Ressourcen des Fenstersystems verwendet, um
seine Widgets darzustellen. Zu diesen Ressourcen gehören beispielsweise Fenster, Farben, Zeichensätze und Tasten. Java-Objekte, die solche Ressourcen kapseln, können
vom Garbage-Collector nicht automatisch wieder freigeben werden. Dieses muss manuell durch den Aufruf der dispose-Methode des Objektes erfolgen. Nach diesem Aufruf
darf weder lesend noch schreibend auf das Objekt zugegriffen werden. Sollte das doch
geschehen, dann quittiert SWT das mit dem Auslösen einer Ausnahme. Wie sieht das in
der Praxis aus? Es gibt zwei einfache Regeln, die das Verhalten beschreiben.
Regel 1: Wer ein Element erzeugt, gibt es auch mit dispose() wieder frei. Beispiel
für die Erzeugung eines neuen Farb-Objektes:
Color color = new Color(display, 0, 0, 255);
...
color.dispose();
Wird dagegen ein existierendes, vordefiniertes Element verwendet, dann darf es
nicht freigeben werden. Beispiel für die Verwendung eines von SWT erzeugten FarbObjektes:
1
Ok, das ist etwas vereinfacht ausgedrückt.
22
3.1 Grundlagen
Color color = display.getSystemColor(SWT.COLOR_RED);
Regel 2: Viele Widgets und andere Ressourcen werden hierarchisch geschachtelt.
Bei ihrer Erzeugung wird ein Vaterelement im Konstruktor übergeben. Wird jetzt
das Vaterelement gelöscht, dann werden automatisch auch alle seine Kindelemente
entfernt und korrekt mit dispose() freigegeben. Beispiel, in dem eine Taste als
Kind einem Fenster hinzugefügt wird:
Shell shell = new Shell(display);
Button button = new Button(shell, SWT.PUSH);
Es ist in dem Beispiel ausreichend, das Fenster freizugeben. Dieses löscht automatisch alle seine Kindelemente:
shell.dispose();
// Gibt auch button wieder frei.
Leider ist es in der Praxis nicht ganz einfach, immer daran zu denken, alle Ressourcen auch wieder freizugeben. Daher sollte sich ein Entwickler an einen gewissen Standardaufbau in seiner Anwendung halten. Die folgenden beiden Abschnitt zeigen diesen
sowohl für reine SWT-Lösungen als auch für Anwendungen, die JFace verwenden. Die
SWT-Variante ist nur eine Notlösung. Wann immer es möglich ist, sollte der JFace-Weg
gewählt werden.
3.1.4.1
Ressourcenverwaltung bei reinen SWT-Anwendungen
Die Hauptidee besteht darin, das Anlegen sowie die Freigabe von Ressourcen zentral zu
bündeln. Beispiel (Quelltext FirstSWTImageResourceApplication):
private void
if (image1
image1 =
if (image2
image2 =
}
createImages() {
== null)
new Image(display, "resources/image1.gif");
== null)
new Image(display, "resources/image2.gif");
private void disposeImages() {
if (image1 != null)
image1.dispose();
if (image2 != null)
image2.dispose();
}
public void run() {
createImages();
createGUI();
// Ereignisschleife
disposeImages();
display.dispose();
}
Dieser Ansatz funktioniert für sehr kleine Programm ganz gut. Leider hat er aber gerade für „richtige“ Anwendungen große Nachteile. So werden unter Umständen Bilder
angelegt, die nicht immer benötigt werden. Es ist ja bei der Erzeugung nicht sicher, dass
beispielsweise der Dialog, der die Ressourcen verwendet, überhaupt angezeigt wird. Es
ist auch bei vielen Klassen schwierig, Bilder und andere Ressourcen zwischen mehreren
Dialogen, Menüs, usw. zu teilen.
23
3.1 Grundlagen
3.1.4.2
Ressourcenverwaltung bei JFace-Anwendungen
Der JFace-Ansatz versucht, die Nachteile der SWT-Lösung zu umgehen. Dazu werden
entweder ein zentrales oder aber mehrere Registry-Objekte für Bilder, Zeichensätze und
Farben der Anwendung angelegt. Eine Registry nimmt normalerweise lediglich Beschreibungen der Ressourcen (sogenannte Deskriptoren) auf. Ein solcher Deskriptor beinhaltet z.B. den Namen der Bilddatei. Beim Programmstart werden die Deskriptoren aller
benötigten Ressourcen in der Registry abgelegt. Ist sicher, dass bestimmte Ressourcen immer benötigt werden, dann lassen sich statt der Beschreibungen auch direkt die
Ressource-Objekte erzeugen und in der Registry ablegen. Wird später im Betrieb ein
Bild oder eine andere Ressource benötigt, dann erzeugt die Registry diese beim ersten
Zugriff anhand der Beschreibung. Damit gehören die Ressourcen der Registry, so dass
diese beim Programmenende für deren Freigabe verantwortlich ist. Und wann wird die
Registry gelöscht? Jedes Registry-Objekt ist ein Kindelement eines Display-Objektes.
Sobald das Display-Objekt der Anwendung beseitigt wird, entfernt es auch alle seine
Kindelement. Beispiel (Quelltext FirstJFaceImageResourceApplication):
public class FirstJFaceImageResourceApplication
extends ApplicationWindow {
private ImageRegistry imageRegistry;
public FirstJFaceImageResourceApplication() {
super(null);
createImages();
}
@Override
protected Control createContents(Composite parent) {
Button button = new Button(parent, SWT.PUSH);
button.setImage(imageRegistry.get("fighting-duke"));
// ...
return parent;
}
// Registry mit allen möglicherweise benötigten
// Deskriptoren der Ressourcen erzeugen.
private void createImages() {
imageRegistry = new ImageRegistry(Display.getDefault());
imageRegistry.put("fighting-duke",
ImageDescriptor.createFromFile(this.getClass(),
"/resources/duke-fight.gif"));
}
public void run() {
setBlockOnOpen(true);
open();
Display.getDefault().dispose();
}
}
public static void main(String[] args) {
new FirstJFaceImageResourceApplication().run();
}
In dem Beispiel oben sollten die Ressourcen-Namen am besten noch durch StringKonstanten definiert werden.
24
3.2 Layout-Management
Der JFace-Ansatz ist recht mächtig und vermeidet zuverlässig Freigabefehler. Wenn es
möglich ist, sollte er verwendet werden.
3.2 Layout-Management
3.2.1
Motivation
Dieser Abschnitt behandelt generell das Thema, wie einzelne GUI-Elemente („Widgets“)
in einem Dialog angeordnet werden. Dazu stellt sich natürlich sofort die Frage, warum
man nicht einfach die Koordinaten der Elemente sowie deren Größen vorgibt. Das Ergebnis wäre also eine absolute Positionierung. Daraus resultieren eine ganze Anzahl von
Problemen, die die folgenden Abschnitte anhand verschiedener Szenarien beschrieben.
3.2.1.1
Unterschiedliche Plattformen
Betrachtet man die Ausgabe auf unterschiedlichen Plattformen, dann ist leicht zu sehen,
dass nicht immer alle Zeichensätze auf allen Betriebssystemen vorhanden sind und somit
die Texte unterschiedliche Ausmaße besitzen. Weiterhin unterscheiden sich die Designs
und damit die Größen der Widgets auf den Plattformen. Das folgende Beispiel wurde mit
dem Quelltext NullLayout erstellt.
Windows 7:
Abbildung 3.8: Windows 7
Windows XP:
Abbildung 3.10: Windows XP
Abbildung 3.9: LinuX, GNOME 2.28.2
Mac OS X (10.6):
Abbildung 3.11: MacOS X
Es ist gut zu sehen, dass das Layout, das für Windows 7 erstellt wurde, unter LinuX
und MacOS X zu zu kleinen Tasten führt. Die Kernaussage lautet also, dass auf die
Zeichensatz- und Widgetgrößen kein Verlass ist. Somit können die Größen einzelner
Komponenten nicht manuell berechnet werden.
3.2.1.2
Internationalisierung
Programme werden in der Regel nicht nur mit einer Spracheinstellung ausgeliefert. Gerade für den internationalen Markt ist es wichtig, dass ein Programm beliebig viele Sprachen unterstützt. Dummerweise sind aber viele Texte in Sprachen wie deutsch und englisch unterschiedlich lang. Somit schwankt auch deren Platzbedarf auf dem Bildschirm.
Die folgenden Darstellungen verdeutlichen das Problem (Quelltexte NullLayoutLanguagesEN.java und NullLayoutLanguagesDE.java, basieren auf dem vorherigem
Beispiel).
25
3.2 Layout-Management
Abbildung 3.12: Englische Texte
Abbildung 3.13: Deutsche Texte
Die Idee, einen Dialog in seinem Aussehen manuell der jeweiligen Sprache anzupassen,
sollte man schnell wieder begraben, da der Aufwand gewaltig wird. Es muss nicht nur
für jede neue Sprache der Dialog angepasst werden, sondern es müssen dann auch
verschiedene Varianten desselben Dialogs im Laufe der Jahre gepflegt werden.
3.2.1.3
Interaktive Größenänderungen
Dialoge sind für den Anwender dann besonders komfortabel zu verwenden, wenn sie in
ihrer Größe so verändert werden können, dass sich der Inhalt sinnvoll anpasst. Damit ist
gemeint, dass beispielsweise bei manueller Vergößerung eines Dialogs die Texteingabefelder breiter werden, damit mehr Text sichtbar wird. Beschriftungen dagegen sollten ihre
Größe behalten. Bei absoluter Platzierung passiert das, was die folgenden Bilder zeigen:
Abbildung 3.14: Vergrößerter Dialog
Abbildung 3.15: Verkleinerter Dialog
Wie zu sehen ist, werden im linken Bild die Tasten nicht größer, obwohl jetzt genügend
Platz vorhanden ist. Auch bei anderen Widgets wie Textfeldern ist es durchaus interessant, den Dialog zu vergrößern, um mehr Text sehen zu können, wenn es die Bildschirmgröße zulässt.
3.2.1.4
Look and Feel
SWT erlaubt, das Aussehen durch CSS-Dateien zu verändern. Da davon die Zeichensatzgrößen sowie Bilder betroffen sein können, führt auch hier die absolute Platzierung
in eine Sackgasse.
3.2.1.5
Zusammenfassung
Eine echte plattformunabhängige Programmierung ist mit absoluten Layouts nicht sinnvoll möglich. Unterschiedliche Zeichensätze oder Zeichengrößen verbieten die manuelle
Berechnung einer Größe. Weiterhin erfordert die Portierung einer Anwendung in mehrere Sprachen bei absoluten Layouts manuelles Nacharbeiten, was nicht praktikabel ist.
Deshalb sollten in SWT und damit auch JFace Dialoge niemals ohne Layoutmanager
erstellt werden. Aber auch andere Toolkits wie QT, GTK, Swing, AWT und WPF arbeiten
mit Layouts. Die Manager funktionieren zwar überall etwas unterschiedlich, die Idee und
grundsätzliche Arbeitsweise aber sind identisch.
Die folgenden Abschnitte stellen die Verwendung sogenannter Layoutmanager vor, die
die Platzierung von Widgets anhand von Regeln übernehmen. Eine gute Einführung ist
auch unter [Layouts] zu finden.
26
3.2 Layout-Management
3.2.2
Größe einer Komponente
Wenn jetzt auf die absolute Positionierung verzichtet wird und statt dessen Layoutmanager Einsatz finden, stellt sich die Frage, wie ein Layoutmanager die Größe aller möglichen
Komponenten ermitteln kann. Die Antwort ist einfach: Er kann es nicht selbst. Nur eine
Komponente bzw. deren Basisklasse Control weiß, wie groß sie gerne werden möchte. Und diese bevorzugte Größe fragt der Layout-Manager an allen Komponenten ab,
die er platzieren muss. Da jedes SWT-Widget von Control erbt, steht die Angabe allen
Widgets zur Verfügung.
Abbildung 3.16: Hierarchie Button
Abbildung 3.17: Hierarchie Label
Die Basisklasse Control besitzt die Methode computeSize, die die bevorzugte Größe
des Widgets berechnet und zurückgibt. Mit Hilfe der Methoden getSize bzw. setSize
kann die aktuelle Größe des Widgets ausgelesen bzw. überschrieben werden. Somit
besitzt jedes Widget eine bevorzugte und eine aktuelle Größe. Die aktuelle Größe kann
von der bevorzugten abweichen, wenn beispielsweise der Dialog manuell in seiner Größe
verändert und die Widgets angepasst werden. Dann kann ein Widget größer oder kleiner
werden, als es eigentlich sein möchte.
3.2.3
Fill-Layout
Das Fill-Layout platziert alle darin enthaltenen Widgets nacheinander bzw. untereinander
in einer einheitlichen Größe. Somit werden alle Widgets auf die Maße des größten im
Layout vorhandenen Widgets gedehnt. Weiterhin lassen sich die Abstände zwischen den
Widgets, die äußeren Ränder um das Layout sowie die Ausrichtung (horizontales oder
verktikales Layout) angeben.
Widget 1
Widget 2
...
Widget 1
Widget 2
...
Widget n
Widget n
Abbildung 3.18: Anordnung im Fill-Layout
Die Ausrichtung wird – wie leider Vieles in SWT – durch Konstanten im Konstruktor festgelegt:
SWT.HORIZONTAL: Die Widgets werden waagerecht angeordnet.
SWT.VERTICAL: Die Widgets werden senkrecht angeordnet.
Die folgenden Bilder zeigen verschiedene Anwendungen des Layouts. Es ist gut zu erkennen, dass der Layout-Manager trotz unterschiedlicher Plattformen immer sicherstellt,
dass die Texte lesbar sind. Die Widgets, in diesem Fall die Tasten, liefern ja auf jeder
Plattform unterschiedliche Werte für ihre bevorzugte Größe zurück.
27
3.2 Layout-Management
Abbildung 3.19: Horizontale Anordnung im Fill-Layout
Manuelle Verkleinerung der Dialoge:
Abbildung 3.20: Manuelle Verkleinerung im Fill-Layout
Vertikale Ausrichtung ohne manuellen Eingriff:
Abbildung 3.21: Vertikale Anordnung im Fill-Layout
Das folgende, kleine Beispiel zeigt den Einsatz des Fill-Layouts mit vertikaler Ausrichtung
(Quelltext FillLayoutApplication).
...
shell.setLayout(new FillLayout(SWT.VERTICAL));
Button button = new Button(shell, SWT.PUSH);
button.setText("1. Taste");
button = new Button(shell, SWT.PUSH);
button.setText("2. lange Taste");
button = new Button(shell, SWT.PUSH);
button.setText("3. ganz lange Taste");
shell.pack();
...
Die Abstände um die Widgets sowie zum Rand hin werden nicht verändert. SWT vergibt dazu Standardwerte. Sollen diese überschrieben werden, dann kann das mit Hilfe
öffentlicher Attribute geschehen. Dieses ist ohnehin ein Prinzip der Layouts in SWT und
JFace. Die Eigenschaften werden nicht über Getter- und Setter-Methoden gelesen und
beschrieben, sondern durch direkten Zugriff auf die entsprechenden Attribute.
Einige Attribute der Klasse FillLayout:
28
3.2 Layout-Management
marginWidth: linker und rechter Rand um das Element, das dieses Layout besitzt
marginHeight: oberer und unterer Rand um das Element, das dieses Layout besitzt
spacing: Abstand zwischen den einzelnen Widgets im Layout
Das Vaterelement aller Widgets im Beispiel ist das Fenster. Ihm wird der Layout-Manager
zugewiesen. Wichtig ist noch der Aufruf shell.pack() am Ende des Beispiels. Mit
ihm werden die Layout-Berechnung gestartet und somit die Widgets durch den Manager
platziert.
3.2.4
Row-Layout
Der RowLayout ähnelt in gewisser Weise dem FillLayout. Auch hier werden die Widgets vertikal oder horizontal angeordnet. Allerdings berücksichtigt der Layout-Manager
die bevorzugten Größen der Widgets und verwendet sie. Somit werden in der Standardeinstellung alle Widgets in ihrer bevorzugten Größe platziert. Dieses Verhalten lässt sich
allerdings durch Schreiben eines Attributes ändern. So wird manuell erzwungen, dass alle Komponenten – wie beim FillLayout – so groß wie die größte Komponente werden
sollen. Eine genaue Betrachtung erfolgt weiter unten bei der Vorstellung der Attribute des
Layouts. Weiterhin kann bei diesem Layout festgelegt werden, dass ein manueller „Zeilenumbruch“ erfolgen soll, wenn die Layout-Zeile voll ist und dadurch Widgets verkleinert
werden müssten.
Widget 1
Widget 2
Widget 3
Widget 1
Widget 2
...
Widget n
...
Widget n
Abbildung 3.22: Anordnung im Row-Layout
Die Ausrichtung wird auch hier durch die bereits bekannten Konstanten im Konstruktor
festgelegt:
SWT.HORIZONTAL: Die Widgets werden waagerecht angeordnet.
SWT.VERTICAL: Die Widgets werden senkrecht angeordnet.
Die folgenden Bilder zeigen verschiedene Anwendungen des Layouts. In der Abbildung
3.23 ist zu erkennen, wie alle Widgets in ihrer bevorzugten Größe platziert wurden.
Abbildung 3.23: Horizontale Anordnung im Row-Layout
Nach einer manuellen Verkleinerung ergibt sich das folgende Bild. Zu erkennen ist, dass
der automatische Zeilenumbruch nicht eingeschaltet wurde.
29
3.2 Layout-Management
Abbildung 3.24: Manueller Verkleinerung im Row-Layout (ohne Umbruch)
Eine manuelle Verkleinerung mit aktivem Zeilenumbruch dagegen würde die Widgets in
ihrer bevorzugten Größe belassen:
Abbildung 3.25: Manueller Verkleinerung im Row-Layout (mit Umbruch)
Das Beispiel mit vertikaler Ausrichtung ist schließlich nicht mehr sonderlich spannend:
Abbildung 3.26: Vertikale Anordnung im Row-Layout
Der Programmcode für das Beispiel, mit dessen Hilfe die Screenshots hier angefertigt
wurden, ist nicht komplex (RowLayoutApplication):
...
RowLayout layout = new RowLayout(SWT.HORIZONTAL);
layout.wrap = false; // kein "Zeilenumbruch"
shell.setLayout(layout);
Button button = new Button(shell, SWT.PUSH);
button.setText("1. Taste");
button = new Button(shell, SWT.PUSH);
button.setText("2. lange Taste");
button = new Button(shell, SWT.PUSH);
button.setText("3. ganz lange Taste");
shell.pack();
...
Bei leistungsfähigeren und damit auch komplexeren Layout-Managern ist relativ Programmcode erforderlich, um alle Attribute einzustellen. Daher wurden mit JFace FabrikKlassen für alle Layout-Manager eingeführt, die die Erzeugung und das Einstellen des
Layouts vereinfachen. Für das im Beispiel eingesetzte RowLayout handelt es sich um
die Klasse RowLayoutFactory. Damit wird der Programmcode oben umgeformt:
...
Button button = new Button(shell, SWT.PUSH);
button.setText("1. Taste");
button = new Button(shell, SWT.PUSH);
button.setText("2. lange Taste");
button = new Button(shell, SWT.PUSH);
button.setText("3. ganz lange Taste");
30
3.2 Layout-Management
RowLayoutFactory.swtDefaults().type(SWT.HORIZONTAL)
.wrap(false).applyTo(shell);
pack() wird nicht mehr manuell aufgeufen. Das geschieht automatisch nach Einstellen
des Layouts. Daher muss die Layout-Erzeugung jetzt unbedingt nach Einfügen aller Widgets erfolgen. Eine genaue Erklärung der Fabrikklasse ist in der API-Dokumentation zu
JFace zu finden.
Wie beim FillLayout gibt es auch beim RowLayout einige Attribute, die das Verhalten
steuern. In JFace erlaubt die Klasse RowLayoutFactory hier das einfachere Setzen
dieser Attribute innerhalb der Layout-Angabe. Attribute:
center: Ist das Attribut true, dann werden die Widgets bei unterschiedlichen Breiten bzw. Höhen zueinander zentriert Ansonsten werden sie linksbündig bzw. oben
ausgerichtet.
justify: Ist das Attribut true, dann werden die Widgets wie im Blocksatz angeordnet. Ist es false, dann werden die Widgets am Ursprung platziert.
marginTop: oberer Rand über dem Layout
marginLeft: linker Rand neben dem Layout
marginBottom: unterer Rand unter dem Layout
marginRight: rechter Rand neben dem Layout
pack: Ist das Attribut true, dann verwendet der Layout-Manager die bevorzugten
Größen der Widgets. Ansonsten erzwingt er, dass alle Widgets die Maße der größten Komponente annehmen.
spacing: Abstand zwischen den Widgets
wrap: Ist das Attribut true, dann wird der automatische Zeilenumbruch aktiviert.
Mit diesen Attributen lassen sich bereits viele Eigenschaften des Layouts verändern.
Gerade bei komplexeren Layout-Manager soll der Entwickler aber noch weitergehende
Einflussmöglichkeiten erhalten. Dazu kann jedem Widget eine Layout-Eigenschaft zugeordnet werden. Diese Eigenschaft ist ein Objekt, das zusätzliche Layout-Bedingungen
aufnimmt, die der Layout-Manager berücksichtigen muss. Es existiert für nahezu jeden
Layout-Manager eine eigene Klasse mit Layout-Eigenschaften, weil die Eigenschaften
eng an den Manager gekoppelt sind. Die Eigenschaften werden durch den Aufruf der
Methode setLayoutData (nicht setData!!) am Widget abgelegt.
Am Beispiel des RowLayouts lässt sich gut zeigen, was die zusätzlichen Layout-Eigenschaften bewirken können. Möchte der Entwickler ein Widget etwas größer machen, als
es die bevorzugte Größe vorsieht, dann ist das bisher praktisch kaum möglich. Wird jetzt
aber dem Widget eine Layout-Eigenschaft mit einer fest vorgegeben Größe übergeben,
dann verwendet der Layout-Manager diese. Im RowLayout heißt die Eigenschaftsklasse RowData. Dieser Namensaufbau ist übrigens bei allen Layout-Managern einheitlich:
Statt Layout erhält die Eigenschaftsklasse die Namensendung Data.
Das folgende Beispiel erweitert das bisherige, indem die erste Taste zwangsweise eine
feste Breite vom 200 Pixeln erhält, aber ihre bevorzugte Höhe beibehält (Verwendung
von SWT.DEFAULT für den Standardwert).
31
3.2 Layout-Management
...
Button button = new Button(shell, SWT.PUSH);
button.setText("1. Taste");
RowData layoutData = new RowData(200, SWT.DEFAULT);
button.setLayoutData(layoutData);
...
Abbildung 3.27: Row-Layout mit veränderter Widget-Breite
Wie beim Layout werden auch bei den Eigenschaften die Werte direkt und nicht über
Setter-Methoden manipuliert. Attribute der Klasse RowData:
width: Manuell vergebene Breite des Widgets. Die Angabe von SWT.DEFAULT bewirkt, dass die errechnete bevorzugte Breite des Widgets verwendet wird.
height: Manuell vergebene Höhe des Widgets. Die Angabe von SWT.DEFAULT
bewirkt, dass die errechnete bevorzugte Höhe des Widgets verwendet wird.
exclude: Die Ausgabe des Widgets soll im Layout unterdrückt werden. Es ist somit
unsichtbar und belegt auch keinen Platz.
Manuelle Größenabgaben sollten möglichst sparsam eingesetzt werden, weil ansonsten
spätestens im Zuge der Internationalisierung der Anwendung wieder das Problem auftritt, dass unterschiedlich lange Texte eventuell nicht mehr richtig passen. Für Widgets,
die keinen eigenen Text haben, ist eine manuelle Größenvergabe dagegen manchmal
sinnvoll. So können mehrzeilige Texteingabefelder einen vordefinierten Platz belegen.
Dieser Platz kann nicht vom Widget selbst ermittelt werden, weil es nicht wissen kann,
wieviel Text gleichzeitig sichtbar sein soll.
3.2.5
Grid-Layout
Nach den recht eingeschränkten FillLayout und RowLayout wird mit GridLayout
jetzt ein Layout-Manager vorgestellt, der dem Entwickler deutlich mehr Möglichkeiten in
Bezug auf die Platzierung sowie die Größen der Widgets einräumt. Die Grundidee besteht darin, alle Widgets in einem tabellarischen Raster anzuordnen. In jeder einzelnen
Zelle des Rasters kann sich immer nur ein Widget befinden. Dabei dürfen sich allerdings einzelne Widgets über mehrere Zeilen und Spalten erstrecken. Weiterhin kann der
Layout-Manager durch einstellbare Regeln auch Widgets über ihre bevorzugte Größe
hinaus vergrößern und eine einheitliche Spaltenbreite erzwingen.
32
3.2 Layout-Management
Widget 1
Widget 2
Widget 7
Widgets dürfen sich über
mehrere Zeilen und/oder Spalten
erstrecken.
Widget 3
Zeilen dürfen unterschiedliche Höhen,
Spalte unterschiedliche Breiten
besitzen.
Widget 8
Widget 6
Widget 5
Widget 4
Abbildung 3.28: Anordnung im Grid-Layout
Bevor ein Beispiel betrachtet wird, sollen zunächst die Attribute und somit die Eigenschaften des Layout-Managers vorgestellt werden.
horizontalSpacing: Horizontaler Abstand zwischen den Zellen. Damit ist der
Abstand zwischen den einzelnen Spalten gemeint.
verticalSpacing: Vertikaler Abstand zwischen den Zellen. Dieser beschreibt die
Distanz zwischen den Zeilen.
makeColumnsEqualWidth: Wenn dieses Attribut true ist, dann erzwingt das Layout eine einheitliche Breite aller Spalten. Die Breite des breitesten Widgets im kompletten Layout bestimmt die Breite aller Spalten. Ist der Wert dagegen false, dann
bestimmt in jeder einzelnen Spalte die Breite des darin enthaltenen breitesten Widgets die Gesamtbreite der Spalte. Damit ist noch nicht gesagt, wie sich die schmaleren Widgets verhalten. Normalerweise belegen diese nur einen Teil der ihnen zur
Verfügung stehenden Zelle, indem sie an deren jeweiligen Ursprung (z.B. links oben)
ausgerichtet werden. Dieses ist anhand der stilisierten Abbildung 3.28 ersichtlich.
Es kann aber über die Layout-Eigenschaften einzelner Widgets eingestellt werden,
dass sich die Widgets über ihre bevorzugte Größe hinaus vergrößern und die Zelle
komplett ausfüllen. Dieses Verhalten wird weiter unten näher betrachtet.
marginTop: oberer Rand über dem Layout
marginLeft: linker Rand neben dem Layout
marginBottom: unterer Rand unter dem Layout
marginRight: rechter Rand neben dem Layout
marginHeight: Oberer und unterer Rand um das Layout. Diese Eigenschaft kann
durch das Setzen von marginTop und marginBottom überschrieben werden.
marginWidth: Linker und rechter Rand neben dem Layout. Diese Eigenschaft kann
durch das Setzen von marginLeft und marginRight überschrieben werden.
numColumns: Anzahl der Spalten im Layout. Diese Angabe ist erforderlich, weil die
Widgets ohne die Angabe einer Zelle platziert werden. Statt dessen wird die Reihenfolge, in der die Widgets zu ihrem Vaterelement hinzugefügt werden, verwendet,
um daraus die Position im Layout zu ermittlen: Die Widgets werden, beginnend mit
dem Ursprung, „von links nach rechts“ und „von oben nach unten“ platziert. Immer
wenn die voreingestellte Spaltenzahl erreicht ist, wird eine neue Zeile angefangen.
33
3.2 Layout-Management
Wie für das RowLayout existiert ebenso für das GridLayout eine Fabrikklasse zum
leichteren Erzeugen des Layouts(GridLayoutFactory). Die Mächtigkeit des LayoutManagers ergibt sich aus den vielen Layout-Eigenschaften, die den Widgets übergeben
werden können. Die dafür zuständige Klasse heißt GridData. Dessen wichtigste Attribute sind:
exclude: Die Anzeige des Widgets wird im Layout unterdrückt, so dass dessen
Zelle leer erscheint.
grabExcessHorizontalSpace: Dieses Attribut steuert das Verhalten des Widgets, wenn das Vaterelement (z.B. das Fenster) schmaler oder breiter wird. Ist das
Attribut true, dann schrumpft oder wächst das Widget in der Breite, wenn sich die
Breite des Vaterelementes ändert. So kann z.B. ein Textfeld dem Anwender mehr
Zeichen gleichzeitig darstellen, wenn der Dialog größer wird. Ein Bezeichner („Label“) dagegen würde sich nicht verändern, weil sein kompletter Text ohnehin in der
Regel sichtbar ist.
grabExcessVerticalSpace: Dieses Attribut steuert das Verhalten des Widgets,
wenn das Vaterelement flacher oder höher wird. Ist das Attribut true, so schrumpft
oder wächst das Widget in der Höhe, wenn sich die Höhe des Vaterelementes ändert.
widthHint: Wird dieses Attribut verwendet, dann überschreibt es die bevorzugte
Breite eines Widgets. So lassen sich manuell Größen vorgeben. Wie schon beim
Row-Layout beschrieben, sollte das manuelle Setzen einer Größe nur sparsam und
gezielt eingesetzt werden, weil es ansonsten spätestens bei der Internationalisierung Probleme geben kann.
heightHint: Dieses Attribut überschreibt die bevorzugte Höhe eines Widgets.
horizontalAlignment: Mit diesem Attribut wird gesteuert, wie sich ein Widget in
einer Zelle verhalten soll:
SWT.BEGINNG: Ist das Widget schmaler als die Zelle, dann wird es an den
Anfang der Zelle platziert.
SWT.CENTER: Ist das Widget schmaler als die Zelle, dann wird es innerhalb der
Zelle zentriert.
SWT.END: Ist das Widget schmaler als die Zelle, dann wird es an das Ende der
Zelle platziert.
SWT.FILL: Ist die bevorzugte Breite des Widgets kleiner als die Zellenbreite,
dann wird das Widget auf die Breite der Zelle gestreckt. Dieser Wert zusammen
mit dem Setzen von grabExcessHorizontalSpace bewirkt, dass ein Widget
immer die komplette Zellenbreite belegt, auch wenn das Vaterelement seine
Größe verändert.
verticalAlignment: Mit diesem Attribut wird gesteuert, wie sich ein Widget in
einer Zelle verhalten soll, wenn seine bevorzugte Höhe kleiner als die Zellenhöhe
ist. Die Konstanten sind dieselben wie im vorherigen Auszählungspunkt bei der horizontalen Platzierung mit horizontalAlignment.
34
3.2 Layout-Management
horizontalIndent: Belegt ein Widget nicht die komplette Breite einer Zelle, dann
kann mit diesem Einzug eingestellt werden, um wieviele Pixel es vom Platzierungsursprung entfernt angeordnet werden soll. Bei einer Ausrichtung am Anfang der Zelle würde es um die angegebene Pixelzahl vom Anfang der Zelle entfernt platziert
werden. Bei einer Ausrichtung am Ende der Zelle dagegen würde der Einzug den
Abstand vom Ende der Zelle definieren.
verticalIndent: Wie horizontalIndent, nur auf Höhe des Widgets im Layout
bezogen.
horizontalSpan: Anzahl Spalten, die das Widget im Layout belegen soll
verticalSpan: Anzahl Zeilen, die das Widget im Layout belegen soll
minimumWidth: Manche Widgets sollen im Layout bei einer Größenänderung des
Dialogs eine gewisse Mindestbreite nicht unterschreiten, damit wichtige Informationen immer sichtbar bleiben. Diese Mindestbreite kann hier festgelegt werden.
minimumHeight: Manche Widgets sollen im Layout bei einer Größenänderung des
Dialogs eine gewisse Mindesthöhe nicht unterschreiten, damit wichtige Informationen immer sichtbar bleiben. Diese Mindesthöhe kann hier festgelegt werden.
Aus der langen Aufstellung ist ersichtlich, dass im schlimmsten Fall ziemlich viele Attribute eingestellt werden müssen. Glücklicherweise können die häufigsten Parameter direkt
über die Konstruktoren der Klasse GridData übergeben werden. Näheres dazu ist in
der API-Dokumentation zu finden. Zusätzlich existiert auch für die Klasse GridData mit
GridDataFactory eine Fabrikklasse zum leichteren Befüllen.
Das folgende Beispiel zeigt die Layout-Eigenschaften einer Taste, die sich über die volle
Breite und Höhe einer Zelle erstrecken soll, obwohl sie eigentlich kleiner sein möchte.
Button button = new Button(shell, SWT.PUSH);
button.setText("Ok");
data = new GridData(SWT.FILL, SWT.FILL, true, true);
button.setLayoutData(data);
Die vier Parameter des Konstruktors der Klasse GridData bedeuten:
1. SWT.FILL: Die Taste belegt die komplette Breite der Zelle.
2. SWT.FILL: Die Taste belegt die komplette Höhe der Zelle.
3. true: Die Taste wächst und schrumpft in der Breite, wenn sich die Zellenbreite
ändert.
4. true: Die Taste wächst und schrumpft in der Höhe, wenn sich die Zellenhöhe ändert.
Anhand des folgenden, relativ sinnlosen Dialogs soll gezeigt werden, wie mit Hilfe des
GridLayouts Widgets platziert werden.
Abbildung 3.29: Beispieldialog für das Grid-Layout
35
3.2 Layout-Management
Zunächst wird ein Raster über den Dialog gelegt. Immer dort, wo Widgets aneinander
stoßen, entstehen neue Spalten bzw. Zeilen:
Abbildung 3.30: Rasterhilfslinien auf dem Dialog
Jetzt kann das Layout-Objekt für drei Spalten erzeugt werden. Dabei wird keine identische Spaltenbreite erzwungen. Der folgende Quelltext (GridLayoutApplication)
verzichtet auf den Einsatz der Fabrikklasse:
...
GridLayout layout = new GridLayout(3, false);
shell.setLayout(layout);
Im nächsten Schritt wird das obere Label als Überschrift in den Dialog eingefügt. Es
soll sich über alle drei Spalten erstrecken, weil der Text in anderen Sprachen ja durchaus
länger werden kann. Würde das Label lediglich in Spalte 1 stehen, dann würde die Spalte
durch einen längeren Labeltext breiter werden. Das ist hier aber nicht gewünscht.
Abbildung 3.31: Platzierung des Labels im Grid-Layout
Label label = new Label(shell, SWT.BEGINNING);
label.setText("Titel");
data = new GridData(SWT.FILL,
// Zelle horizontal füllen
SWT.DEFAULT, // bevorzugte Höhe
true,
// horizontal wachsen
false,
// vertikal nicht wachsen
3,
// 3 Spalten
1);
// eine Zeile
label.setLayoutData(data);
Das mehrzeilige, scrollbare Texteingabefeld ist sicherlich das interessanteste Widget im
Beispiel. Es soll sowohl horizontal als auch vertikal die komplette Zelle belegen und mitwachsen. Weiterhin soll dem Widget eine bevorzugte Breite übergeben werden, weil das
Widget selbst eine viel zu kleine annimmt.
36
3.2 Layout-Management
Abbildung 3.32: Platzierung des Textfeldes im Grid-Layout
Text text = new Text(shell, SWT.MULTI | SWT.WRAP | SWT.BORDER
| SWT.H_SCROLL | SWT.V_SCROLL);
data = new GridData(SWT.FILL, // Zelle horizontal füllen
SWT.FILL, // Zelle vertikal füllen
true,
// horizontal wachsen
true,
// vertikal wachsen
1,
// eine Spalte
2);
// zwei Zeilen
data.widthHint = 200;
// bevorzugte Breite 200 Pixel
text.setLayoutData(data);
Abschließend soll noch eine der beiden Tasten betrachtet werden. Zu beachten ist, dass
beide Tasten unabhängig von ihrem Inhalt (ihrer Beschriftung) immer gleich breit sein
sollen. Normalerweise würde der Text die Breite bestimmen. Weiterhin ist im Beispiel
verlangt, dass die Tasten auch horizontal wachsen. Das ist in der Praxis natürlich recht
unsinnig.
Abbildung 3.33: Platzierung einer Taste im Grid-Layout
Button button = new Button(shell, SWT.PUSH);
button.setText("Ok");
data = new GridData(SWT.FILL, // Zelle horizontal füllen
SWT.FILL, // Zelle vertikal füllen
false,
// horizontal nicht wachsen
true);
// vertikal wachsen
button.setLayoutData(data);
Soll die Taste dagegen ihre bevorzugte Breite behalten, dann dürfte sie nicht auf die
Zellenbreite gestreckt werden:
Button button = new Button(shell, SWT.PUSH);
button.setText("Ok");
data = new GridData(SWT.DEFAULT, // BEVORZUGTE BREITE!
SWT.FILL,
// Zelle vertikal füllen
false,
// horizontal nicht wachsen
true);
// vertikal wachsen
button.setLayoutData(data);
37
3.2 Layout-Management
Das Ergebnis zeigt Abbildung 3.34.
Abbildung 3.34: Platzierung einer Taste im Grid-Layout ohne Streckung
3.2.6
Geschachtelte Layout
Die bisher vorgestelten Layout-Manager waren für sich alleine gesehen zwar schon recht
hilfreich, scheitern aber schnell bei komplexen Layouts. Das liegt daran, dass immer jedem Fenster genau ein Layout-Manager zugeordnet war, der das Aussehen des Dialoginhalts selbst festlegen musste. Dieser Ansatz ist allerdings für komplizierte Layouts nicht
flexibel genug, bzw. würde extrem mächtige und damit auch umständlich zu verwendende Layout-Manager benötigen. Eine einfachere Lösung besteht darin, Layouts ineinander
zu schachteln. Dazu existieren Widgets, die selbst wiederum als Container für andere
Widgets dienen. Diesen Containern kann ein eigener Layout-Manager zugewisen werden. Somit ergibt sich eine hierarchische Schachtelung von Layouts. Im folgenden Bild
wird ein Composite-Widget als Container eingesetzt. Das Widget hat bei dieser Verwendung normalerweise keine eigene Darstellung. Es dient hauptsächlich dazu, andere
Widgets aufzunehmen und diese mit Hilfe eines eigenen Layouts darzustellen.
Fenster mit GridLayout
Widget 1
Widget 2
Composite-Widget
mit FillLayout
Abbildung 3.35: Verschachtelung von Containern mit jeweils eigenen Layouts
Das folgende Beispiel zeigt einen Dialog mit zwei nebeneinander angeordneten, identisch breiten Tasten (NestedLayoutsApplication). Für solche Dialoge mit Standardtasten werden später spezielle Fensterklassen vorgestellt, so dass es normalerweise
unnötig ist, die Tasten wie im Beispiel selbst zu platzieren.
NestedLayoutsApplication
Composite-Widget mit FillLayout
(der Rahmen zeigt die Ausmaße
des Composite-Objektes)
Abbildung 3.36: Beispiel für ineinander geschachtelte Layouts
38
3.2 Layout-Management
Der Rahmen um das Composite-Objekt mit den beiden Tasten soll nur die Ausmaße
des Containers verdeutlichen.
// Der Container für die Tasten erhält ein FillLayout.
Composite buttonPanel = new Composite(shell, SWT.NONE);
buttonPanel.setLayout(new FillLayout());
// Der Container streckt sich horizontal über
// zwei Spalten. Er wird im übergeordneten GridLayout
// rechtsbündig (SWT.END) in seine Zelle gesetzt.
// Er soll nicht mit der Zelle mitwachsen.
data = new GridData(SWT.END, SWT.DEFAULT, false, false, 2, 1);
buttonPanel.setLayoutData(data);
// Die Tasten werden zum Composite-Objekt hinzugefügt.
new Button(buttonPanel, SWT.PUSH).setText("Ok");
new Button(buttonPanel, SWT.PUSH).setText("Cancel");
Die Screenshots zeigen das Verhalten des Dialogs bei Größenänderungen unter Windows und Mac OS X.
Abbildung 3.37: Größenänderungen bei ineinander geschachtelten Layouts
Man erkennt, dass die Reihenfolge der Tasten unter Mac OS X nicht den Vorgaben Apples entspricht. Dieses Problem lösen die vordefinierten Dialogklassen, die später betrachtet werden.
3.2.7
Form-Layout
Das FormLayout ist das flexibelste und komplexeste Standardlayout im SWT. Hier werden die Widgets relativ zueinander platziert und an ihren Kanten ausgerichtet.
3.2.7.1
Grundidee
Die Abbildung 3.38 zeigt, wie die Widget-Ränder entweder an den Rändern des VaterContainers (hier das Fenster) oder an den Rändern anderer Widgets ausgerichtet wer39
3.2 Layout-Management
den. Zusätzlich lassen sich auch relative Maße angeben, so dass z.B. ein Widgetrand
immer an 50 % der Fensterbreite ausgerichtet sein soll. Ändert sich die Fensterbreite,
dann „wandert“ das Widget bzw. dessen Rand mit.
Widget 1
Widget 2
Widget 3
Widget 1 wird mit seinem linken und
rechten Rand am Fenster
ausgerichtet.
Widget 3 wird mit seinem rechten
Rand immer am Rand des Fenster
ausgerichtet.
Widget 3 wird mit seinem unteren Rand
am oberen Rand von Widget 5
ausgerichtet.
Widget 4 Widget 5
Widget 5 wird mit seinem unteren
Rand immer am Fenster
ausgerichtet.
Abbildung 3.38: Anordnung im Form-Layout
Das FormLayout ist sehr mächtig, dummerweise aber auch etwas unhandlich in seiner
Anwendung. Zunächst werden die wichtigsten Eigenschaften des Layouts vorgestellt.
spacing: Standardabstand zwischen den einzelnen Widgets in Pixeln
marginTop: Größe des Randes oberhalb des Layouts in Pixeln
marginLeft: Größe des Randes links des Layouts in Pixeln
marginBottom: Größe des Randes unterhalb des Layouts in Pixeln
marginRight: Größe des Randes rechts des Layouts in Pixeln
marginHeight: Größe des Randes oberhalb und unterhalb des Layouts in Pixeln
marginWidth: Größe des Randes links und rechts des Layouts in Pixeln
Auch beim FormLayout werden die eigentlichen Platzierungsangaben für einzelne Widgets über Layout-Eigenschaften der Widgets eingestellt. In diesem Fall ist es die Klasse
FormData. Sie nimmt für die vier Seiten des Widgets die Platzierungsbeziehungen auf.
Jede Beziehung wird wiederum durch ein Objekt der Klasse FormAttachment definiert.
Attribute der Klasse FormData:
top: Ausrichtung der oberen Kante des Widgets
left: Ausrichtung der linken Kante des Widgets
bottom: Ausrichtung der unteren Kante des Widgets
right: Ausrichtung der rechten Kante des Widgets
width: Wenn die bevorzugte Breite des Widgets nicht zum Layout passt, dann kann
hier manuell eine Breite vergeben werden. Das sollte wie bei den anderen Layouts
in der Regel nur dann verwendet werden, wenn das Widget nicht in der Lage ist,
eine sinnvolle Breite selbst zu berechnen (z.B. mehrzeilige Texteingabefelder).
height: Wenn die bevorzugte Höhe des Widgets nicht zum Layout passt, dann
kann hier eine Höhe vorgegeben werden.
40
3.2 Layout-Management
Schließlich fehlt noch die Klasse FormAttachment, die eine Platzierungsregel für eine
Kante enthält. Wichtig ist hierbei, dass es prinzpiell zwei Möglichkeiten der Anordnung
einer Kante gibt: Sie wird an einer Kante eines Zielwidgets im selben Layout oder an
einer Kante des Vater-Containers (z.B. des Fensters) ausgerichtet.
alignment: Kante des Widgets, an dem sich dieses Widget ausrichten soll. Die
möglichen Werte für die eigene obere oder untere Kante sind:
SWT.TOP: Die Platzierungsangabe bezieht sich auf den oberen Rand des Zielwidgets.
SWT.CENTER: Die Platzierungsangabe bezieht sich auf die Mitte des Zielwidgets.
SWT.BOTTOM: Die Platzierungsangabe bezieht sich auf den unteren Rand des
Zielwidgets.
Die möglichen Werte für die eigene linke oder rechte Kante sind:
SWT.LEFT: Die Platzierungsangabe bezieht sich auf den linken Rand des Zielwidgets.
SWT.CENTER: Die Platzierungsangabe bezieht sich auf die Mitte des Zielwidgets.
SWT.RIGHT: Die Platzierungsangabe bezieht sich auf den rechten Rand des
Zielwidgets.
control: Dieses Attribut nimmt die Referenz des Widgets auf, zu dem das eigene
Widget relativ positioniert werden soll. Hat control den Wert null, dann soll die
eigene Widgetseite relativ zum Vater-Container platziert werden.
offset: Absoluter Abstand in Pixeln von der Kante des Zielwidgets
numerator, denominator: Ein Beispiel verdeutlicht die Verwendung dieser beiden zunächst merkwürdig erscheinenden Attribute: Ein Widget soll mit seinem linken Rand immer in der Mitte des Dialogs erscheinen. Somit muss sein linker Rand
am linken Rand des Dialogs ausgerichtet werden. Die Angabe „in der Mitte“ wird
durch den Prozentwert der Breite des Dialogs angegeben, hier also 50 % der Breite.
Die beiden Attribute stellen jetzt Zähler (numerator) und Nenner (denominator,
Standardwert ist 100) des Bruchs, mit dem der Prozentwert abgebildet wird, dar.
Somit ergibt sich die aktuelle Position durch folgende Berechnung:
numerator
pos = denominator
∗ size + of f set.
size ist hierbei in dem Beispiel die automatisch ermittelte Breite des Dialogs.
Damit das Ganze nicht zu abstrakt bleibt, zeigen einige Beispiele in den folgenden Abschnitten die Verwendung der Klassen auf.
3.2.7.2
Beispiel für die relative Anordnung zum Vaterobjekt
Hier soll sich Widget W1 links und rechts am Fenster ausrichten. Sein linker Rand bleibt
also immer am linken Rand des Fenster, sein rechter am rechten Rand des Fenster.
Damit belegt W1 die komplette Fensterbreite.
41
3.2 Layout-Management
FormData data = new FormData();
// Der rechte Rand sitzt bei 100% Breite
// des Fensters (100/100). Der Offset
// ist 0.
data.right = new FormAttachment(100);
W1
// Der linker Rand sitzt bei 0% Breite
// des Fensters (0/100). Der Offset
// ist 0.
data.left = new FormAttachment(0);
w1.setLayoutData(data);
Abbildung 3.39: Beispiel 1 für die Anordnung im Form-Layout
3.2.7.3
Beispiel für die relative Platzierung zu einem anderen Widget
Widget W1 wird soll seinen linken Rand immer relativ zum rechten Rand von W2 platzieren. Der Abstand zwischen beiden beträgt 0 Pixel, was nicht sehr praxisnah ist.
FormData data = new FormData();
// Der rechte Rand schließt direkt an
// den linken Rand von W1 an. Der Abstand
// wird durch das Layout vorgegeben.
data.right = new FormAttachment(w2);
W1 W2
// Der untere Rand sitzt bei 100% Höhe
// des Fensters (100/100). Der Offset
// ist 0.
data.bottom = new FormAttachment(100);
w1.setLayoutData(data);
Abbildung 3.40: Beispiel 2 für die Anordnung im Form-Layout
3.2.7.4
Beispiel für die relative Anordnung zum Vaterobjekt und zu
einem anderen Widget
W1 und W2 belegen jeweils 50 % der Breite des Fensters und haben in der Mitte einen
zusätzlichen Abstand von 4 Pixeln.
42
3.2 Layout-Management
FormData w1Data = new FormData();
FormData w2Data = new FormData();
W1
W2
// Der rechte Rand von W2 liegt am Fenster.
w2Data.right = new FormAttachment(100);
// Der linke Rand von W2 liegt mit 4 Pixeln
// Abstand rechts von W1.
w2Data.left = new FormAttachment(w1, 4);
// Der rechte Rand von W1 liegt bei 50% der
// Breite (minus 2 Pixel).
w1Data.right = new FormAttachment(50, -2);
// Der linke Rand von W1 liegt am Fenster.
w1Data.left = new FormAttachment(0);
w1.setLayoutData(w1Data);
Abbildung 3.41: Beispiel 3 für die Anordnung im Form-Layout
3.2.7.5
Auszurichtende Zielseiten
Es fällt auf, dass nirgendwo die Kanten, auf die sich die Positionierungen beziehen, angegeben sind. alignment wird also in der Klasse FormAttachment niemals beschrieben. Das ist in diesen Beispielen auch nicht notwendig, weil das Form-Layout die Angabe
selbst ermitteln kann.
Beispiel:
W1
W2
Der linke Rand von W2 wird an dem rechten
Rand von W1 ausgerichtet.
Abbildung 3.42: Automatische Randerkennung im Form-Layout
Der rechte Rand von W1 hat den linken von W2 als Nachbarn. Das wird automatisch
erkannt und muss daher nicht angegeben werden. Lediglich in Szenarien, in denen die
Kante des Zielobjektes nicht eindeutig erkennbar ist, muss sie angegeben werden. Das
ist beispielsweise dann der Fall, wenn ein Widget über einem anderen steht und seine
linke oder rechte Kante auf die linke oder rechte Kante des Ziels ausgerichtet werden
muss. Dann kann das Form-Layout nicht entscheiden, welche Kante des Ziels gemeint
ist. Beispiel:
W1
W2
Soll der linke Rand von W2 an dem rechten oder linken
Rand von W1 ausgerichtet werden?
Abbildung 3.43: Keine automatische Randerkennung im Form-Layout
43
3.2 Layout-Management
3.2.7.6
Durchgängiges Beispiel
Ein vollständiges Beispiel (FormLayoutApplication) schließt die Betrachtung des
Form-Layouts ab. Viele der vorher vorgestellten Attribute lassen sich vergleichsweise
bequem über die Kontruktoren setzen. Als Basis dient dieser Screenshot:
Abbildung 3.44: Screenshot des Beispiels
Die Rahmen um das Widget für die Titelzeile sowie das Bild dienen nur dazu, deren
Ausmaße zu erkennen. Auch für dieses Beispiel gilt, dass es bereits vordefinierte Dialoge
mit den Standardtasten gibt und dass diese der manuellen Programmierung vorzuziehen
sind. Im Layout erhalten alle Widgets einen Abstand untereinander von 4 Pixeln. Das ist
auch der Abstand zum Fensterrand.
FormLayout layout = new FormLayout();
layout.marginHeight = 4; // oberer und unterer Rand
layout.marginWidth = 4; // linker und rechter Rand
layout.spacing
= 4; // Abstände zwischen den Komponenten
shell.setLayout(layout);
Im nächsten Schritt wird die Titelzeile eingefügt. Sie ist ein Label, das sich immer über
die komplette Fensterbreite erstrecken soll und oben am Fenster platziert ist. Der untere
Rand wird nicht festgelegt. Seine Position ergibt sich aus der des oberen zuzüglich der
bevorzugten Höhe.
Abbildung 3.45: Einfügen des Titels
Label label = new Label(shell, SWT.BEGINNING | SWT.BORDER);
label.setText("Titel");
FormData data = new FormData();
// linker Rand bei 0% Fensterbreite
data.left = new FormAttachment(0);
// rechter Rand bei 100% Fensterbreite
data.right = new FormAttachment(100);
label.setLayoutData(data);
44
3.2 Layout-Management
Jetzt wird die Taste für den Abbruch („Cancel“) in den Dialog eingefügt. Sie sitzt immer
am unteren rechten Rand des Dialogs. Die Positonen ihres oberen und linken Randes
ergeben sich dann aus der bevorzugten Größe.
Abbildung 3.46: Einfügen der Abbruch-Taste
Button cancelButton = new Button(shell, SWT.PUSH);
cancelButton.setText("Cancel");
data = new FormData();
// rechter Rand bei 100% Fensterbreite
data.right = new FormAttachment(100);
// unterer Rand bei 100% Fensterhöhe
data.bottom = new FormAttachment(100);
cancelButton.setLayoutData(data);
Jetzt kann die Ok-Taste links neben die Abbruch-Taste gesetzt werden. Ihr rechter Rand
positioniert sich relativ zum linken Rand der Abbruch-Taste. Die Abstände zwischen den
Tasten ergeben sich aus dem spacing-Wert des Layouts. Sollen beide Tasten eine identische Breite aufweisen, dann müsste in das Form-Layout ein Fill-Layout eingefügt werden, das beide Tasten aufnimmt.
Abbildung 3.47: Einfügen der Ok-Taste
Button okButton = new Button(shell, SWT.PUSH);
okButton.setText("Ok");
data = new FormData();
// rechten Rand an der Cancel-Taste ausrichten
data.right = new FormAttachment(cancelButton);
// unterer Rand bei 100% Fensterhöhe
data.bottom = new FormAttachment(100);
okButton.setLayoutData(data);
Das Bild am linken Rand soll in der Höhe, aber nicht in der Breite wachsen. Es wird daher
mit seinem linken Rand am Fenster, mit seinem oberen am Titel und mit seinem unteren
an der Abbruch-Taste befestigt. Nur durch die Anbindung an die Taste wächst das Bild in
45
3.2 Layout-Management
der Höhe mit, weil die Taste ja bei einer Dialogvergrößerung „nach unten wandert“ und
ihre Größe beibehält.
Abbildung 3.48: Einfügen des Bildes
Label dukeLabel = new Label(shell, SWT.CENTER | SWT.BORDER);
dukeLabel.setImage(imageRegistry.get("duke"));
data = new FormData();
// linker Rand bei 0% Fensterbreite
data.left
= new FormAttachment(0);
// oberen Rand am Titel-Label ausrichten
data.top
= new FormAttachment(label);
// unteren Rand an der Cancel-Taste ausrichten
data.bottom = new FormAttachment(cancelButton);
dukeLabel.setLayoutData(data);
Abschließend fehlt noch das mehrzeilige Texteingabefeld mit seinen Scrollbalken. Es soll
sowohl in der Höhe als auch in der Breite mitwachsen. Dazu werden sein linker Rand am
Bild, sein oberer Rand am Titel, sein rechter Rand am Fensterrahmen und sein unterer
Rand an der Abbruch-Taste ausgerichtet. Durch die beiden zuletzt genannten Bedingungen kann der Text bei Größenänderung des Dialogs mitwachsen oder auch schrumpfen.
Abbildung 3.49: Einfügen des Textfeldes
Text text = new Text(shell, SWT.MULTI | SWT.WRAP | SWT.BORDER
| SWT.H_SCROLL | SWT.V_SCROLL);
data = new FormData();
// linken Rand am Duke-Label (Bild) ausrichten
data.left
= new FormAttachment(dukeLabel);
// oberen Rand am Titel-Label ausrichten
data.top
= new FormAttachment(label);
// rechter Rand ist bei 100% Fensterbreite
data.right = new FormAttachment(100);
// unteren Rand an der Cancel-Taste ausrichten
data.bottom = new FormAttachment(cancelButton);
text.setLayoutData(data);
46
3.2 Layout-Management
3.2.8
Form-Layout (JGoodies)
Das Form-Layout erlaubt zwar eine sehr flexible Gestaltung von Dialogen, hat aber die
Mängel, dass einerseits eine große Menge Code geschrieben werden muss und andererseits das Vorgehen häufig als wenig intuitiv empfunden wird. Der zweite Punkt rührt
daher, dass viele Entwickler ein Raster auf einen Dialogentwurf legen und so in Zeilen
und Spalten denken. Genau das wird beim Form-Layout aber nicht direkt sichtbar. Das
FormLayout von JGoodies schafft hier auf eine recht komfortable Art und Weise Abhilfe. An dieser Stelle soll keine komplette Einführung in das FormLayout erfolgen. Unter
der URL http://ffxml.net/swtforms.html lässt sich die SWT-Portierung der eigentlich für Swing geschriebenen Klassenbibliothek mit einer sehr guten Dokumentation
kostenlos herunterladen.
3.2.8.1
Grundidee
Im Form-Layout werden die einzelnen Komponenten tabellarisch angeordnet. Im Gegensatz zum Form-Layout aus SWT lassen sich die Struktur der Tabelle und das Verhalten
der einzelnen Zeilen und Spalten vorab durch einen String festlegen. Dadurch fallen die
einzelnen Ausrichtungen beim Einfügen von Komponenten weg. Weiterhin erlaubt das
Form-Layout auch Größenangaben in Form so genannter „Dialog Units“. Werden jetzt
Abstände nicht pixel-genau sondern durch Dialog Units spezifiziert, dann passen sich
die Abstände automatisch an, wenn beispielsweise die Zeichensatzgröße im Dialog oder
die Pixelgröße des Bildschirm verändert werden. Durch eine Pixelangabe dagegen wären die Abstände bei vergrößerten Zeichensätzen dann zu klein.
Die Abbildung 3.50 zeigt einen Beispieldialog mit dem zugrunde liegenden tabellarischen
Raster. Wichtig ist hier, dass auch die Abstände zwischen den Komponenten als einzelne
Zeilen und Spalten beschrieben werden.
FormLayout layout = new FormLayout(
"4dlu, left:pref, 4dlu, fill:max(80dlu;pref):grow, 4dlu, pref, 4dlu",
"4dlu, pref, 4dlu, fill:pref:grow, 4dlu, fill:pref:grow", 4dlu);
Zeilen
fill:max(80dlu;pref):grow
Abbildung 3.50: Dialog mit Form-Layout
Die Definition der Zeilen und Spalten wird in Form zweier Strings an den Layout-Manager
übergeben. Der erste String beinhaltet die Spaltenmaße:
1. Die erste Spalte ist lediglich ein Abstandshalter zum linken Rand mit einer Breite
von vier Dialog-Einheiten („Dialog Units“).
2. Die Spalte mit dem „Duke“ soll in der bevorzugten Breite (pref) linksbündig (left)
gesetzt werden. Die linksbündige Angabe ist eigentlich überflüssig, da sich in dieser
Spalte nur eine Komponente befindet.
3. Jetzt folgt wieder ein Platzhalter von vier Dialog-Einheiten.
47
3.2 Layout-Management
4. Die vierte Spalte ist etwas komplizierter definiert. Sie soll beim Start ihre bevorzugte Breite (pref) bzw. mindestens 80 Dialog-Einheiten einnehmen. Die Angabe
(80dlu,pref) beinhaltet ein Intervall mit einer Untergrenze von 80 Dialogeinheiten und einer Obergrenze, der bevorzugten Breite. Die Spalte kann zusätzlichen
Platz belegen (fill) und sie darf, wenn der Dialog größer wird, auch in die Höhe
wachsen (grow).
5. Dann folgt der Platzhalter, der einen festen Abstand zu den beiden Tasten erzwingt.
6. Die Tasten sollen in ihren bevorzugten Breiten angeordnet werden.
7. Abschließend kommt ein Platzhalter, der den Abstand zum rechten Rand definiert.
Nach den Spalten fehlt noch die Festlegung der Zeilen:
1. Die erste Zeile ist ein Abstandshalter zum oberen Rand mit einer Höhe von vier
Dialog-Einheiten.
2. Die Spalte mit der Überschrift soll in der bevorzugten Höhe der Komponenten gesetzt werden. Hier bestimmt nur die bevorzugte Höhe des Labels „Titel“ den Platz.
3. Jetzt folgt wieder ein Platzhalter von vier Dialog-Einheiten.
4. Die vierte Zeile besitzt drei Angaben. Sie soll beim Start ihre bevorzugte Höhe einnehmen (pref), sie kann zusätzlichen Platz belegen (fill) und sie darf, wenn der
Dialog größer wird, auch in die Höhe wachsen (grow).
5. Dann folgt der Platzhalter, der einen festen Abstand zwischen den beiden Tasten
erzwingt.
6. Diese Zeile verhält sich genau wie die vierte.
7. Abgeschlossen wird die Beschreibung durch einen Platzhalter, der den Abstand zum
unteren Rand definiert.
3.2.8.2
Einfügen von Komponenten im Beispiel
Nachdem das Grundgerüst des Dialogs steht, lassen sich jetzt sehr einfach die Komponenten einfügen. In der Regel ist es ausreichend, ihre Position im Raster sowie die
Anzahl Zeilen und Spalten, über die sich sich erstrecken, anzugeben. Dabei ist zu beachten, dass die Zählung der Zeilen und Spalten bei 1 beginnt! Der PanelBuilder hilft
beim Einfügen von Widgets in den Vater-Container. Dazu werden ihm Bedingungen in
Form der Klasse CellConstraints übergeben. Diese bestehen in der Regel aus der
X- und Y-Position der Zelle, in der das Widget platziert sein soll. Zusätzlich lassen sich
auch die Anzahl Spalten und Zeilen angeben, die ein Widget belegen soll. Fehlt diese
Angabe, dann belegt das Widget genau eine Zelle.
// Der Panel-Builder hilft beim Einfügen von Komponenten
// in das Layout. Er bekommt als zweiten Parameter
// den Container übergeben, in den die Widgets
// eingefügt werden.
PanelBuilder builder = new PanelBuilder(layout, shell);
// Die Bedingungen werden später durch Aufruf von
// Methoden übergeben. Das Objekt kann
// wiederverwendet werden.
CellConstraints cc = new CellConstraints();
48
3.2 Layout-Management
Die Titelzeile erstreckt sich über eine Zeile und fünf Spalten, wobei die Platzhalterspalten
mitgezählt werden.
Abbildung 3.51: Einfügen des Titels
// Der Text über allen Komponenten befindet sich in
// Zelle (2,2) und erstreckt sich über 5 Spalten
// und eine Zeile.
Label label = new Label(shell, SWT.BEGINNING);
label.setText("Titel");
builder.add(label, cc.xywh(2, 2, 5, 1));
Das mehrzeilige Texteingabefeld erstreckt sich über eine Spalte und drei Zeilen.
Abbildung 3.52: Einfügen des Textfeldes
Text text = new Text(shell, SWT.MULTI | SWT.WRAP
| SWT.BORDER | SWT.H_SCROLL
| SWT.V_SCROLL);
builder.add(text, cc.xywh(4, 4, 1, 3));
Als letztes Widget in diesem Beispiel soll noch die Ok-Taste eingefügt werden. Sie belegt
nur eine einzige Zelle.
Abbildung 3.53: Einfügen der Ok-Taste
Button button = new Button(shell, SWT.PUSH);
button.setText("Ok");
builder.add(button, cc.xy(6, 4));
Das Form-Layout von JGoodies unterstützt auch identische Spaltenbreiten und Zeilenhöhen (siehe API-Dokumentation).
49
3.3 SWT-Widgets
3.2.9
GUI-Designer
Es gibt Entwickler, denen die manuelle Programmierung von Layouts wenig Spaß bereitet. Daher bietet es sich in der Praxis an, die Oberfläche mit einen GUI-Editor interaktiv
zu erzeugen. Unterstützung bieten unter Anderem:
WindowBuilder Pro für Eclipse (kostenlos unter
http://code.google.com/webtoolkit/tools/wbpro/index.html)
Visual Editor Plugin für Eclipse (noch etwas „unfertig“, unterstützt nicht immer die
aktuellsten Eclipse-Versionen, http://www.eclipse.org/vep/)
Jigloo-Plugin für Eclipse (www.cloudgarden.com/jigloo/, kostenlos für den
nicht-kommerziellen Einsatz)
Hier können die Komponenten visuell den Zellen zugeordnet werden. Ein eigener Editor erlaubt die Änderung von Attributen einer Komponente oder eines Constraints. Der
Nachteil des Ansatzes besteht darin, dass die mit einer IDE interaktiv erstellten Oberflächen sich nicht immer in einer anderen IDE interaktiv verändern lassen, da GUI-Editoren
nicht zwangsweise dieselben Techniken verwenden, um die erzeugten Oberflächen so
abzuspeichern, dass sie bearbeitbar bleiben. Hier hilft nach einem IDE-Wechsel gelegentlich nur noch der Weg der manuellen Programmierung. Ein GUI-Designer entbindet
den Entwickler aber nicht davon, Layout-Manager und deren Verhalten zu kennen. Auch
ein GUI-Designer verwendet für die Platzierung Layouts.
3.3 SWT-Widgets
In diesem Kapitel werden einige wichtige Widgets aus SWT vorgestellt. Das Kapitel 3.7
stellt anhand eines Widgets aus JFace vor, wie es das entsprechende Gegenstück aus
SWT kapselt und ihm einen sogenannten Model-View-Controller-Ansatz (MVC) hinzufügt.
Sie finden an dieser Stelle keine Erklärung der kompletten SWT-API. Diese würde sicherlich mehrere Bücher füllen. Statt dessen erhalten Sie eine Übersicht über die wichtigsten
Komponenten, die zum Bau einer Oberfläche benötigt werden. Diese Komponenten sind
aber nicht unabhängig voneinander. Sie stehen vielmehr über eine Vererbungshierarchie
mit gemeinsamen Basisklassen in Verbindung (siehe Abschnitt 3.5).
Die folgende Aufstellung ist nicht vollständig. Sie soll Ihnen lediglich eine erste Orientierung zur Auswahl und zur Suche in der SWT-API geben. Sehen Sie sich einfach
einmal die Übersicht über wichtige Widgets unter http://www.eclipse.org/swt/
widgets/ an. Neben den Widgets aus dem Paket org.eclipse.swt existieren auch
weitere im Paket org.eclipse.swt.custom. Diese haben ähnliche Namen wie die
aus org.eclipse.swt, besitzen aber teilweise ein erweitertes Verhalten oder wurden
in ihrer Darstellung so angepasst, dass sie optimal in Tabellen eingefügt werden können.
50
3.3 SWT-Widgets
Tabelle 3.1: Wichtige Widgets
Name
Verhalten und Verwendung
Browser
Der Browser ist ein Widget, das einen im Betriebssystem installierten
Browser kapselt.
Button
(SWT.PUSH)
Ein Button, der mit der Konstanten SWT.PUSH erzeugt wurde, ist eine „normale“ Taste. Sie kann gedrückt werden, wobei der Selektionszustand nicht
erhalten bleibt. Sie dient dazu, Aktionen anzustoßen, indem Ereignisse ausgelöst werden.
Button
(SWT.FLAT)
Die Taste verhält sich wie die mit der Konstanten SWT.PUSH erzeugte. Sie
weist aber eine andere (flachere) Rahmendarstellung auf.
Button
(SWT.ARROW |
SWT.LEFT)
Die Taste verhält sich wie die mit der Konstanten SWT.PUSH erzeugte. Anhand der zusätzlich übergebenen Konstante erzeugt sie eine Pfeildarstellung. Erlaubt sind SWT.LEFT, SWT.RIGHT, SWT.UP und SWT.DOWN.
Button
(SWT.CHECK)
Es handelt sich um eine Taste, die selektiert und deselektiert werden kann.
Der Selektionszustand bleibt bis zur Änderung erhalten. Die Komponente
dient dazu, eine Möglichkeit an- oder abzuwählen, ohne dass dadurch die
Belegung einer anderen Komponente direkt beeinflusst wird. Beispiel: Fettund Kursivschrift können unabhängig von einander bestimmt werden.
Button
(SWT.RADIO)
Ein Button, der mit der Konstanten SWT.RADIO erzeugt wurde, ist eine
Taste, die sich an- oder abwählen lässt. Befinden sich andere solcher Tasten im Layout in ihrer Nähe, dann bilden diese eine Gruppe, von der immer
nur eine Taste zu einem Zeitpunkt selektiert sein kann. Dadurch ist sichergestellt, dass bei Auswahl einer Belegung keine andere in der Gruppe möglich
ist. Beispiel: Die Auswahl einer Textausrichtung für links- und rechtsbündig
ist nicht gleichzeitig sinnvoll.
Button
(SWT.TOGGLE)
Diese ist eine Taste, die wie eine Taste mit der Konstanten SWT.CHECK eingesetzt werden kann. Lediglich die Darstellung unterscheidet sich. Diese
Taste wird sehr häufig mit einem eigenen Icon verwendet. Beispiel: Auswahl
einer Textausrichtung
Combo
(SWT.DROP_DOWN),
CCombo
Eine Combobox zeigt einen Eintrag aus einer Liste möglicher Einträge an.
Der Eintrag kann frei gewählt werden. Je nach Einstellung der Combobox ist
sogar die manuelle Eingabe eines Wertes möglich. Die Liste ist normalerweise verborgen und wird erst durch Interaktion mit dem Benutzer sichtbar.
Die Klasse CCombo ist eine Combobox, die für die Darstellung in einer Tabellenzelle angepasst wurde.
Combo
(SWT.SIMPLE)
Wie eine Combobox mit der Konstanten SWT.DROP_DOWN erlaubt sie die
Selektion aus bestimmten Vorgaben. Hinzu kommt aber noch, dass eine
Mehrfachauswahl erlaubt werden kann. Weiterhin ist die maximale Anzahl
gleichzeitig sichtbarer Vorgaben einstellbar. Die manuelle Eingabe neuer
Werte ist allerdings nicht möglich.
Date
(SWT.DATE)
Das Datumswidget erlaubt nur die Eingabe korrekter Datumswerte. Dieses
kann entweder durch eine manuelle Eingabe oder durch Drücken der Tasten
neben dem Feld erfolgen. Dieses Widget ist daher für die Datumseingabe
einem einfachen Textfeld vorzuziehen.
51
3.3 SWT-Widgets
Name
Verhalten und Verwendung
Date
(SWT.TIME)
Das Datumswidget erlaubt durch Setzen der Konstanten SWT.TIME statt einer Datums- eine korrekten Zeiteingabe. Dieses kann entweder durch eine
manuelle Eingabe oder durch Drücken der Tasten neben dem Feld erfolgen. Dieses Widget ist daher für die Zeiteingabe einem einfachen Textfeld
vorzuziehen.
Date
(SWT.CALENDAR)
Das Datumswidget erlaubt durch Setzen der Konstanten SWT.CALENDAR
die komfortable Auswahl eines Datums aus einem Kalender.
Group
Eine Gruppe ist ein Container-Objekt, das für Radio-Buttons verwendet wird.
Es gruppiert diese logisch und optisch. Es kann immer nur eine Taste dieser
Gruppe zu einem Zeitpunkt selektiert sein.
Label, CLabel
Das Label dient als Erklärung mit einer textuellen Bezeichnung bzw. einem
Bild für eine andere Komponente. Es erlaubt keine direkte Eingabe von Werten. Die Klasse CLabel erweitert das normale Label um eine gleichzeitige
Darstellung von Bild und Text. Es bietet einen Farbverlauf im Hintergrund,
ein Hintergrundbild sowie automatische Textkürzungen, falls der Text zu lang
sein sollte.
Link
Der Link zeigt einen Text an, innerhalb dessen der auswählbare Teil mit
<A>Text</A> markiert ist. So kann z.B. innerhalb eines Textes in einem
Dialog ein Verweis auf die zugehörige Hilfe eingebaut werden.
List
Eine List funktioniert ähnlich wie ein Combo-Widget mit der Konstanten
SWT.SIMPLE.
ProgressBar
SWT.SMOOTH
Der Fortschrittsbalken wird mit dieser Konstanten verwendet, um den aktuellen Stand einer eine länger laufenden Operation für eine bekannte Anzahl
von Schritten bis zum Ende anzuzeigen.
ProgressBar
SWT.INDETERMINATE
Der Fortschrittsbalken wird der Konstanten SWT.INDETERMINATE eingesetzt, um den aktuellen Stand einer eine länger laufenden Operation für
eine unbekannte Anzahl an Schritten bis zum Ende anzuzeigen.
Scale
Der Schieberegler erlaubt die Auswahl eines ganzzahligen Wertes aus einem vorgegebenen Bereich. Der Regler kann sowohl horizontal als auch
vertikal angeordnet werden und an vorgegebenen Markierungen einrasten.
ScrollBar
Scrollbalken lassen sich vertikal und horizontal ausrichten. Normalerweise
werden sie nicht direkt erzeugt, sondern durch Angabe einer entsprechenden Konstante beim Erzeugen eines Widgets, dass das Scrollen unterstützt.
Bisher wurde in diesem Skript immer ein mehrzeiliges Texteingabefeld verwendet, dessen Inhalt gescrollt werden soll, wenn er zu groß für das Widget
ist. Die Scrollbalken können mit Hilfe der Methoden getVerticalBar()
bzw. getHorizontalBar() ausgelesen werden.
Slider
Der Schieberegler erlaubt die Auswahl eines ganzzahligen Wertes aus einem vorgegebenen Bereich. Der Regler kann sowohl horizontal als auch
vertikal angeordnet werden und an vorgegebenen Markierungen einrasten.
52
3.3 SWT-Widgets
Name
Verhalten und Verwendung
Spinner
Mit Hilfe dieses Widgets lassen sich ganzzahlige Werte bequem einstellen.
Es können eine Obergrenze sowie die Schrittweite der Erhöhungen vergeben werden.
StyledText
Dieses Textfeld ist Text sehr ähnlich. Allerdings erlaubt es variable Textattribute. Ein Beispiel dazu ist in Abschnitt 3.7.3 zu finden.
Table
Die Table erlaubt die tabellarische Darstellung und Eingabe von Daten.
Die Art der Darstellung kann vom Entwickler beeinflusst werden. Einzelne
Zellen, Spalten, Zeilen oder Bereiche lassen sich auswählen. Die Abschnitte 3.7.1 und 3.7.2 behandelen die Konzepte (ohne und mit MVC-Ansatz)
genauer.
Text
Es handelt sich hierbei um ein einzeiliges Freitexteingabefeld mit Cursorsteuerung und Selektion. Variable Textattribute werden nicht unterstützt. Somit haben alle Zeichen dieselbe Größe, Farbe und einen identischen Zeichensatz. Das Textfeld wird auch für Passworteingaben verwendet. Dazu
muss die Konstante SWT.PASSWORD übergeben werden.
Tree
Ein Tree erlaubt die baumartige Darstellung mit ein- und ausklappbaren
Knoten. Die Art der Darstellung kann ähnlich wie bei der Tabelle vom Entwickler beeinflusst werden.
Daneben besitzt SWT eine ganze Anzahl weiterer Widgets. So wurde das Composite
als Container für andere Widgets bereits vorgestellt. Weitere Container werden später
im Abschnitt 3.8 im Skript betrachtet. Interessant ist auch das Canvas-Widget. Innerhalb
seiner Fläche kann der Entwickler durch Zeichenoperationen den Inhalt komplett frei
gestalten. Weitere Widgets, die bisher nicht Bestandteil von SWT sind, befinden sich im
Eclipse-Projekt Nebula (http://www.eclipse.org/nebula/).
Abbildung 3.54: Einige Widgets aus dem Nebula-Projekt
Aber auch außerhalb der Eclipse-Seiten gibt es viele Widgets, die zum Teil kostenlos verfügbar sind. Ein Beispiel dafür ist die „RCPToolbox“ (http://www.richclientgui.
com/detail.php?product_id=1). Sie besitzt unter Anderem ein Widget zur Anzeige
von Karten aus „Google Maps“.
53
3.4 Menüs und Toolbar-Leisten
Abbildung 3.55: Einige Widgets aus der RCPToolbox
3.4 Menüs und Toolbar-Leisten
Diese Kapitel zeigt die Verwendung von Menüs und Toolbar-Leisten mit reinem SWT.
Später in Abschnitt 3.6.3 werden Sie sehen, dass unter Verwendung von JFace einige
Schritte deutlich einfacher umgesetzt werden können.
3.4.1
Normale Menüs
Menüs setzen sich aus einer Anzahl unterschiedlicher Komponenten zusammen, die im
Folgenden dargestellt werden:
Mnemonic
MenuItem
(SWT.CASCADE)
Menu (SWT.BAR)
Accelerator
MenuItem
(SWT.NONE)
Menu
MenuItem
(SWT.CHECK)
Abbildung 3.56: Komponenten in einem Menü
54
3.4 Menüs und Toolbar-Leisten
In Menüs dürfen auch Radio-Tasten dargestellt werden:
T.CHECK)
MenuItem
(SWT.RADIO)
MenuItem
(SWT.SEPARATOR)
Abbildung 3.57: Radio-Tasten in einem Menü
Bei Menüs werden ebenso wie bei den bisher vorgestellten Widgets Konstanten verwendet, um eigentlich unterschiedliche Klassen anzusprechen.
Menu (SWT.BAR): Das ist die Menüleiste selbst, die innerhalb eines Fensters platziert wird. Die Menüleiste nimmt die einzelnen Menüs auf.
Menu (ohne Konstante): Ein solches Menü ist ein einzelnes Menü in der Menüleiste
oder ein Untermenü in einem anderen Menü.
MenuItem (SWT.CASCADE): Hierbei handelt es sich um einen Eintrag in einem
Menü oder in der Menüleiste, der ein Untermenü enthält.
MenuItem (SWT.NONE): Dieser Eintrag verhält sich wie eine Taste. Wenn er ausgewählt wird, löst er genau dieselben Ereignisse wie eine Taste aus. Es handelt sich
also um einen „normalen“ Menüeintrag.
MenuItem (SWT.CHECK): Hierbei handelt es sich um eine Checkbox, die dem Aussehen und Verhalten der Menüeinträge angepasst wurde. Ein solcher Eintrag kann
also gewählt und auch wieder abgewählt werden.
MenuItem (SWT.RADIO): Dieses Element entspricht einem Button mit der Konstanten SWT.RADIO, dessen Aussehen dem der Menüeinträge angepasst wurde.
So können einzelne Auswahlen (z.B. von Optionen) direkt im Menü angezeigt werden. Um eine Selektion aus einer Anzahl solcher Tasten zu ermöglichen, werden
Radio-Tasten zu Gruppen zusammengefasst. Dieses geschieht automatisch anhand
der Einträge vor und nach den Radio-Tasten. Alle zusammengehörig platzierten
Radio-Tasten bilden eine Gruppe, in der immer nur ein Eintrag ausgewählt sein
kann.
MenuItem (SWT.SEPARATOR): Dieser Eintrag ist ein optisches Trennelement zwischen zwei Menüeinträgen. So lassen sich logisch unterschiedliche Gruppen von
Einträgen optisch trennen.
Accelerator: Menüs sollen nicht nur mit der Maus, sondern auch über die Tastatur
bedient werden können. Durch einen Accelerator kann ein Menüpunkt aufgerufen
werden, ohne dass das zugehörige Menü überhaupt sichtbar ist (z.B. F2, Ctrl-C).
Der Accelerator wird normalerweise rechts innerhalb des Menüeintrags dargestellt.
Mnemonic: Mittels Mnemonics kann durch die Tastatur innerhalb des Menüs navigiert werden. Ein Mnemonic ist ein einzelnes Zeichen, das in der Regel durch einen
unterstrichenen Buchstaben im zugehörigen Text dargestellt wird. Die Navigation
erfolgt plattformabhängig (z.B. mit ALT-C unter Windows). Weil Apple die Verwendung von Mnemonics in seinen eigenen Richtlinien nicht empfiehlt, werden diese
unter Mac OS X gar nicht erst dargestellt.
55
3.4 Menüs und Toolbar-Leisten
3.4.1.1
Bau von Menüstrukturen
Der folgenden Schritte zeigen die Konstruktion eines Menüs, das aus mehreren Einträge
besteht (Quelltext MenuApplication.java). Die Tastatursteuerung wird erst später im
Abschnitt 3.4.1.6 ergänzt.
1. Zuerst wird eine Menüleiste erzeugt und am Fenster registriert. Dazu wird der Leiste
das Fenster als Vater-Element übergeben.
Menu menubar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menubar);
2. Jetzt wird der erste Eintrag in der Menüleiste erzeugt. An diesen Eintrag soll im
Anschluss daran ein Menü angehängt werden.
MenuItem fileItem = new MenuItem(menubar, SWT.CASCADE);
fileItem.setText("File");
3. Das Menü in Form eines Untermenüs kann an den erzeugten Menüeintrag angehängt werden. Das Untermenü hat den Eintrag, über den es aufgeklappt wird, als
Vaterelement.
Menu fileMenu = new Menu(menubar); // Drop-Down-Menue
fileItem.setMenu(fileMenu);
4. Das neue Untermenü wird mit zwei Einträgen („New“ und „Open“) gefüllt.
MenuItem item = new MenuItem(fileMenu, SWT.NONE);
item.setText("New");
item = new MenuItem(fileMenu, SWT.NONE);
item.setText("Open");
Das ist das Ergebnis der Konstruktion, wobei die Schritte oben nicht ganz vollständig
sind. Es werden nicht alls Menü-Einträge erzeugt. Außerdem fehlt noch die Tastatursteuerung.
Abbildung 3.58: Ausgabe des ersten Menü-Beispiels
Dürfen gewisse Einträge zu bestimmten Zeitpunkte nicht verwendet werden, dann bietet
es sich an, diese mit setEnabled(false) einfach zu sperren.
3.4.1.2
Checkbox als Menü-Eintrag
Menüs dürfen Tasten enthalten, die ihren Selektionszustand speichern. Diese entsprechen den Button-Widgets mit der Konstanten SWT.CHECK.
MenuItem checkItem = new MenuItem(fileMenu, SWT.CHECK);
checkItem.setText("Check");
checkItem.setSelection(true); // vorausgewählt
56
3.4 Menüs und Toolbar-Leisten
Das ist das Ergebnis, nachdem den Schritten aus Abschnitt 3.4.1.1 der Checkbox-Eintrag
hinzugefügt wurde:
Abbildung 3.59: Hinzufügen der Checkbox zum Menü
3.4.1.3
Radio-Taste als Menü-Eintrag
Darf immer nur eine Selektion aus einer Gruppe von Einträgen aktiv sein, dann sollten
Radio-Tasten verwendet werden. Diese entsprechen den Button-Widgets mit der Konstanten SWT.RADIO.
MenuItem redItem = new MenuItem(editMenu, SWT.RADIO);
redItem.setText("Red");
redItem.setSelection(true);
// vorausgewählt
Hier sehen Sie die Ausgabe. Der Trennstrich wird im folgenden Beispiel hinzugefügt.
Abbildung 3.60: Hinzufügen der Radio-Taste zum Menü
3.4.1.4
Trennstrich zwischen Menü-Einträgen
Der letzte Code-Ausschnitt zeigt die Erzeugung eines Trennstriches. Das Ergebnis war
bereits in Abbildung 3.60 zu sehen.
new MenuItem(editMenu, SWT.SEPARATOR);
3.4.1.5
Weitere Menü-Eigenschaften
Menüs dürfen neben dem Text auch Bilder besitzen. Dazu besitzen Einträge die Methode
setImage.
3.4.1.6
Tastatursteuerung
Um eine bequeme Bedienung der Menüfunktionen ohne Maus erzielen zu können, müssen die sogenannten Accelerator an den Menüeinträgen registriert werden. Dazu zeigt
die folgende Abbildung noch einmal die in wichtigsten Begriffe für die Tastatursteuerung.
57
3.4 Menüs und Toolbar-Leisten
keine Mnemonics
bei Mac OS X (von
Apple nicht empfohlen)
Accelerator
Mnemonic
Abbildung 3.61: Begriffe zur Tastatursteuerung in Menüs
Erklärung der beiden Begriffe:
Mnemonic: Die direkte Bedienung des Menüs erfolgt unter Zuhilfenahme von Mnemonics. Ein Mnemonic steht für einen Buchstaben aus dem Text des Menüeintrags.
Dieses Zeichen wird einfach dadurch festgelegt, dass vor dem entsprechenden
Buchstaben im Text ein &-Zeichen gesetzt wird. Dieses wird von SWT nicht angezeigt. Beispiel: &New deklariert das Zeichen N als Mnemonic für diesen Eintrag.
Accelerator: Um diese Schnellbedienung zu aktivieren, sind zwei Schritte erforderlich. Zum Einen muss die Tastenkombination mit einem Tabulatorzeichen dem Ausgabetext angehängt werden, zum Anderen muss die Tastenkombination explizit als
Accelerator festgelegt werden. Dazu dient der Methoden-Aufruf setAccelerator
eines Menü-Eintrags. Beispiel:
MenuItem newItem = new MenuItem(fileMenu, SWT.NONE);
// Text + Accelerator-Text setzen
newItem.setText("&New\tCtrl+N");
// Accelerator einschalten
newItem.setAccelerator(SWT.CTRL | ’N’);
Da mit SWT plattformunabhängige Programme erstellt werden, ist das Problem zubeachten, dass sich die Tastenkombinationen auf den Plattformen unterscheiden. Wo unter
Windows die CTRL-Taste verwendet wird, setzt Mac OS auf die Apfel-Taste. Daher ist es
in der Regel besser, plattformneutrale „Modifizierer“ (Konstanten aus der Klasse SWT) zu
verwenden:
SWT.MOD1 statt SWT.CTRL: Unter Windows wird die CTRL-, unter MacOS X die
Apfel-Taste genommen.
SWT.MOD2 statt SWT.SHIFT: SHIFT-Taste sowohl unter Windows als auch unter
Mac OS X
SWT.MOD3 statt SWT.ALT: ALT-Taste unter Windows und Mac OS X
Es gibt noch ein weiteres plattformspezifisches Verhalten. Wo ist es unter Windows erforderlich, sowohl den Text für den Accelerator mit \t getrennt anzugeben als auch den
Code mit setAccelerator zu setzen, wird unter Mac OS X dagegen der Text automatisch anhand des Tastatur-Codes gesetzt. Es ist dort aber zulässig, wie unter Windows
zu verfahren.
Damit sind aus technischer Sicht die wichtigsten plattformspezifischen Merkmale unterschieden. Leider gibt es aber noch ein nicht-technisches Problem zu beachten. So geben Hersteller von Fenstersystemen häufig Entwurfsrichtlinien für die Reihenfolge und
Positionen der Menüs und ihrer Einträge vor. Weiterhin unterscheiden sich auf den Plattformen in der Regel auch die Tastatur-Kürzel zur schnelleren Bedienung. So führt normalerweise kein Weg daran vorbei, Menüstrukturen plattformabhängig zu bauen.
58
3.4 Menüs und Toolbar-Leisten
3.4.2
Popup-Menüs
Popup-Menüs werden in der Regel bei einem „Rechtsklick“ auf eine Komponente angezeigt. Damit kann der Anwender Operationen ausführen, die diese Komponente direkt
manipulieren. Die Erstellung eines solchen Menüs unterscheidet sich nur unwesentlich
von der eines „normalen“ Menüs.
1. Zuerst wird das Popup-Menü erzeugt und an dem Widget registrieren. Das Menü
erhält das Widget auch als Vaterelement.
Menu popupMenu = new Menu(component, SWT.CASCADE);
component.setMenu(popupMenu);
2. Jetzt lassen sich Einträge in das Menü einfügen:
MenuItem copyItem = new MenuItem(popupMenu, SWT.NONE);
copyItem.setText("Copy");
3. Wie bei normalen Menüs dürfen auch Untermenüs eingehängt werden.
Die Abbildung 3.62 zeigt ein Popup-Menü, das direkt an einem Fenster registriert wurde.
Abbildung 3.62: Popup-Menü auf verschiedenen Plattformen
3.4.3
System-Tray
Das System-Tray ist ein spezielles Menü, das auf dem Desktop verankert wird. Mit seiner Hilfe kann der Anwender wichtige Funktionen einer Anwendung steuern, ohne deren
Fenster manuell öffnen zu müssen. Auch diese Menü wird fast wie ein „normales“ erzeugt.
1. Zuerst wird die Referenz auf das System-Tray vom Fenstersystem geholt. Es besteht
die Möglichkeit, dass einzelne Fenstersysteme kein System-Tray unterstützen. Dieser Fall muss berücksichtigt werden. Anschließend wird für die eigene Anwendung
ein einzelnes TrayItem-Element als Menü-Eintrag für das System-Tray erzeugt.
Das TrayItem-Element sollte ein eigenes Bild erhalten, damit der zugehörige Eintrag in der häufig recht kompakten Darstellung im System-Tray gut erkennbar ist.
Tray tray = display.getSystemTray();
if (tray != null) {
TrayItem trayItem = new TrayItem(tray, SWT.NONE);
trayItem.setImage(imageRegistry.get("duke"));
2. Jetzt kann dem TrayItem ein Menü zugeordnet werden, dass bei Aktivierung angezeigt wird. Es muss sich um ein Popup-Menü handeln, weil es ein Untermenü des
System-Tray ist.
final Menu menu = new Menu(shell, SWT.POP_UP);
59
3.4 Menüs und Toolbar-Leisten
3. Danach lassen sich Einträge und weitere Untermenüs in das Menü einfügen:
MenuItem item = new MenuItem(tray, SWT.NONE);
item.setText("Higher");
4. Das eigene Menü muss manuell bei einem Mausklick eingeblendet werden. Dazu
ist etwas Ereignisbehandlung erforderlich. Diese wird in Abschnitt 3.6 eingehend
betrachtet.
trayItem.addListener(SWT.MenuDetect, new Listener () {
public void handleEvent (Event event) {
menu.setVisible (true);
}
});
Die Positionen und das Aussehen unterscheiden sich etwas auf den unterschiedlichen
Plattformen, was die Abbildung 3.63 zeigt.
Abbildung 3.63: Menü in einem System-Tray auf verschiedenen Plattformen
3.4.4
Toolbars
Toolbars werden eingesetzt, um dem Benutzer einen schnellen Zugriff auf häufig benötigte Funktionen zu bieten. Sie enthält in der Regel Tasten und Trennabstände, aber auch
andere Komponenten sind möglich, wenn auch ungewöhnlich. Normalerweise werden
Radio-Tasten, „normale“ Tasten, Checkboxen und Drop-Down-Listen eingesetzt. Gerade
wenn Tasten mit sehr kleinen Bildern verwendet werden, sollten unbedingt auch TooltipTexte angegeben werden, da nicht jeder Benutzer sofort die Bedeutung eines kleines
Bildchens erkennen kann.
Die folgenden Schritte sind in der Regel zu durchlaufen, um eine Toolbar zu erzeugen:
1. Zuerst wird die Toolbar als Container angelegt. Neben einer horizontalen wäre auch
eine vertikale Ausrichtung denkbar.
ToolBar bar = new ToolBar(shell, SWT.HORIZONTAL);
2. Jetzt lassen sich Einträge zur Leiste hinzufügen. Hier wird eine „normale“ Taste
eingesetzt, die statt eines Textes ein Bild besitzt. Da Bilder nicht immer sehr aussagekräftig sind, erhält die Taste auch einen Tooltip-Text.
ToolItem item = new ToolItem(bar, SWT.PUSH);
item.setImage(image);
item.setToolTipText("Neu...");
Mögliche Tasten-Typen sind:
SWT.PUSH: „normale“ Taste
60
3.4 Menüs und Toolbar-Leisten
SWT.CHECK: Taste, die ihren Zustand speichern kann (Checkbox)
SWT.RADIO: exklusive Auswahl aus einer Anzahl Möglichkeiten (Radio-Taste)
SWT.SEPERATOR: Trennsymbol (z.B. Strich, größerer Abstand, . . . )
SWT.DROP_DOWN: Taste für eine Auswahlliste. Die Liste muss manuell angelegt und angezeigt werden. Das ist recht umständlich und soll daher hier nicht
gezeigt werden.
Zu beachten ist noch, dass sich die Toolbar im Layout des Fensters befindet. Daher muss
ein passender Layoutmanager verwendet werden, der so ein schmales Element an seinem Rand unterstützt. Das Beispiel ToolBarApplication verwendet ein einspaltiges
GridLayout, in dem die Toolbar in der obersten Zeile sitzt.
Und so sieht das Resultat aus:
ToolBarApplication
Abbildung 3.64: Toolbar auf verschiedenen Plattformen
3.4.5
Coolbars
Das Widget mit dem merkwürdigen Namen CoolBar ist eine flexiblere Toolbar. Da es
nicht nur auf Tasten und Auswahllisten beschränkt ist, darf es beliebige Widgets beinhalten. Weiterhin kann der Anwender interaktiv die Einträge in der Coolbar verschieben. Die
Schritte zur Erstellung sind auch hier relativ einfach:
1. Zuerst wird die Coolbar als Container angelegt. Neben einer horizontalen wäre auch
eine vertikale Ausrichtung denkbar.
CoolBar bar = new CoolBar(shell, SWT.HORIZONTAL);
2. Anschließend lassen sich einzelne Einträge in Form von CoolItems zur Leiste hinzufügen. Diese nehmen dann die eigentlichen Widgets auf. Dabei handelt es sich
häufig um Toolbar-Elemente, aber auch andere Widgets sind denkbar. Leider muss
die Größe eines Eintrags manuell berechnet werden, weil die CoolItems dazu nicht
in der Lage sind.
CoolItem item = new CoolItem(bar, SWT.NONE);
item.setControl(contentsOfItem);
// manuell Größe des CoolItems berechnen
Ein ausführliches Beispiel folgt nach dieser Aufzählung.
Wie bei der Toolbar gilt auch hier, dass sich die Coolbar im Layout des Fensters befindet.
Daher muss der Layout-Manager „passen“.
61
3.4 Menüs und Toolbar-Leisten
Coolbar
Coolitem
(veränderbar)
Abbildung 3.65: Elemente einer Coolbar
Das folgende Beispiel (CoolBarApplication) zeigt, wie eine Toolbar gefüllt und als
Widget in die Coolbar eingefügt wird. Es handelt sich dabei um die erste Toolbar aus der
Abbildung 3.65.
private void initGUI(Shell shell) {
// GridLayout mit einer Spalte: Zeile 0 für die Leiste
shell.setLayout(new GridLayout(1, false));
// Horizontal angeordnete Leiste erzeugen.
CoolBar bar = new CoolBar(shell, SWT.HORIZONTAL);
// Größenänderung: Die Coolbar soll horizontal wachsen.
GridData data = new GridData(SWT.FILL, SWT.DEFAULT,
true, false);
bar.setLayoutData(data);
}
// Einen Eintrag mit drei Tasten hinzufügen.
CoolItem item = new CoolItem(bar, SWT.NONE);
Composite element = createFileToolBar(bar);
computeCoolItemSize(item, element);
Jetzt wird die Toolbar erzeugt:
// Drei Tasten zum Eintrag hinzufügen:
// Dazu wird eine Toolbar mit den Tasten
// angelegt und die Leiste als Control des CoolItems angegeben.
// Prinzipiell könnte auch ein Composite mit eigenem
// Layout verwendet werden.
private Composite createFileToolBar(Composite parent) {
ToolBar bar = new ToolBar(parent, SWT.HORIZONTAL);
ToolItem item = new ToolItem(bar, SWT.PUSH);
item.setImage(imageRegistry.get("new"));
item.setToolTipText("Neu...");
}
// Weitere Tasten ...
return bar;
Abschließend fehlt noch die Methode zur Größenberechnung des CoolItems anhand
seines Inhalts.
62
3.5 Basisklassen
// Größe eines CoolItems anhand seines Inhalts eintragen.
// Element ist in diesem Beispiel eine Toolbar,
// deren Größe die Maße des CoolItems bestimmt.
private void computeCoolItemSize(CoolItem item,
Composite element) {
// ToolBar in das CoolItem als Element einfügen.
item.setControl(element);
// Größe des ToolBars berechnen.
Point size = element.computeSize(SWT.DEFAULT, SWT.DEFAULT);
// Größe des CoolItems so berechnen, dass ein
// Element der Größe size aufgenommen werden kann.
size = item.computeSize(size.x, size.y);
}
// Berechnete Größe in das CoolItem eintragen.
item.setSize(size);
Und so sieht dann das fertige Ergebnis aus, wenn alle Einträge hinzugefügt wurden:
Abbildung 3.66: Beispiel einer Coolbar
3.5 Basisklassen
Dieser Abschnitt beschäftigt sich mit einem Teil der Klassenhiererachie des Widgets.
Das ist erforderlich, um zu verstehen, welche Klassen bestimmte Aufgaben erfüllen. Die
Abbildung 3.67 zeigt einen Teil der Klassenhierarchie.
Widget
Caret
Menu
ScrollBar
Control
Label
Button
Scrollable
Slider
Text
List
Composite
Abbildung 3.67: Vereinfachte Klassenhierachie
63
3.5 Basisklassen
3.5.1
Widget
Das Widget ist die Basisklasse aller Klassen, die Benutzerinteraktion zulassen. Das
heißt, dass alle Elemente, mit denen der Benutzer in irgendeiner Form interagieren kann,
von Widget abstammen. Das Widget verwaltet darüber hinaus den Freigabezustand.
Dazu ist es wichtig, sich daran zu erinnern, dass SWT Ressourcen und somit Widgets
des darunterliegenden Fenstersystems verwendet. Diese Ressourcen können nicht vom
Garbage Collector freigegeben werden, sondern müssen vom Entwickler verwaltet werden (siehe Abschnitt 3.1.4). In der Klasse Widget ist die Information abgelegt, ob das
Widget bereits wieder manuell freigegen wurde. Dann würden viele Methodenaufrufe auf
solch einem Widget mit einer Ausnahme abgebrochen werden. Weiterhin erlaubt ein Widget die Speicherung anwendungsspezifischer Informationen. Es handelt sich hierbei um
eine Hashtabelle. Diese hilft dem Entwickler dabei, Daten direkt am Widget abzulegen,
die er später z.B. zur Auswertung der Eingaben des Anwenders benötigt. So muss der
Entwickler dazu keine eigene Verwaltung aufbauen. Die Verwendung ist sehr einfach:
void setData(String key, Object value): Lege den Wert value unter
dem Schlüssel key ab.
Object getData(String key): Liest den Wert aus, der zum Schlüssel key gehört.
Das Widget führt auch eine erste Ereignisbehandlung mit zwei verschiedenen Typen von
Ereignissen ein:
DisposeEvent: Dieses wird ausgelöst, wenn das Widget manuell freigegeben wurde. So lässt sich überwachen, ab wann auf dieses Widget nicht mehr zugegriffen
werden kann.
Untypisierte Ereignisse: Ein Ereignistyp wird durch eine Konstante beschrieben. Es
gibt keine unterschiedlichen Klassen für die verschiedenen möglichen Ereignisse.
Das Control, das von Widget erbt, besitzt dagegen für alle unterstützten Ereignisse spezielle Klassen und Methoden. Man spricht in diesem Fall von typisierten
Ereignissen.
Die Ereignisse und ihre Behandlung werden in Kapitel 3.6 eingeführt.
3.5.2
Control
Ein Control ist ein Widget, das ein Gegenstück auf Betriebssystemebene besitzt. Es
verwaltet eine ganze Anzahl von Eigenschaften, die in den folgenden Unterabschnitten
vorgestellt werden.
3.5.2.1
Größen und Position
Im Control werden die bevorzugte sowie die aktuelle Größe der Komponente abgelegt.
Weiterhin beinhaltet es die aktuelle Position bezogen auf sein Vaterelement. In Abbildung 3.68 hat die Taste „Ok“ ein (zur Hervorhebung mit einem Rahmen versehenes)
Composite-Objekt als Vater. Die Taste wird relativ zum Composite-Objekt, dieses wiederum relativ zu seinem Vater, dem Fenster, platziert.
64
3.5 Basisklassen
Abbildung 3.68: Relative Positionierung von Controls
3.5.2.2
Farben
Alle Control-Elemente unterstützen separate Vorder- und Hintergrundfarben. Diese
lassen sich mit setForeground bzw. setBackground setzen. Zusätzlich kann mit Hilfe der Methode setBackgroundImage ein Bild als Hintergrund platziert werden.
Abbildung 3.69: Hintergrundfarbe eines Controls
3.5.2.3
Tooltips
Gerade wenn einem Anwender nicht sofort auf den ersten Blick ersichtlich ist, was er mit
einem Control machen kann, dann sollte dem Control ein erklärender Tooltip-Text
mit setToolTipText eingetragen werden. Dieses betrifft besonders solche Controls,
die lediglich ein Bild und keinen Text besitzen.
setToolTipText
Abbildung 3.70: Tooltip an einem Control
3.5.2.4
Aktivierungszustand
Controls erlauben die Ein- und Ausgabe von Werten. Ist zu einem Zeitpunkt eine Eingabe nicht erwünscht oder sinnvoll, dann sollte sie auch verhindert werden. Dazu kann ein
Control gesperrt und später auch wieder freigegeben werden (Methode setEnabled).
Abbildung 3.71: Gesperrtes Control
65
3.5 Basisklassen
3.5.2.5
Weitere Eigenschaften
Es gibt noch einige weitere interessante Eigenschaften:
Die Sichtbarkeit kann mit setVisible ein- oder ausgeschaltet werden.
Soll das Control ein eigenes Popup-Menü erhalten, dann kann es mit setMenu
eingetragen werden.
Jedes Control kann seinen Text in einem separaten Zeichensatz darstellen. Dieser
wird mit Hilfe der Methode setFont am Control registriert.
Das Kapitel 3.2 hat verschiedene Layout-Manager eingeführt. Bei einigen von ihnen
ist es erforderlich, zusätzliche Layout-Eigenschaften am Control abzulegen. Die
Methode setLayoutData übernimmt diese Aufgabe.
Jedem Control kann ein individueller Mauszeigertyp zugewiesen werden, um die
Art der Eingabe anzuzeigen.
Es ist möglich, die Reihenfolge festzulegen, in der Controls den Tastaturfokus erhalten. Weiterhin kann einem Control auch direkt der Fokus zugewiesen werden.
Controls erlauben das „Ziehen und Ablegen“ (Drag- und Drop) von Daten.
Ein kompletter Dialog besteht aus einem Baum von Controls und Widgets. Es lässt
sich innerhalb dieses Baums nicht nur vom Vater zu seinen Kindern, sondern auch
von einem Kind zu seinem Vaterlement navigieren. Dazu enthält jedes Control
eine Referenz auf sein Vater-Element.
3.5.2.6
Ereignisse
Die Basisklasse Widget verwaltet lediglich untypisierte Ereignisse. In der abgeleiteten
Klasse Control dagegen werden typisierte Ereignisse unterstützt. Damit ist gemeint,
dass es für jede Art von Ereignis eine eigene Ereignisklasse und separate Methoden
gibt. Dieses vereinfacht die Ereignisbehandlung gerade in den Fällen, in denen der Entwickler lediglich an bestimmten Ereignistypen interessiert ist. Weiterhin ist der Zugriff
auch typsicherer. Auswahl an Ereignissen:
ControlEvent: Dieses Ereignis wird gemeldet, wenn das Control verschoben
oder in seiner Größe verändert wurde.
DragDetectEvent: Der Inhalt des Controls soll per „Drag and Drop“ verschoben
oder kopiert werden.
FocusEvent: Das Control hat den Tastaturfokus erhalten oder verloren.
HelpEvent: Es wurde eine Hilfe für das Control angefordert.
KeyEvent: Es wurde eine Taste der Tastatur gedrückt oder losgelassen.
MenuDetectEvent: Es wurde ein Popup-Menü für die Komponente angefordert.
MouseEvent: Es wurde eine Maustaste über der Komponente gedrückt.
MouseMoveEvent: Der Mauszeiger wurde über der Komponente bewegt.
PaintEvent: Die Komponente muss neu gezeichnet werden. Das Ereignis kann
dazu verwendet werden, eigene Controls zu erstellen, die sich selbst zeichnen.
TraverseEvent: Es wurde eine Taste gedrückt, um den Fokus auf eine andere
Komponente zu setzen.
Von Control abgeleitete Klassen bieten häufig noch weitere typisierte Ereignisse.
66
3.6 Ereignisbehandlung
3.5.3
Scrollable
Einige Controls verwalten Inhalte, die für einen Dialog zu groß werden können. Dabei kann es sich beispielsweise um ein mehrzeiliges Texteingabefeld oder eine Zeichenfläche in einem Malprogramm handeln. Um den Inhalt trotzdem bearbeiten zu können,
muss dieser gescrollt werden. Alle Controls, die ihren Inhalt verschieben („scrollen“)
können, erben von der Basisklasse Scrollable. Diese verwaltet den zur Verfügung
stehenden Platz und stellt bei Bedarf die horizontalen und vertikalen Scrollbalken zu Verfügung, auf die mit den folgenden Methoden zugegriffen werden kann:
ScrollBar getHorizontalBar(): horizontalen Scrollbalken auslesen
ScrollBar getVertikalBar(): vertikalen Scrollbalken auslesen
3.5.4
Composite
Das Composite ist schon aus Beispielen zu geschachtelten Layouts bekannt. Es ist die
Basisklasse für alle Widgets, die weitere Widgets als Kindelemente besitzen können, die
also als Container für andere Controls oder Widgets dienen. Ein Composite verwaltet die Kindelemente und sowie Layout-Manager zu deren Platzierung. Weiterhin kennt
es die Reihenfolge, in der die Kindelemente beim Druck auf die Tabulatortaste besucht
werden.
3.6 Ereignisbehandlung
Nachdem in den vorherigen Abschnitten immer wieder einmal auf die Ereignisbehandlung hingewiesen wurde, soll sie jetzt endlich richtig eingeführt werden. Mit dem Begriff
der „Ereignisbehandlung“ ist hier gemeint, wie die Anwendung Ereignisse mitgeteilt bekommt. Da in Anwendungen ein aktives Abfragen von Benutzerinteraktionen (Polling)
völlig indiskutabel ist, wird die Anwendung durch Ereignisse informiert, wenn der Anwender Aktionen einleiten möchte. Der Auslöser einer solchen Aktion kann beispielsweise
ein Tastendruck, eine Mausbewegung usw. sein. Denkbar sind aber auch alternative Eingabegeräte wie Braille-Tastaturen für blinde Anwender.
3.6.1
Beobachter-Muster
Die Ereignisbehandlung basiert auf dem Beobachter-Entwurfsmuster. Statt „Beobachter“
wird in deutschsprachiger Literatur auch manchmal der englischsprachige Begriff „Listener“ eingesetzt.
Das folgende Diagramm zeigt zur Erinnerung noch einmal die Funktionsweise des Beobachtermusters ohne einen Bezug zu SWT und JFace.
67
3.6 Ereignisbehandlung
EventObject
-source: Object
<<interface>>
Listener
+eventHappened
AnEvent
<<interface>>
Subject
0..*
+addAnEventListener
+removeAnEventListener
+fireAnEventHappened
ConcreteListener
-state: Type
ConcreteSubject
+eventHappened
+addAnEventListener
+removeAnEventListener
+fireAnEventHappened
AnEvent event = new AnEvent(…);
for each listener (listeners)
listener.eventHappened(event);
Abbildung 3.72: Allgemeines Bobachter-Muster
Die Interaktion läuft immer wie folgt ab, wobei die Namen der Klassen und Schnittstellen
in Diagramm 3.72 nicht den realen Gegebenheiten entsprechen:
1. Je nach Aufgabe erzeugt ein Control sehr unterschiedliche Ereignisse. Beispiele
sind das Verschieben des Mauszeigers oder das Drücken einer Taste. Diese Ereignisse werden durch unterschiedliche Klassen implementiert (AnEvent). Alle Klassen haben eine gemeinsame Basisklasse (EventObject) und eventuelle weitere
Attribute und Methoden.
2. Möchte man einen Beobachter für einen bestimmten Ereignistyp schreiben, dann
muss dieser eine zum Ereignistyp passende Schnittstelle implementieren. Der Name der Schnittstelle ist aus dem Ereignistyp ableitbar: EreignisnameListener
3. Danach registrieren sich ein oder mehrere Beobachter (ConcreteListener) an
den Objekten (ConcreteSubject), über deren Änderungen sie informiert werden
wollen. Die Methode zur Registrierung hat in SWT und JFace immer den festen
Namensaufbau addEreignisnameListener.
4. Sollte der Beobachter nicht länger an der Benachrichtigung interessiert sein, dann
kann er sich auch wieder abmelden.
5. Tritt im Laufe der Zeit ein Ereignis auf, dann wird intern im Subjekt eine Methode (hier: fireAnEventHappened) aufgerufen. Diese durchläuft die Liste aller registrierten Beobachter und informiert sie, indem (in diesem Beispiel) die Methode
eventHappened in ConcreteListener aufgerufen wird.
68
3.6 Ereignisbehandlung
Der Ablauf lässt sich wie folgt zusammenfassen:
listener
ConcreteListener
subject
ConcreteSubject
1: addAnEventListener(this)
registrieren
event
AnEvent
2: create
benachrichtigen
3: eventHappened()
Abbildung 3.73: Ablauf im Bobachter-Muster
Teilweise ist in der Literatur auch die Bezeichnung „publisher-subscriber model“ für das
Beobachter-Muster zu finden.
3.6.2
Ablauf unter SWT
Bevor die programmatische Verwendung der SWT-Ereignisse behandelt wird, soll im
kommenden Abschnitt noch ein kurzer Blick das Zusammenspiel zwischen Fenstersystem und SWT bei der Ereigniszustellung geworfen werden.
3.6.2.1
Anbindung an das Fenstersystem
SWT und JFace setzen auf dem zugrundeliegende Fenstersystem auf. Da der Ursprung
der Ereignisse auch das Fenstersystem ist, lohnt es sich, einen Blick auf den „Weg“
eines Ereignisses von seiner Entstehung bis zu einem Widget bzw. Control als Ziel
zu werfen.
Betriebssystem
Ereignis-Queue
Nachricht
Display
Top-Level- Ereignis
Shell
Widget
Ereignis ListenerInterface
Aufruf
Event-HandlerMethode
Abbildung 3.74: Ereigniszustellung unter SWT
69
3.6 Ereignisbehandlung
Der interne Ablauf sieht so aus:
1. Nachdem das Betriebs- bzw. Fenstersystem das Ereignis erkannt hat, wird es intern in einer Warteschlange („Ereignis-Queue“) gespeichert. Diese Pufferung kann
erforderlich sein, weil eventuell schneller Ereignisse anfallen, als sie von einer Anwendung abgearbeitet werden können.
2. Danach wird das Display-Objekt ermittelt, das zur Anwendung gehört. Normalerweise besitzt jede SWT-Anwendung nur ein solches Objekt.
3. Anhand des Display-Objektes kann das Fenster ermittelt werden, in dem das Ereignis ausgelöst wurde.
4. In diesem Schritt wird innerhalb des Fensters das Widget gesucht, dem das Ereignis galt. Handelt es sich beispielsweise um ein Mausereignis, dann ist das Ziel der
Interaktion das oberste Widget an der Position des Mauszeigers.
5. Das Widget wird informiert, dass ein Ereignis eingetreten ist. Es informiert daraufhin
alle Beobachter, die an ihm registriert sind. Das geschieht, indem die entsprechende
Methode, die die Beobachter-Schnittstelle vorgegeben hat, aufgerufen wird.
6. Der „Event-Handler“ ist der Beobachter. Er wird vom Widget informiert und kann
seinerseits Aktionen durchführen.
Hier wird begrifflich zwischen „Nachricht“ und „Ereignis“ unterschieden. Das ist relativ
willkürlich und soll lediglich ausdrücken, dass der normale Entwickler die Nachrichten
des Fenstersystems gar nicht zu Gesicht bekommt.
3.6.2.2
Programmierung
In SWT werden sehr viele unterschiedliche Arten von Ereignissen unterstützt. Das wohl
am häufigsten verwendete Ereignis ist das SelectionEvent. Es soll daher hier exemplarisch für alle anderen Ereignisse näher betrachtet werden. So lösen unter Anderem
Tasten („Buttons“) dieses Ereignis aus, nachdem sie gedrückt und wieder losgelassen
wurden. Um diese Ereignisse abzufangen, müssen immer die folgenden Schritte durchlaufen werden:
1. Definition der Beobachter-Klassen
2. Erzeugen einer Instanz der Beobachter-Klasse
3. Registrierung der Beobachter-Instanz an einem Widget
Das ist nicht neu und wurde bereits im vorherigen Abschnitt über das allgemeine Beobachter-Muster so vorgestellt. Da Ereignisse in SWT einem festen Namensschema folgen,
kann sehr leicht aus dem Namen des Ereignistyps auf die Namen der anderen beteiligten
Klassen geschlossen werden. Im Falle des SelectionEvents sind das:
Die Ereignisklasse, die zusätzliche Informationen zum Ereignis beinhaltet, heißt so
wie der Ereignistyp: SelectionEvent
Die Schnittstelle des Beobachters bekommt an den Typ des Ereignisses die Endung
Listener angehängt. Somit heißt sie SelectionListener.
70
3.6 Ereignisbehandlung
Die Beobachter-Schnittstellen besitzen bei vielen Ereignistypen eine ganze Anzahl
von Methoden, die aber nicht immer in jedem Szenario benötigt werden. Damit der
Entwickler nicht alle unnötigen Methoden selbst (leer) implementieren muss, gibt
es Adapter-Klassen. Diese implementieren die Beobachter-Schnittstelle mit leeren
Methodenrümpfen. Der Name des Adapaters bekommt als Endung Adapter. Beim
SelectionEvent heißt der demnach SelectionAdapter.
Der Name der Methode des Controls, über die sich ein Beobachter anmeldet,
beginnt mit add, gefolgt vom Ereignistyp und endet mit Listener. Für das Selektionsereignis lautet der Name also addSelectionListener.
Das nächste Beispiel zeigt, wie sich ein Beobachter in Form einer anonymen inneren
Klasse an einer Taste registriert.
Button okButton = new Button(shell, SWT.PUSH);
okButton.setText("Weiter");
// Als Beobachter an der Taste anmelden.
okButton.addSelectionListener(new SelectionListener() {
// die wichtige Methode
@Override
public void widgetSelected(SelectionEvent event) {
System.out.println("widgetSelected: " + event.widget);
}
// Wird auf einigen Plattformen nie erzeugt (z.B.
// Doppelklick auf einen Listeneintrag).
@Override
public void widgetDefaultSelected(SelectionEvent event) {
}
});
Einfach ist es, wenn statt einer Schnittstelle der passende Adapter verwendet wird. In
diesem Fall ist es nicht mehr erforderlich, die zweite, nicht benötigte Methode widgetDefaultSelected zu überschreiben.
Button okButton = new Button(shell, SWT.PUSH);
okButton.setText("Weiter");
// Wieder als Beobachter anmelden.
okButton.addSelectionListener(new SelectionAdapter() {
// die wichtige Methode
@Override
public void widgetSelected(SelectionEvent event) {
System.out.println("widgetSelected: " + event.widget);
}
});
3.6.2.3
Unterscheidung der Ereignisquellen
Nun kommt es in realen Anwendungen nicht selten vor, dass mehr als ein Widget ein
Selection-Ereignis auslösen kann. Wie können die Quellen der Ereignisse unterschieden werden? Es ist ja durchaus interessant zu wissen, ob die Weiter- oder Abbruchtaste
in einem Dialog gedrückt wurde. Für jede Quelle einen eigenen Beobachter zu nehmen,
ist nicht nur sehr aufwändig sondern auch unflexibel. Die Lösung besteht darin, den Beobachter die Ereignisquelle herausfinden zu lassen. Generell für alle Ereignistypen ist
71
3.6 Ereignisbehandlung
das durch Auslesen der Quelle aus dem Ereignisobjekt mit Hilfe des öffentlichen Attributs widget der Basisklasse TypedEvent möglich. Alle Ereignisklassen, die typisierte
Ereignisse repräsenatieren, erben direkt oder indirekt von TypedEvent. Das Attribut
widget liefert eine Widget-Referenz auf den Auslöser des Ereignisses zurück. Diese
Referenz kann mit den bekannten Referenzen verglichen werden. Nun kann es aber vorkommen, dass die Referenzen auf die Oberflächen-Komponenten in der Dialogklasse
privat sind und ein Zugriff somit nicht erlaubt ist. Da der Fall nicht selten vorkommt, gibt
es eine weitere Lösung. Abschnitt 3.5.1 hat die Eigenschaften der Klasse Widget aufgeführt. Dazu gehört auch die Möglichkeit, benutzerspezifische Informationen an einem
Widget abzulegen. An jeder Komponente, die ein Ereignis auslösen kann, wird eine Information abgelegt, die später während der Ereignisbehandlung ausgelesen wird. Das
Beispiel zeigt den Einsatz (Datei ButtonEventApplication.java):
final Button okButton = new Button(shell, SWT.PUSH);
okButton.setText("Weiter");
// Benutzerspezifische Information ablegen.
// - "source" ist der Schlüssel.
// - "ok" ist der zugehörige Wert.
okButton.setData("source", "ok");
// Beobachter registrieren
okButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
// Variante 1: Auslesen der benutzerspezifischen
// Information
boolean wasOk = event.widget.getData("source").equals("ok");
}
});
// Variante 2: Vergleich der Referenzen auf das Widget
boolean wasOk = event.widget == okButton;
In der Methoden widgetSelected können nun die Ereignisquellen sehr einfach unterschieden werden. Es ergibt sich in der Praxis häufig der folgende Aufbau, wobei der
Name des Schlüssels natürlich nicht unbedingt "source" sein muss:
public void widgetSelected(SelectionEvent event) {
if (event.widget.getData("source").equals("command1")) {
// Ausführung für Komponente 1
}
else if (event.widget.getData("source").equals("command2")) {
// Ausführung für Komponente 2
}
// ...
else if (event.widget.getData("source").equals("commandN")) {
// Ausführung für Komponente N
}
}
3.6.2.4
Entwurfsmöglichkeiten
Wer aber behandelt die Ereignisse? Soll es die Klasse machen, die den Dialog erzeugt
hat, oder aber ist es sinnvoller, eine separate Klasse zu verwenden?
72
3.6 Ereignisbehandlung
Eine zentrale Beobachterklasse empfängt die Ereignisse
Zunächst wird die Dialogklasse so erweitert, dass sie von der Klasse SelectionAdapter erbt oder die Schnittstelle SelectionListener implementiert. Interessant
ist auch hier wieder nur die Methode widgetSelected. Danach wird ein Objekt der
Beobachter-Klasse erzeugt und an den Komponenten (hier Tasten) registriert:
SelectionListener listener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
...
}
});
okButton.addSelectionListener(listener);
cancelButton.addSelectionListener(listener);
Jedes Widget erhält seinen eigenen Beobachter
Im Unterschied zur Variante 1 wird für jedes Widgets, das ein Ereignis auslösen kann, eine eigene Beobachter-Klasse geschrieben. Ansonsten unterscheidet sich diese Variante
nicht von der ersten.
Vergleich der Ansätze
In der Praxis gibt es weitere Möglichkeiten, die später auch noch vorgstellt werden.
Wichtig in diesem Zusammenhang ist auch die Trennung von Anwendungslogik bzw.
Geschäftslogik und Oberfläche. Weil gerade dieses Thema in der Praxis eine hohe Bedeutung erfährt, wird im Skript in Kapitel 4 eine separate Dialogsteuerung behandelt.
Daher werden hier nur die beiden bereits bekannten Varianten diskutiert.
1. Variante: Eine separate Klasse empfängt die Ereignisse:
+ Die Lösung ist schnell erstellt.
+ Es werden weniger Klassen benötigt.
– Der Ansatz wird schnell sehr unübersichtlich, da eine Methode fast die komplette
Ereignisbehandlung übernimmt.
2. Variante: Jedes Widget erhält seinen eigenen Beobachter:
+
+
–
–
3.6.2.5
Die Auftrennung ist bei vielen verschiedenen Ereignistypen häufig flexibler.
Der Ereigniscode kann für verschiedene Klasse wiederverwendet werden.
Die Lösung ist teilweise aufwändiger zu erstellen.
Es werden mehr Klassen benötigt.
Ereignisse bei Fenstern
Dieser kleine Abschnitt zeigt exemplarisch die Verwendung der Ereignisses, die ein Fenster auslösen kann. Der Ereignistyp ist in diesem Fall das ShellEvent.. Somit heißen die
Beobachter ShellListener für die Schnittstelle und ShellAdapter. Es gibt in diesem
Beobachter mehrere Methoden:
73
3.6 Ereignisbehandlung
public void shellIconified(ShellEvent event): Das bisher geöffnete
Fenster wurde zu einem Icon verkleinert.
public void shellDeiconified(ShellEvent event): Das Fenster wurde
aus einem Icon zu seiner vorherigen Größe wiederhergestellt.
public void shellDeactivated(ShellEvent event): Ein offenes Fenster
ist nicht länger aktiv. Es war bisher das oberste Fenster. Jetzt wurde aber ein anderes nach oben gebracht.
public void shellActivated(ShellEvent event): Das Fenster ist aktiv.
Es wird das oberste Fenster, das Eingaben erwartet.
public void shellClosed(ShellEvent event): Das Fenster soll vom Anwender geschlossen werden. Diese Aktion lässt sich programmgesteuert verhindern, indem das Attribut doIt im Ereignisobjekt auf false gesetzt wird. Dieses auf
den ersten Blick merkwürdige Verhalten lässt sich gut an einem Beispiel begründen:
Das Programm soll einen Anwender beim Schließen eines Fensters fragen, ob alle
bisher ungesicherten Daten gespeichert oder verworfen werden sollen, oder ob das
Programm das Fensters lieber doch nicht schließen soll. Für den letzten Fall kann
das Attribut doIt verwendet werden.
shell.addShellListener(new ShellAdapter() {
@Override
public void shellClosed(ShellEvent event) {
if (!fensterschliessen()) {
event.doit = false;
}
}
});
Das Attribut wird auch für andere Ereignistypen verwendet, die aber bisher noch
nicht vorgestellt wurden. Die API-Dokumentation ist hier wie immer hilfreich.
3.6.3
Ablauf unter JFace
JFace basiert auf den SWT-Ereignissen, ergänzt aber eine Abstraktionsschicht. Die Idee
dahinter stellt der kommende Abschnitt vor.
3.6.3.1
Abstraktion von SWT mittels Aktionen
Ein kleines Beispiel einer Anwendung soll die Abstraktion vorstellen: Das Programm besitzt ein Menü sowie eine Toolbar-Zeile. In beiden befindet sich jeweils ein Eintrag, über
den der Anwender seine Daten speichern kann. Er kann also auf zwei Arten dieselbe Aktion auslösen. Unter SWT würde ein Entwickler den Menüeintrag sowie die Taste in der
Toolbar-Leiste separat erzeugen und einfügen. Dann müsste er noch einen Beobachter
schreiben und an beiden Elementen registrieren. In JFace dagegen wird eine Operation sowie dessen Beschreibungen in einer Einheit, der Aktion, gekapselt. Eine Aktion
(Klasse Action) in dem Beispiel würde also aus dem Text wie z.B. „Sichern“, einem
Icon sowie einer Methode bestehen, die den Beobachter darstellt. Zusätzlich kann einer Aktion auch eine Tastenkombination zugeordnet werden, über die der zugehörige
Beobachter aufgerufen wird. Damit ist die Aktion unabhängig davon, wie sie dargestellt
wird. Aus ihrer Beschreibung werden nun Menüeinträge oder Tasten für Toolbar-Leisten
74
3.6 Ereignisbehandlung
erzeugt. Weiterhin wird damit automatisch der Beobachter registriert. Zusammengefasst
lässt sich sagen, dass eine Aktion aus diesen Komponenten besteht:
ein beschreibender Text, der z.B. als Beschriftung für Tasten verwendet werden kann
ein Bild für Tasten, Toolbar-Tasten usw.
Tastenkombinationen als Auslöser
Beobachter-Code, der bei Aktivierung der Aktion ausgeführt wird.
Anhand der Abbildung 3.75 ist ersichtlich, dass aus einer Aktion Widgets erzeugt werden.
Umgekehrt rufen diese die Aktion als Beobachter auf, wenn ein Ereignis eingetreten ist.
2. ruft auf
1. beschreibt Darstellung
1. beschreibt Darstellung
Aktion
Abbildung 3.75: Aktion für Menüeintrag und Toolbar-Taste
Die Darstellung ist teilweise plattformabhängig. So kann es vorkommen, dass die MenüIcons unter Gnome in dessen Standard-Einstellung ausgeblendet sind. In Abbildung 3.76
ist das anhand der zweiten Zeile von Screenshots gut zu erkennen.
Abbildung 3.76: Darstellungen aus einer Aktion
3.6.3.2
Anbindung an das Fenstersystem
JFace verbirgt einen Teil der Details, die unter SWT noch direkt erkannbar waren. So fällt
dann die Zustellung eines Ereignisses durch das Fenstersystem auch etwas anders aus.
Betriebssystem:
Ereignis-Queue
verwaltet jeweils ein Menü,
eine Toolbar, ...
Nachricht
Display
Application
Window
Action
Contribution
Aufruf
Hauptfenster einer
Anwendung
run-Methode
einer Aktion
Abbildung 3.77: Ereigniszustellung unter JFace
75
3.6 Ereignisbehandlung
ApplicationWindow ist eine Klasse, die das Shell-Objekt kapselt (siehe Abschnitt
3.1.2). Dabei werden bestimmte Methoden vorgegeben, die ein Entwickler überschreiben
kann, um seine Oberfläche zu erzeugen. Eine „Contribution“ ist für die Verteilung von
Ereignisses zuständig, die aus eine Aktion erzeugt wurden. Je Menü, Toolbar-Leiste usw.
gibt es ein solches Objekt. Trifft bei ihm ein Ereignis ein, dann ruft es in der zugehörigen
Aktion die run-Methode auf. Diese Methode ersetzt die widgetSelected-Methode der
SWT-Ereignisbehandlung und wird vom Entwickler überschrieben.
3.6.3.3
Programmierung
Aktionen abstrahieren nur von SelectionEvents. Andere Ereignistypen sind nicht betroffen. Anhand des folgenden Beispiels wird gezeigt, wie prinzipiell mit Aktionen gearbeitet wird. Die Aktion soll ein neues Dokument erzeugen.
Abbildung 3.78: Aktion zur Dokumenterzeugung
Aktionsklassen erzeugen
Zunächst muss eine Klasse erstellt werden, die die Aktion darstellt. Im einfachsten Fall
erbt sie von der Klasse Action. Im Konstrutor werden Methoden der Basisklasse aufgerufen, um Texte, das Bild und sowie die Tastenkombination zur Aktivierung der Aktion
zu übergeben (Beispiel ActionApplication).
public class NewDocumentAction extends Action {
public NewDocumentAction() {
// Text, der die Aktion darstellt
super("&Neu"); // Text, "N" ist Mnemonic
// Tooltip-Text
setToolTipText("Neues Dokument");
// Tastenkombination zur Aktivierung
setAccelerator(SWT.MOD1 | ’N’);
}
// Bild, ActionApplication.NEW_ICON ist ein
// String der Beispielanwendung mit einem
// Dateinamen.
setImageDescriptor(ImageDescriptor.createFromFile(
this.getClass(),
ActionApplication.NEW_ICON));
// Aktion, die bei Selektion des Eintrags ausgeführt wird.
@Override
public void run() {
// Im Beispiel wird eine Meldung in der Statuszeile
76
3.6 Ereignisbehandlung
}
}
// des Fensters ausgegeben.
ActionApplication.getApplication().setStatus(
"Neues Dokument erzeugt!");
Aktionsobjekte erzeugen
Nach der Erstellung der Aktionsklassen können diese in der eigentlichen Anwendung
eingesetzt werden. Dazu werden Objekte der Klassen erzeugt.
public class ActionApplication extends ApplicationWindow {
// Verwendete Icons
public static final String NEW_ICON = "/res/new_wiz.gif";
public static final String SAVE_ICON = "/res/save_edit.gif";
// Alle
private
private
private
benötigten Aktionen
Action newDocumentAction;
Action saveDocumentAction;
Action exitAction;
// Alle Aktionsobjekte erzeugen.
public ActionApplication() {
super(null);
newDocumentAction = new NewDocumentAction();
saveDocumentAction = new SaveDocumentAction();
exitAction
= new ExitAction();
}
// Die Methode wird gleich erstellt.
createContents();
//...
Menüeintrag aus einer Aktion erzeugen
Wie in Abschnitt 3.6.3.2 beschrieben, verwaltet eine sogenannte „Contribution“ die Aktionen eines Menüs oder einer Toolbar. Für Menüs wird hierzu die Klasse MenuManager
verwendet. Der Manager wird in der überschriebenen Methode createMenuManager
der Fensterklasse ApplicationWindow erzeugt, wobei für jedes Untermenü eine eigene „Contribution“ erzeugt werden muss.
// ... Fortsetzung der Klasse ActionApplication
@Override
protected MenuManager createMenuManager() {
// Menüzeile (Contribution) erzeugen.
MenuManager mainManager = new MenuManager();
// Das Dateimenü bekommt auch eine Contribution.
MenuManager fileMenuManager = new MenuManager("&Datei");
// Aktionen zum Dateimenü hinzufügen,
// erzeugt den jeweiligen Menüeintrag.
fileMenuManager.add(newDocumentAction);
fileMenuManager.add(saveDocumentAction);
// Trennstrich
fileMenuManager.add(new Separator());
77
3.6 Ereignisbehandlung
fileMenuManager.add(exitAction);
}
// Dateimenü in Menüzeile einhängen.
mainManager.add(fileMenuManager);
// Menüzeile (Contribution) zurückgeben.
return mainManager;
// ...
sprechenden
Abbildung 3.79: Menü aus Aktionen erzeugen
Coolbar-Taste aus einer Aktion erzeugen
Für Toolbar- und Coolbar-Tasten funktioniert der Mechanismus genauso wie für Menüeinträge. Hier liegen die „Contributions“ in Form der beiden Klasse CoolBarManager bzw.
ToolBarManager vor.
// ... Fortsetzung der Klasse ActionApplication
@Override
protected CoolBarManager createCoolBarManager(int style) {
// CoolBar-Leiste erzeugen.
CoolBarManager coolManager = new CoolBarManager(style);
// ToolBar-Leiste erzeugen.
ToolBarManager fileManager = new ToolBarManager(style);
// Aktionen zur ToolBar-Leiste hinzufügen.
fileManager.add(newDocumentAction);
fileManager.add(saveDocumentAction);
}
// ToolBar-Leiste zur CoolBar-Leiste hinzufügen.
// Das funktioniert wie bei Menüs mit vorhandenen.
// Untermenüs.
coolManager.add(fileManager);
return coolManager;
// ...
Abbildung 3.80: Coolbar aus Aktionen erzeugen
78
3.7 Model-View-Controller mit JFace
Anwendung
In Anwendung müssen jetzt noch die Methoden der Basisklasse ApplicationWindow
aufgerufen werden, die sicherstellen dass das Menü sowie die Coolbar auch wirklich
erstellt werden.
// ... Fortsetzung der Klasse ActionApplication
private createContents() {
// Bewirkt, dass createMenuManager aufgerufen wird.
addMenuBar();
// Bewirkt, dass createCoolBarManager aufgerufen wird.
addCoolBar(SWT.HORIZONTAL);
// Bewirkt, dass die Statuszeile erzeugt wird.
addStatusLine();
}
// ...
Abbildung 3.81: Ergebnis auf einigen Fenstersystemen
Wie Ihnen vielleicht aufgefallen ist, müssen die Tastenkombinationen für die „Accelerator“
nicht mehr dem Text, mit einem Tabulator getrennt, angehängt werden. Es reicht aus, die
Tastencodes mit setAccelerator einzutragen.
3.7 Model-View-Controller mit JFace
In den bisherigen Kapiteln hat die eigentliche Anwendung immer direkt auf die Widgets
zugegriffen. Allerdings hat sich in den vergangenen Jahren sehr schnell gezeigt, dass
diese fehlende Trennung zwischen Darstellung und den dargestellten Daten problematisch ist und zu einem schwer wartbaren Programm führt. Deshalb wurde als einer der
ersten Lösungsansätze das MVC-Entwurfsmuster eingeführt. Den Begriff „Entwurfsmuster“ gab es damals allerdings in der Informatik noch nicht. MVC wurde zuerst in Smalltalk
Ende der 80er des vorigen Jahrhunderts eingesetzt. Es handelt sich dabei um eine Anwendung des Beobachter-Musters, das aus drei Teilen besteht:
1. Das Model enthält die Zustandsinformation eines Widgets. Dazu könnte bei einem
Texteingabefeld beispielsweise der eingegebene Text gehören.
2. Der View ist Beobachter des Zustands (des Modells), um diesen bei Änderungen
sofort darzustellen.
3. Der Controller legt das Verhalten des Widgets auf Benutzereingaben fest.
79
3.7 Model-View-Controller mit JFace
View
Model
Controller
Abbildung 3.82: Struktur im MVC-Ansatz
So sieht der Ablauf bei einer Eingabe des Benutzers aus:
Der Controller verändert das Modell.
Weiterhin informiert er die Sicht bei solchen Benutzereingaben, die nicht das Modell
verändern. Dazu gehört beispielsweise das Verschieben eines Textcursors.
Das Modell ändert seinen Zustand und informiert seinerseits die Sicht.
Die Sicht holt sich die Daten aus dem Modell und stellt sie dar.
Wichtig beim MVC-Ansatz ist, dass das Modell weder Controller noch Sicht „kennt“. Der
große Vorteil dieses Ansatzes besteht darin, dass die Implementierungen der Komponenten leicht ausgetauscht werden können, oder dass mehrere Sichten das Modell auf
unterschiedliche Arten darstellen können.
Diagramm 3.83 zeigt den Ablauf mit Pseudomethoden zur MVC-Kommunikation mit einer
Komponente, wenn der Benutzer ein Ereignis ausgelöst hat.
view
Component
model
Datamodel
controller
Controller
1: registrieren
2: Ereignis senden
event
AnEvent
2.1.1 erzeugen
2.1: Modellereignis erzeugen
2.1.2: Zustandswechsel senden
Abbildung 3.83: Ablauf im MVC
Die nächsten Abschnitte zeigen die Verwendung von Tabellen mit und ohne Trennung
von Ansicht und Modell. SWT selbst bietet keine Trennung. Erst mit JFace ist eine solche
Trennung durch die Programmierschnittstelle vorgegeben. Neben Tabellen unterstützen
eine ganze Anzahl weitere Widgets die Trennung. Dazu gehören mehrzeilige Texteingabefelder, Listen und Bäume.
80
3.7 Model-View-Controller mit JFace
3.7.1
Tabelle ohne MVC
Abbildung 3.84 zeigt, wie sich eine SWT-Tabelle zusammensetzt.
Table
TableItem
TableColumn
Abbildung 3.84: Wichtige Klassen für eine Tabelle
Eine Tabelle der Klasse Table nur mit SWT zu erzeugen bedeutet, dass die komplette
Tabellenstruktur manuell gebaut werden muss und dass ein direkter Lese- und Schreibzugriff auf die einzelnen Zellen erforderlich ist. Wie funktioniert so eine SWT-Tabelle?
Die Klasse Table stellt eine Tabelle dar, die einzelne Spalten (TableColumn) und Zeilen in Form der Klasse TableItem visualisiert. Damit der Anwender später nicht nur
Text eingeben kann, besteht die Möglichkeit, die Eingaben in einzelne Zellen durch verschiedene Editoren vorzunehmen. Dazu wird die Klasse TableEditor eingesetzt. Die
Vorlesung behandelt nur einen kleinen Aspekt der Klasse Table. Sie finden aber sowohl
in der API-Dokumentation einen guten Einstieg als auch in [JavaBsp] Beispiele für eigene
Experimente.
3.7.1.1
Bau einer Tabelle
Um eine Tabelle zu erzeugen, werden normalerweise die folgenden Schritte durchlaufen.
Eine Eingabe über Editoren ist hier nicht vorgesehen.
1. Erzeugen der Tabelle.
table = new Table(shell, SWT.MULTI | SWT.CHECK
| SWT.FULL_SELECTION);
SWT_CHECK bewirkt, dass die erste Spalte zusätzlich eine Checkbox erhält. Mit
SWT.MULTI sind Mehrfachauswahlen, mit SWT.SINGLE nur Einfachauswahlen von
Einträgen erlaubt. Und schließlich weist SWT.FULL_SELECTION die Tabelle an, immer die komplette Zeile bei einer Auswahl zu markieren.
2. Das Neuzeichnen der Tabelle sollte während ihrer Erzeugung verhindern. Das ist
wichtig, weil ansonsten die Darstellung der Tabelle flackern kann. Außerdem würde
ein Neuzeichnen nach jedem Schritt den Aufbau deutlich verlangsamen.
table.setRedraw(false);
81
3.7 Model-View-Controller mit JFace
3. Jede Spalte muss einzeln angelegt werden. Die Tabelle ist immer das Vaterelement.
In der folgenden Abbildung ist die Spalte rot markiert, die durch den anschließenden
Code-Schnippsel erzeugt wird.
TableColumn tcol = new TableColumn(table, SWT.LEFT);
tcol.setText("Auswahl");
Mit SWT.LEFT wird der Inhalt linksbündig ausgerichtet. Erlaubt sind auch die Angaben SWT.CENTER und SWT.RIGHT. Die Methode setText trägt den Spaltentitel
ein.
4. Jede Zeile muss mit ihren Daten angelegt werden. Auch hier ist das Vaterelement
die Tabelle. Die nächste Abbildung zeigt rot markiert die Zeile, die durch den CodeAusschnitt darunter erzeugt wird.
TableItem item = new TableItem(table, SWT.NONE);
item.setText(new String[]{"Zeilenwert 0", "Zeilenwert 1",
"Zeilenwert 2"});
Die einzige erlaubte Konstante ist SWT.NONE, weil eine Tabellenzeile keine eigenen
Attribute besitzt. Die Methode setText erhält ein eindimensionales Array mit den
Werten einer kompletten Zeile.
5. SWT kann die Spaltenbreiten automatisch anhand des Inhaltes berechnen. Dazu
muss auf jedem Spaltenobjekt die pack-Methode aufgerufen werden. Später bei
der Einführung virtueller Tabellen werden Sie sehen, wie die Spaltenbreiten manuell
vergeben werden können.
table.getColumn(colIndex).pack();
6. Zum Schluss muss das Neuzeichnen der Tabelle wieder zugelassen werden.
table.setRedraw(true);
82
3.7 Model-View-Controller mit JFace
3.7.1.2
Beispiel
Die Beispielanwendung (Klasse TableSWTApplication) enthält den fast vollständigen
Code, um die Tabelle aus Abbildung 3.84 zu erzeugen.
public class TableSWTApplication {
// ...
// Tabellendaten
private String[][] data;
// Spaltenüberschriften
private static final String[] COL_HEADERS =
{ "Auswahl", "Spalte 1", "Spalte 2" };
// Tabelle bei der Konstruktion der Oberfläche anlegen
private void createGUI() {
// ...
// Tabelle erzeugen: Mehrfachselektion, Checkbox in der
// ersten Spalte, Selektion erstreckt sich immer über
// alle Spalten
table = new Table(shell, SWT.MULTI | SWT.CHECK
| SWT.FULL_SELECTION);
// Spaltenüberschriften sollen sichtbar sein.
table.setHeaderVisible(true);
// Trennlinien zwischen den Zellen werden angezeigt.
table.setLinesVisible(true);
// Neuzeichnen während der Konstruktion der Tabelle
// verhindern, damit es schneller geht.
table.setRedraw(false);
// Tabellenspalten erzeugen und Überschriften eintragen.
for (int col = 0; col < COL_HEADERS.length; col++) {
TableColoumn col = new TableColumn(table, SWT.LEFT);
col.setText(COL_HEADERS[ col ]);
}
// Zeilen in die Tabelle einfügen und mit Daten füllen.
for (int rowIndex = 0; rowIndex < data.length; rowIndex++) {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(data[ rowIndex ]);
}
// Spaltenbreiten berechnen lassen. Ansonsten sind nicht
// alle Inhalte lesbar.
for (int col = 0; col < table.getColumnCount(); col++) {
table.getColumn(col).pack();
}
}
}
// Neuzeichnen der Tabelle wieder zulassen.
table.setRedraw(true);
// ...
// ...
83
3.7 Model-View-Controller mit JFace
3.7.1.3
Tabellen mit sehr vielen Zeilen
Was passiert, wenn die Tabelle sehr viele Einträge besitzt (z.B. mehr als 1000)? Die Einträge sind aber niemals alle gleichzeitig sichtbar. Trotzdem werden zunächst einmal alle
Zeilenobjekte (TableItem) erzeugt. Das bedeutet, dass sowohl die Konstruktion sehr
lange Zeit benötigt, als auch, dass Speicherplatz verschwendet wird. SWT bietet als Lösung des Problems virtuelle Tabellen, die ihre Daten dann dynamisch durch Ereignisse
anfordert, wenn diese Daten sichtbar werden sollen. Die Konstruktion einer solchen Tabelle unterscheidet sich doch deutlich von der einer statisch erzeugten.
1. Zuerst muss die Tabelle angelegt werden. Zum Kennzeichnen, dass die Daten dynamisch zur Laufzeit bereitgestellt werden, muss die Konstante SWT.VIRTUAL übergeben werden.
table = new Table(shell, SWT.MULTI | SWT.CHECK
| SWT.FULL_SELECTION | SWT.VIRTUAL);
2. Anschließend werden die Spalten mit ihren Überschriften angelegt. Weil die Tabelle
immer nur die sichtbaren Daten dynamisch anfordert, kann sie nicht selbst ermitteln, wieviel Platz jede einzelne Spalte benötigt. Somit kann die Tabelle auch nicht
automatisch die Spaltenbreiten festlegen. Das muss der Entwickler manuell durch
Aufruf der Methode setWidth auf jeder einzelnen Spalte durchführen.
TableColumn tcol = new TableColumn(table, SWT.LEFT);
tcol.setText("Auswahl");
tcol.setWidth(200);
3. Wichtig ist noch, dass ein SetData-Listener an der Tabelle registriert wird. Über
ihn meldet die Tabelle immer dann ein Ereignis, wenn sie neue Daten anzeigen
muss. Im Beobachter werden die neuen Daten in die Tabelle bzw. das TableItemObjekt eingetragen. Das Ereignisobjekt hält in seinem öffentlichen Attribut index
die Nummer der Zeile, für die neue Daten benötigt werden.
table.addListener(SWT.SetData, new Listener() {
public void handleEvent(Event event) {
// "data" ist in diesem Beispiel ein Array mit den
// Tabellendaten. Das Array wird intern von der
// Anwendung verwaltet. data[event.index] liefert
// ein eindimensionales Array mit den Zellwerten
// für alle Spalten einer Zeile.
((TableItem) event.item).setText(data[event.index]);
}
});
Das Beispiel VirtualTableSWTApplication enthält den vollständigen Quelltext, um
eine virtuelle Tabelle für Abbildung 3.84 zu erzeugen.
3.7.1.4
Weitere Eigenschaften
SWT-Tabellen sind mächtiger, als die vorhergehenden Kapitel vermuten lassen. So können Spalten durch Klick in den Tabellenkopf sortiert werden. Weiterhin können die Ausgabe der Werte in den Zellen sowie die Eingabemöglichkeit durch den Anwender vielfältig
verändert werden. Das Beispiel VirtualEditableTableSWTApplication zeigt die
prinzipielle Funktionsweise. Da dieses aber relativ umständlich ist und JFace hier einfachere Möglichkeiten bietet, soll an dieser Stelle lediglich kurz auf das Sortieren der
Einträge verwiesen werden:
84
3.7 Model-View-Controller mit JFace
1. Zunächst muss ein Beobachter an der Tabelle registriert werden, der auf Klicks in
die Spaltenköpfe reagiert.
2. Wird der Beobachter aufgerufen, dann muss er die Tabellendaten selbst sortieren.
3. Anschließend löscht er den kompletten Tabelleninhalt und fügt die sortieren Daten
wieder in die Tabelle ein. Alternativ ist es häufig sinnvoller, statt alle TableItemObjekte zu löschen und erneut anzulegen, diese nur mit den sortierten Daten neu
zu befüllen.
Damit sind aber leider nicht alle Probleme gelöst. So findet hier keine echte Trennung
von Daten und ihrer Darstellung statt. Genau diese bietet aber JFace, so dass in den
kommenden Abschnitten auf den dort vorhandenen MVC-Ansatz eingegangen wird.
3.7.2
Tabelle mit MVC
JFace bietet bei einigen Komponenten, die komplexere Datenstrukturen visualisieren,
einen sauberen MVC-Ansatz:
Für die SWT-Klasse Tree steht ein TreeViewer zur Verfügung.
Die SWT-Tabelle Table wird von einem TableViewer gekapselt.
Für die SWT-Liste List stellt ListViewer den MVC-Ansatz zur Verfügung.
Die Auswahlliste Combo aus SWT wird vom ComboViewer gekapselt.
Die erforderlichen JFace-Klassen sind alle im Paket org.eclipse.jface.viewers zu
finden. Da die Arbeit mit allen Viewer-Klassen relativ ähnlich ist, soll hier nur das Prinzip
anhand der Tabelle gezeigt werden. Alle Beispiele funktionieren mit Eclipse ab Version
3.3. Für ältere Versionen gibt es auch entsprechende Lösungen, die aber nicht unbedingt
mehr zu empfehlen sind. Das folgende vereinfachte Klassendiagramm zeigt die wichtigsten Klassen für den TableViewer. Die nicht mehr relevanten älteren Klassen fehlen.
Mit blauer Schrift sind die Klassen gekennzeichnet, die vom Benutzer in seiner eigenen
Anwendung erstellt werden. Die Namen sind nur beispielhaft zu sehen.
Table
TableColumn
stellt dar
1
stellt dar
1
TableViewerColumn
holt Daten über
1
SWT
<<abstract>>
CellLabelProvider
TableViewer
1 gehört zu
eingeben
<<interface>>
IContentProvider
<<abstract>>
EditingSupport
stellt dar
1
1
<<interface>>
IStructuredContent
Provider
MyEditingSupport
verwendet 1
ColumnLabelProvider
<<abstract>>
CellEditor
MyContentProvider
MyLabelProvider
TextCellEditor
ModellDaten
Eingabe
Modell
Darstellung
Abbildung 3.85: Wichtige Klassen für eine JFace-Tabelle
85
3.7 Model-View-Controller mit JFace
Gut zu erkennen sind die unterschiedlichen Klassen für das Modell, die Ansicht und die
Benutzereingabe. Die grau hinterlegte Bereich „SWT“ beinhaltet einen Teil der Klassen,
die JFace von SWT nutzt.
3.7.2.1
Modell
Dieser Abschnitt beschäftigt sich mit dem Modell sowie dessen Anbindung an die Tabelle.
Table
TableColumn
stellt dar
1
stellt dar
1
TableViewerColumn
holt Daten über
1
SWT
<<abstract>>
CellLabelProvider
TableViewer
1 gehört zu
eingeben
<<interface>>
IContentProvider
<<abstract>>
EditingSupport
stellt dar
1
1
<<interface>>
IStructuredContent
Provider
MyEditingSupport
verwendet 1
ColumnLabelProvider
<<abstract>>
CellEditor
MyContentProvider
MyLabelProvider
TextCellEditor
ModelData
Eingabe
Modell
Darstellung
Abbildung 3.86: Modellklassen für die JFace-Tabelle
Um ein Modell für eine Tabelle zu erstellen, ist keine direkte Abhängigkeit von einer
JFace-Klasse erforderlich. Im einfachsten Fall besteht das Modell aus einer ArrayList
mit Objekten. Jedes Objekt repräsentiert eine Zeile der Tabelle. Durch Filter kann die
Anzeige bestimmer Zeilen (Objekte) unterdrückt werden. Eine separate Sortierung erlaubt es, die Reihenfolge in der Ansicht anhand von Kriterien vertauschen zu lassen.
Denkbar ist beispielsweise, den Tabelleninhalt anhand des Nachnamens einer Person
zu sortieren. Dabei wird nicht der Modellinhalt verändert. Es handelt sich lediglich um
eine Darstellung der Zeilen, die nicht der Reihenfolge im Modell entspricht.
Die folgende Beispiel zeigt eine einfache Klasse Person mit dem Namen und dem Alter
einer Person.
public class Person {
private String name;
private int age;
// ...
}
Das Modell enthält eine ArrayList mit Person-Objekten, die wie in Abbildung 3.87
dargestellt werden soll.
86
3.7 Model-View-Controller mit JFace
public class Person {
private String name;
private int age;
// ...
}
ArrayList
Abbildung 3.87: JFace-Tabelle für eine Liste von Personen
Mit diesem Modell kann die Tabelle noch gar nichts anfangen. Um die Daten aus dem Modell in die Tabelle zu kopieren, wird die Schnittstelle IStructuredContentProvider
benötigt. Der Entwickler stellt eine modellspezifische Klasse bereit, die diese Schnittstelle implementiert und die Modelldaten so aufbereitet, dass die Tabelle sie darstellen kann. Dazu müssen die Modelldaten in ein Array mit Objekten konvertiert werden.
Das ist im Falle einer ArrayList recht einfach, weil diese mit der Methode toArray
schon die erforderliche Konvertierung besitzt. Darüber hinaus informiert die Tabelle den
IStructuredContentProvider über eine komplette Änderungen der Daten in der
Tabelle. Abbildung 3.88 zeigt den Ablauf der Interaktion.
application
viewer
TableViewer
1: setInput(dataModell)
provider
IStructuredContentProvider
1.1: inputChanged(dataModell)
2: getElements(dataModell)
liefert eine ArrayDarstellung des
Datenmodells
dataModell.
Abbildung 3.88: Interaktion zwischen JFace-Tabelle und Modell
Die Anwendung trägt die eigentlichen Modelldaten mit setInput in die Tabelle ein. Diese speichert die Daten und informiert den IStructuredContentProvider darüber,
dass sich die Daten geändert haben. Er kann jetzt auf die Datenänderung reagieren,
falls das erforderlich sein sollte. Wenn die Tabelle die Daten zu einem späteren Zeitpunkt anzeigen möchte, dann kann sie mit den bei setInput gespeicherten Daten aber
nicht direkt etwas anfangen. So fordert sie eine Arrayrepräsentation der gepsicherten Daten durch Aufruf der Methode getElements an. Der IStructuredContentProvider
agiert also im einfachsten Fall als Konverter für die Modelldaten.
Ein kleines Beispiel soll die Anwendung des Prinzips zeigen. Sie finden den Quelltext
in den Paketen tableViewer.person.viewer und tableViewer.person.common
des Eclipse-Projektes zur Vorlesung. Das Beispiel verwaltet die Personenobjekte aus
der Einführung in diesem Kapitel. Dazu implementiert PersonContentProvider die
Schnittstelle IStructuredContentProvider mit ihren drei Methoden:
getElements konvertiert die Modelldaten in ein Array mit Objekten. Jeder Eintrag
des Arrays entspricht einer Tabellenzeile.
In dispose kann der Konverter eventuell angelegte Ressourcen wieder freigeben,
bevor er selbst freigegeben wird.
87
3.7 Model-View-Controller mit JFace
inputChanged wird von dem TableViewer aufgerufen, wenn neue Daten in die
Tabelle eingetragen werden.
Im Beispiel ist lediglich die Konvertierungsmethode getElements von Interesse, da hier
keine Ressourcen verwaltet und auch keine Änderungen gespeichert werden.
public class PersonContentProvider
implements IStructuredContentProvider {
@Override
public Object[] getElements(Object inputElement) {
return ((List<Person>) inputElement).toArray();
}
@Override
public void dispose() {
// keine Ressourcen angelegt
}
}
@Override
public void inputChanged(Viewer viewer, Object oldInput,
Object newInput) {
// interessiert uns hier nicht
}
Jetzt kann der Konverter verwendet werden. Dazu wird eine ArrayList mit einigen
Personenobjekten als unser Modell gefüllt. Danach wird der TableViewer im Layout
angelegt. Er wiederum erzeugt intern die benötigte Table aus dem SWT. Abschließend
erhält der TableViewer den Konverter übergeben.
public class TableJFacePersonApplication
extends ApplicationWindow {
private TableViewer tableViewer;
private ArrayList<Person> personModel
= new ArrayList<Person>();
protected Control createContents(Composite parent) {
personModel.add(new Person("Name 1", 42));
personModel.add(new Person("Name 2", 66));
tableViewer = new TableViewer(parent, SWT.FULL_SELECTION);
tableViewer.setContentProvider(new PersonContentProvider());
}
}
// usw.
Abbildung 3.89 zeigt, dass diese Schritte noch nicht ausreichen.
88
3.7 Model-View-Controller mit JFace
Fertig? Nein:
Abbildung 3.89: Ausgabe der JFace-Tabelle (noch unvollständig)
Die Tabelle ist nicht zu sehen, weil JFace nicht automatisch die Spalten erzeugen kann.
Dass muss dann doch wie bei SWT manuell geschehen. In JFace dient hierzu die Klasse TableViewerColumn, deren Objekte jeweils eine Spalte darstellen. Die Spalten
erhalten als ersten Parameter wie immer das Vaterobjekt, was eigentlich immer der
TableViewer ist. Der zweite Parameter bestimmt die Ausrichtung des Spalteninhaltes
(rechts, links, zentriert).
Table table = tableViewer.getTable();
// Fortsetzung der Methode createContents
// aus dem vorherigen Quellcode.
TableViewerColumn column;
column = new TableViewerColumn(tableViewer, SWT.LEFT);
column.getColumn().setText("Name");
column.getColumn().setWidth(200);
column = new TableViewerColumn(tableViewer, SWT.RIGHT);
column.getColumn().setText("Age");
column.getColumn().setWidth(80);
table.setHeaderVisible(true);
table.setLinesVisible(true);
// sichtbare Spaltentitel
// sichtbare Linien
Auch das ist noch nicht ausreichend. Die Tabelle erhält durch den Konverter zwar ein
Array aller Modellobjekte, kann aber nicht selbständig entscheiden, wie die Attribute der
einzelnen Objekte in den Zeilen dargestellt werden sollen.
3.7.2.2
View
Nachdem der vorherige Abschnitt beschrieben hat, wie die Tabelle erzeugt wird und die
wie das Modell die Daten bereitstellt, soll jetzt die Darstellung der Modelldaten untersucht
werden.
89
3.7 Model-View-Controller mit JFace
Table
TableColumn
stellt dar
1
stellt dar
1
TableViewerColumn
holt Daten über
1
SWT
<<abstract>>
CellLabelProvider
TableViewer
1 gehört zu
eingeben
<<abstract>>
EditingSupport
stellt dar
1
1
<<interface>>
IContentProvider
<<interface>>
IStructuredContent
Provider
MyEditingSupport
verwendet 1
ColumnLabelProvider
<<abstract>>
CellEditor
MyContentProvider
MyLabelProvider
TextCellEditor
ModelData
Eingabe
Modell
Darstellung
Abbildung 3.90: Ansichtsklassen für die JFace-Tabelle
Die Idee hier ist auch nicht sonderlich kompliziert. Für jede Spalte muss ein Formatierer
bereitgestellt werden, der aus einem (oder mehreren) Attributen der Modellklasse einen
String erzeugt, der dann in genau dieser Zelle angezeigt wird. Man kann sich leicht vorstellen, dass beispielsweise Datumswerte, die im Modell als Date-Objekte abgelegt sind,
ganz unterschiedlich angezeigt werden können: Soll es in einem kurzen oder langen Format dargestellt werden bzw. welche länderspezifische Einstellungen ist vorhanden?
Auch hier wird wieder nur ein Ansatz betrachtet, der ab Eclipse 3.3 funktioniert. Für ältere Versionen existieren ebenso Lösungen, die aber durch neuere überflüssig geworden
sind. Jeder Formatierer ist eine Klasse, die von CellLabelProvider erbt. Die Klasse
besitzt einige Methoden, die überschrieben werden können.
getFont liefert den Zeichensatz zurück, mit dem die Werte in dieser Spalte dargestellt werden sollen.
getBackground gibt die Hintergrundfarbe für die Spaltenwerte zurück.
getForeground bestimmt die Vordergrundfarbe für die Spaltenwerte.
getImage liefert als Ergebnis ein Bildobjekt zur Darstellung der Werte.
getText ist sicherlich die wichtigste Methode. Sie gibt den fertig formatierten String
zurück, der den Wert eines oder mehrerer Attribute des Modells darstellt.
Alle Methoden bekommen das Modellobjekt als einzigen Parameter übergeben. Es müssen nur die Methoden überschrieben werden, die für die Anwendung relevant sind. Soll
beispielsweise kein Bild verwendet werden, dann muss getImage nicht überschrieben.
Das gilt auch für die Farben und den Zeichensatz. Diese Methoden sind nur dann erforderlich, wenn die Vorgaben abgewandelt werden sollen.
Der Entwickler erstellt für jede Spalte einen eigenen Formatierer und registriert ihn an
dieser. Das Beispiel mit den Personen lässt sich so fortsetzen, wobei statt separater
Klassen auch anonyme innere Klassen möglich sind.
Der Name der Person wird aus dem übergebenen Objekt entnommen. Dazu muss
das Datenobjekt in ein Person-Objekt „gecastet“ werden, so dass das Namensattribut ausgelesen werden kann.
90
3.7 Model-View-Controller mit JFace
// für den Namen einer Person
public class PersonNameLabelProvider
extends ColumnLabelProvider {
public String getText(Object element) {
return ((Person) element).getName();
}
}
Das Alter der Person ist ein ganzzahliger Wert. Er muss in einen String umgewandelt
werden.
// für das Alter einer Person
public class PersonAgeLabelProvider
extends ColumnLabelProvider {
public String getText(Object element) {
return String.valueOf(((Person) element).getAge());
}
}
Jetzt müssen die beiden Formatierer noch an den Spalten registriert werden. column ist
eine Spalte der Klasse TableViewerColumn aus dem Codefragment direkt nach Abbildung 3.89. Wichtig ist, nach dem Registrieren noch die Modelldaten dem TableViewer
zu übergeben.
// ...
// An den Spalten registrieren, hier exemplarisch
// für die Namensspalte:
column.setLabelProvider(new PersonNameLabelProvider());
// Danach müssen die Modelldaten dem TableViewer
// übergeben werden.
tableViewer.setInput(personModel);
Jetzt sieht die Darstellung schon ganz gut aus (Abbildung 3.91).
Abbildung 3.91: Ausgabe der JFace-Tabelle mit Formatierer
Dynamische Spaltenbreiten
Was passiert, wenn der Anwender die Größe des Dialogs verändert? Die Spaltenbreiten
der Tabelle passen sich nicht an, so dass neben der Tabelle im Falle der Vergrößerung
ungenutzter Platz entsteht. Abbildung 3.92 zeigt das Problem.
91
3.7 Model-View-Controller mit JFace
Dynamische Spaltenbreiten?
Abbildung 3.92: Fehlende dynamische Spaltenanpassung bei einer Tabelle
Zur Lösung des Problems besitzt JFace den speziell für diese Tabellen entworfenen
Layout-Manager TableColumnLayout. Es ist in der Lage, die Spalten einer Tabelle automatisch an den zur Verfügung stehenden Platz anzupassen. Dazu muss allerdings die Tabelle das alleinige Widget in einem Composite mit TableColumnLayout
sein. Dann werden die Spaltenbreiten entweder absolut in Pixeln durch Objekte der
Klasse ColumnPixelData oder prozentual bezogen auf den zur Verfügung stehenden Platz durch Objekte der Klasse ColumnWeightData festgelegt. Es können auch
minimale Spaltenbreiten erzwungen werden, damit bestimmte Inhalte immer sichtbar
sind. Die erforderlichen Attribute zur Steuerung des Verhaltens lassen sich entweder
direkt setzen oder aber schon im Konstruktor der jeweiligen Klasse übergeben. Die APIDokumentation zeigt die Details.
Die Implementierung TableJFacePersonResizableColumnsApplication erweitert den aktuellen Stand der Personenanwendung TableJFacePersonApplication
so, dass Spalten prozentuale Breiten zugewiesen bekommen und dass sich die Spalten
dynamisch anpassen.
Zunächst muss die Tabelle jetzt in einem separaten Composite-Objekt erzeugt werden,
das die Tabelle mittels TableColumnLayout anordnet.
Composite tableComp = new Composite(parent, SWT.NONE);
TableColumnLayout tcl = new TableColumnLayout();
tableComp.setLayout(tcl);
tableViewer = new TableViewer(tableComp, SWT.FULL_SELECTION);
Die Spaltenbreiten werden direkt dem Layout und nicht den Spalten selbst übergeben.
In diesem Beispiel sollen relative Spaltenbreiten verwendet werden, die sich bei dynamischer Breitenänderung automatisch anpassen. Eine Minimalbreite wird nicht erzwungen.
// SWT-Tabelle auslesen
Table table = tableViewer.getTable();
// erste Spalte erzeugen (linksbündig)
TableViewerColumn column = new TableViewerColumn(tableViewer,
SWT.LEFT);
// Spaltentitel eintragen
column.getColumn().setText("Name");
// Die Spalte belegt immer 70% der Platzes.
// Eine Größenanpassung ist erlaubt.
tcl.setColumnData(column.getColumn(),
new ColumnWeightData(70, true));
Jetzt simmt das Verhalten (Abbildung 3.93).
92
3.7 Model-View-Controller mit JFace
Abbildung 3.93: Korrekte dynamische Spaltenanpassung bei einer Tabelle
Sortierung
In vielen Anwendungen wie beispielsweise dem Explorer unter Windows oder dem Dateimanager und LinuX kann der Anwender die Tabellendaten anhand der Werte einer
Spalte sortiert anzeigen, indem er auf den entsprechenden Spaltentitel klickt (siehe Abbildung 3.94).
Klick auf Namen-Header
Klick auf Alter-Header
Abbildung 3.94: Sortierung einer Tabelle
Solch ein Verhalten lässt sich auch für JFace-Tabellen implementieren. Dazu muss der
Entwickler der Tabelle im Wesentlichen vorgeben, wie einzelne Werte in den Spalten verglichen werden sollen. Da Spalten ja ganz unterschiedliche Werte darstellen, kann JFace
das nicht automatisch für alle möglichen Datentypen selbst durchführen. Der Ablauf sieht
so aus:
Ein „Sortierer“ (eigentlich handelt es sich eher um einen Vergleicher) erbt von der
Basisklasse ViewerSorter und überschreibt deren compare-Methode. Diese Methode wird von der Tabelle immer aufgerufen, um paarweise zwei Spaltenwerte miteinander zu vergleichen. Der „Sortierer“ muss darüber hinaus in der Lage sein, sowohl aufsteigende als auch absteigende Sortierungen zu unterstützen. Das erwartete Verhalten besteht nämlich darin, bei jedem Klick auf denselben Spaltentitel die
Sortierreihenfolge umzukehren.
Der „Sortierer“ wird am TableViewer registriert.
Schließlich fehlt noch die Auslösung der Sortierung bzw. die Umkehr der Sortierreihenfolge. Dazu wird ein Beobachter (ein SelectionListener) an jedem Tabellentitel registriert. Dieser teilt dem „Sortierer“ mit, anhand welcher Spalte sortiert werden soll und aktualisiert anschließend die Tabellendarstellung. Der Sortierer muss
93
3.7 Model-View-Controller mit JFace
intern unterscheiden, ob direkt hintereinander anhand derselben Spalte sortiert wird.
Dann muss er jedes Mal die Reihenfolge umdrehen.
Prinzipiell kann für jede Spalte ein eigener „Sortierer“ erstellt werden. Im folgenden Beispiel dagegen kann der „Sortierer“ für alle Spalten verwendet werden, weil das Modell
nicht sehr komplex ist.
Die Klasse PersonSorter stellt in diesem Beispiel den „Sortierer“ dar.
public class PersonSorter extends ViewerSorter {
// mögliche Sortiereihenfolgen: aufsteigend
// und absteigend
public enum Direction { ASCENDING, DESCENDING };
// Aktuelle Spalte, anhand derer sortiert wurde.
// Bleibt bei zwei Aufrufen die Spalte gleich,
// dann ändert sich die Sortierreihenfolge.
private int sortColumn;
// Aktuelle Sortierreihenfolge
private Direction sortDirection = Direction.DESCENDING;
// Diese Methode wird bei jedem Klick in einen Spaltentitel
// vom Beobachter aufgerufen. Wenn sich die Spalte ändert,
// wird immer aufsteigend sortiert. Ansonsten wird die
// Sortierreihenfolge umgedreht.
public void doSort(int column) {
if (column == sortColumn) {
sortDirection = (sortDirection == Direction.ASCENDING)
? Direction.DESCENDING
: Direction.ASCENDING;
}
else {
sortColumn = column;
sortDirection = Direction.ASCENDING;
}
}
// Diese Methode wird von der Tabelle aufgerufen, um
// paarweise zwei Werte einer Spalte miteinander zu
// vergleichen. Die Methode verhält sich bezüglich der
// Rückgabewerte exakt so wie die entsprechende Methode
// der Java-Klasse java.util.Comparator.
public int compare(Viewer viewer, Object object1,
Object object2) {
int result = 0;
Person person1 = (Person) object1;
Person person2 = (Person) object2;
switch (sortColumn) {
case PersonIndices.NAME_INDEX:
result = getComparator().compare(person1.getName(),
person2.getName());
break;
case PersonIndices.AGE_INDEX:
result = person1.getAge() - person2.getAge();
break;
}
result = sortDirection == Direction.DESCENDING
? result : -result;
94
3.7 Model-View-Controller mit JFace
}
}
return result;
Der „Sortierer“ kann in seiner Standardimplementierung auch Objekte zu Gruppen (Kategorien) zusammenfassen und innerhalb der Gruppen sortieren. Nähere Informationen
liefert die API-Dokumentation.
Jetzt muss der „Sortierer“ am TableViewer registriert werden.
final PersonSorter sorter = new PersonSorter();
tableViewer.setSorter(sorter)
Abschließend darf der Beobachter nicht fehlen, damit die Klicks auf die Spaltentitel auch
erfasst werden. Das Beispiel zeigt nur die Anmeldung an der Namens-Spalte.
column.getColumn().addSelectionListener(
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Zu sortierende Spalte in den "Sortierer" eintragen.
sorter.doSort(PersonIndices.NAME_INDEX);
// Tabelle zwingen, sich neu zu zeichnen.
tableViewer.refresh();
}
});
Filterung
Enthalten Tabellen sehr viele Zeilen, dann möchte der Anwender eventuell irrelevante
Informationen ausblenden. Genau dazu dient der Filter. Im Windows-Explorer wird diese Eigenschaft unter Anderem dazu benutzt, um versteckte Dateien zu verbergen. Der
prinzipielle Ablauf zur Filterung sieht so aus:
Der Entwickler schreibt eine eigene Klasse, die von der abstrakten Basisklasse
ViewerFilter erbt.
In dieser Klasse überschreiben er die Methode select, die bestimmt, ob ein Objekt
(z.B. eine Person) angezeigt werden soll oder nicht. Diese Methode wird von der
Tabelle für jede Zeile aufgerufen.
JFace-Tabellen unterstützen die gleichzeitige Verwendung beliebig vieler Filter. Der
Entwickler kann also alle Filter am TableViewer mit setFilters registrieren.
Die Tabelle befragt für jede Zeile alle Filter, ob das entsprechende Objekt angezeigt
werden soll oder nicht.
Virtuelle Tabellen
Ebenso wie SWT unterstützt auch JFace virtuelle Tabellen, um große Datenmengen effizient handhaben zu können. Der bisher in Abschnitt 3.7.2.1 vorgestellte Ansatz mit dem
IStructuredContentProvider funktioniert dafür aber nicht mehr. Statt dessen kann
der ILazyContentProvider eingesetzt werden, der nicht mehr alle Modelldaten, sondern einzelne Zeilen zurückliefert. Um hier auch ein eventuell zeitaufwändiges Sortieren
und Filtern zu ermöglichen, kann der DeferredContentProvider bei solchen Tabellen das Sortieren und Filtern in einem Hintergrundthread vornehmen.
95
3.7 Model-View-Controller mit JFace
Gestaltung der Zellenwerte
Möchte der Entwickler das Aussehen der Zellinhalte noch viel freier gestalten, als es
der CellLabelProvider zulässt, dann muss er den StyledCellLabelProvider
einsetzen und das Zeichnen des Zelleninhaltes selbst übernehmen. Eine weitere Möglichkeit besteht darin, andere Widgets zur Darstellung der Zellinhalte zu verwenden. Das
ist etwas komplizierter und soll hier nicht besprochen werden.
3.7.2.3
Eingabe
JFace verwendet intern einen Controller, der Modell und Ansicht steuert. Daher finden
Sie an dieser Stelle auch keinen Abschnitt zum Controller. Statt dessen wird diskutiert,
wie JFace-Tabellen auch Benutzereingaben unterstützen.
Table
TableColumn
stellt dar
1
stellt dar
1
TableViewerColumn
holt Daten über
1
SWT
<<abstract>>
CellLabelProvider
TableViewer
1 gehört zu
eingeben
<<interface>>
IContentProvider
<<abstract>>
EditingSupport
stellt dar
1
1
<<interface>>
IStructuredContent
Provider
MyEditingSupport
verwendet 1
ColumnLabelProvider
<<abstract>>
CellEditor
MyContentProvider
MyLabelProvider
TextCellEditor
ModelData
Eingabe
Modell
Darstellung
Abbildung 3.95: Eingabe in eine JFace-Tabelle
Die einfachste Art und Weise, einen Editor anzugeben, besteht darin, einen vorhandenen
Editor aus JFace zu verwenden. Für die wichtigsten Eingabearten sind bereits Editoren
vordefiniert:
TextCellEditor: Er erlaubt die einzeilige Eingabe in ein Textfeld. Mit einer geeigneten Konvertierung kann hier von Zahlen über normale Bezeichner und Datumswerte nahezu jeder Datentyp im Modell abgebildet werden.
CheckboxCellEditor: Damit kann der Anwender einen Boole’schen Wert auswählen. Der Datentyp im Modell ist dabei in der Regel ein boolean.
ComboBoxCellEditor: Mit der Combobox lässt sich ein Wert aus einer Menge von
Vorgaben durch ein CCombo-Widget auswählen. Im Gegensatz zu einem Textfeld
kann also die Wahlfreiheit eingeschränkt werden.
ColorCellEditor: Der Anwender wählt eine Farbe durch einen Dialog aus. Im
Modell kann so ein Color-Objekt manipuliert werden.
DialogCellEditor: Sind komplexere Eingaben erforderlich, dann kann ein eigener Dialog dem Anwender diese ermöglichen. So lassen sich beispielsweise zusammengesetzte Modell-Attribute durch mehrere Felder im Dialog erfassen. Die Anzeige
96
3.7 Model-View-Controller mit JFace
des Wertes in der Tabelle übernimmt normalerweise ein Label. Durch Druck auf eine
zusätzliche Taste innerhalb der Zelle wird der Auswahldialog gestartet.
Weitere Editoren können durch Überschreiben von CellEditor selbst implementiert werden.
Die Arbeit mit einem Editor skizziert die Abbildung 3.96.
viewer
TableViewer
editor
ComboBoxCellEditor
editingSupport
EditingSupport
Darf das Attribut, für
das der Editor registriert
ist, in dem Objekt
object verändert
werden?
1.1: canEdit(object)
1.2: getCellEditor()
1.3: getValue(object)
1.4: doSetValue(value)
Eingabe
beendet
2.1: doGetValue()
Wert in den Editor
eintragen
Wert aus dem
Editor auslesen
CellEditor für den
Wert auslesen.
Attributwert aus dem
Objekt object lesen
In das Attribut des
Objektes object, für
das der Editor registriert,
den neuen Wert value
eintragen
2.2: setValue(object, value)
Abbildung 3.96: Interaktion mit einem Editor in einer JFace-Tabelle
Die Klasse EditingSupport ist hier die zentrale Klasse. Sie hat vier wesentliche Aufgaben:
1. Sie teilt der Tabelle mit, ob ein Attribut überhaupt bearbeitet werden kann. Vor der
eigentlichen Interaktion ruft die Tabelle daher die Methode canEdit auf. Nur wenn
eine Bearbeitung zugelassen ist, dann sind die folgenden Punkte relevant.
2. EditingSupport stellt der Tabelle den eigentlichen Editor zur Verfügung. Dazu
ruft die Tabelle die Methode getCellEditor auf und übergibt das zu bearbeitende
Modellobjekt.
3. Damit die Tabelle den Editor mit dem richtigen Modellwert starten kann, benötigt sie
diesen Wert. Er wird vom EditingSupport bereitgestellt, indem seine Methode
getValue aufgerufen wird.
4. Nach Beendigung der Eingabe durch den Anwender muss der neue Wert wiederum in das Modellobjekt eingetragen werden. Hierzu ruft die Tabelle die Methode
setValue der Klasse EditingSupport auf.
Somit sieht der typische Interaktionsablauf mit dem EditingSupport anhand des Diagramms 3.96 so aus:
1.1 Die Tabelle fragt im EditingSupport nach, ob der Anwender den Wert überhaupt
verändern darf.
1.2 Ist die Eingabe zulässig, dann fordert die Tabelle den eigentlichen Editor an, mit
dessen Hilfe der Anwender seine Daten verändern kann.
97
3.7 Model-View-Controller mit JFace
1.3 Anschließend fragt die Tabelle den EditingSupport nach dem zu bearbeitenden
Attribut und übergibt dazu das komplette Modellobjekt (die Zeile).
1.4 Den erhaltenen Wert trägt die Tabelle in den Editor ein und startet die Bearbeitung
durch den Anwender.
2.1 Ist die Bearbeitung abgeschlossen, dann liest die Tabelle den neuen Wert aus dem
Editor aus.
2.2 Jetzt muss nur noch der neue Wert wieder in das Modellobjekt kommen. Dazu verwendet die Tabelle wiederum den EditingSupport, der den neuen Wert sowie
das Modellobjekt übergeben bekommt und den Wert in das Objekt einträgt.
Mein Lesen der Erklärungen fällt vielleicht auf, dass nirgendwo eine Möglichkeit beschrieben wurde, wie der EditingSupport herausfinden kann, welche Spalte und damit welches Attribut bearbeitet werden soll. Das ist ziemlich einfach zu beantworten: Die API
sieht die Unterscheidung nicht vor. Im einfachsten Fall wird daher für jede Spalte ein
eigener EditingSupport geschrieben. Damit ist die Zuordnung wieder eindeutig.
Dieser Abschnitt soll noch mit einem kleinen Beispiel abgeschlossen werden. Zunächst
bekommt die Person aus den vorherigen Beispielen ein zusätzliches Attribut, damit später auch eine Combobox zur Eingabe verwendet werden kann. Das Attribut nimmt den
Beschäftigungszustand der Person in Form eines Aufzähltyps auf.
public class Person {
public enum Status { STUDYING, WORKING, RETIRED, DEAD };
}
private
private
private
// usw.
String name;
int
age;
Status status;
(Getter, Setter)
Abbildung 3.97 zeigt das angestrebte Ergebnis.
Abbildung 3.97: Anzeige und Eingabe in eine JFace-Tabelle
Der erste EditingSupport handhabt die Eingabe des Namens durch ein Textfeld. Somit wird als Editor TextCellEditor eingesetzt.
public class PersonNameEditingSupport extends EditingSupport {
// Zur Bearbeitung des Namens wird ein Text-Widget verwendet.
private TextCellEditor cellEditor;
public PersonNameEditingSupport(TableViewer viewer) {
super(viewer);
// Das Vaterelement des Editors ist die Tabelle.
98
3.7 Model-View-Controller mit JFace
}
cellEditor = new TextCellEditor(viewer.getTable());
// Der Name kann immer bearbeitet werden.
protected boolean canEdit(Object element) {
return true;
}
// Rückgabe des eigentlichen Editors.
protected CellEditor getCellEditor(Object element) {
return cellEditor;
}
// Das Namensattribut des Objektes auslesen.
protected Object getValue(Object element) {
return ((Person) element).getName();
}
}
// Den neuen Wert in das Namensattribut eintragen und
// den TableViewer veranlassen, die Zeile mit dieser
// Person neu zu zeichnen.
protected void setValue(Object element, Object value) {
((Person) element).setName((String) value);
getViewer().update(element, null);
}
Jetzt muss der Editing-Support noch an der Spalte registrieren, für die er die Steuerung
der Eingaben übernimmt.
tableColumn.setEditingSupport(
new PersonNameEditingSupport(tableViewer));
Der Beschäftigungsstand wird durch ein Label angezeigt. Möchte der Anwenden diesen
ändern, dann erscheint als Editor eine Combobox (Klasse ComboBoxCellEditor), die
alle Auswahlmöglichkeiten des Aufzähltyps anbietet. Wichtig ist hier zu wissen, dass die
Tabelle als eingegebenen Wert immer den ausgewählten Index der Combobox übergibt.
public class PersonStatusEditingSupport extends EditingSupport {
// Zur Bearbeitung des Status wird intern
// ein CCombo-Widget verwendet.
private ComboBoxCellEditor cellEditor;
public PersonStatusEditingSupport(TableViewer viewer) {
super(viewer);
// Der Benutzer darf keinen Wert manuell in
// die Combobox eingeben. Es ist lediglich eine
// Auswahl aus den vorhandenen Werten möglich.
cellEditor = new ComboBoxCellEditor(viewer.getTable(),
person.getStatusValuesAsStringArray(),
SWT.READ_ONLY);
}
// Auch der Status kann immer bearbeitet werden.
protected boolean canEdit(Object element) {
return true;
}
// Rückgabe des eigentlichen Editors.
protected CellEditor getCellEditor(Object element) {
99
3.8 Weitere Container
}
return cellEditor;
// Den Wert des Statusattributes auslesen und
// als Index innerhalb der Combobox zurueck geben.
protected Object getValue(Object element) {
return ((Person) element).getStatus().ordinal();
}
}
// Den neuen Wert in das Statusattribut eintragen und
// den TableViewer veranlassen, die Zeile mit dieser
// Person neu zu zeichnen. Die Methode setStatus
// der Personenklasse nimmt die Konvertierung
// des Index aus der Combobox in den Aufzählwert vor.
protected void setValue(Object element, Object value) {
((Person) element).setStatus((Integer) value);
getViewer().update(element, null);
}
Der EditingSupport muss noch an der Spalte registriert werden. Das vollständige
Beispiel, das auch die Eingabe des Altersattributes unterstützt, ist in den beiden Paketen
tableViewer.person.editing und tableViewer.person.common des Quelltextes zur Vorlesung zu finden.
3.7.3
Attributierter Text ohne MVC
Dieses Kapitel folgt, wenn alle anderen, die für die Vorlesung relevant sind, fertiggestellt
sind.
3.7.4
Attributierter Text mit MVC
Dieses Kapitel folgt, wenn alle anderen, die für die Vorlesung relevant sind, fertiggestellt
sind.
3.8 Weitere Container
100
4
Anbindung an die Geschäftslogik
5
Deklarative Beschreibungen
6
Eclipse Rich Client Platform
7
Multithreading in Java
Java besitzt eine sehr mächtige und umfangreiche API zur Erstellung multithreadingfähiger Programme. In Java 5 wurde diese API nochmals erweitert. In den folgenden
Abschnitten werden nur die wichtigsten Grundfunktionalitäten vorgestellt. Die Pakete
java.util.concurrent und deren Unterpakete werden hier aus Platzgründen ebenso wenig betrachtet wie viele andere Eigenschaften von Threads.
7.1 Threads erzeugen und starten
Ein Thread ist ein Objekt der Klasse Thread. Im Wesentlichen wird ein Thread auf zwei
verschiedene Arten erzeugt:
1. Eine Klasse erbt von Thread und überschreibt die Methode run. Diese implementiert die (Endlos-)Schleife des Threads und wird beim Start aufgerufen.
2. Eine Klasse implementiert die Schnittstelle Runnable und überschreibt ebenso die
Methode run. Ein Objekt der Klasse wird dem Konstruktor von Thread übergeben.
Beispiel:
public class MyThread extends Thread {
public void run() {
while (true) {
// Implementierung
}
}
}
Der Thread wird schließlich mit start gestartet.
Beispiel:
MyThread mThr = new MyThread();
mThr.start();
Die Thread-Priorität liegt zwischen MIN_PRIORITY (0) und MAX_PRIORITY (10). Im
Standardfall beträgt sie NORM_PRIORITY (5).
7.2 Threads beenden und löschen
7.2 Threads beenden und löschen
Die einzige in aktuellen Java-Versionen unterstützte Möglichkeit, einen Thread zu beenden, besteht darin, dass der Thread seine run-Methode selbst verlässt. Ein Problem tritt
auf, wenn der Thread durch einen anderen terminiert werden soll. Als Ausweg kann einem Thread ein Unterbrechungssignal gesendet werden. Der ausführende Thread testet
zyklisch auf das Vorhandensein des Signals und beendet sich selbst.
Beispiel:
public class MyThread extends Thread {
public void run() {
while (true) {
if (interrrupted()) {
return;
// beenden
}
// ...
}
}
}
Dem Thread wird mit interrupt das Signal zur Unterbrechung geschickt.
Beispiel:
MyThread mThr = new MyThread();
mThr.start();
// ...
mThr.interrupt();
Ein Thread läuft häufig nicht permanent sondern legt sich für eine gewisse Zeit „schlafen“.
Aus diesem Schlafzustand wird er ebenso mit interrupt geweckt. Um zwischen einem
„normalen“ Aufwecken aus dem Schlafzustand und einem Signal zur Beendigung der
Arbeit unterscheiden zu können, lässt sich der Thread sehr einfach durch Hinzufügen
einer Boole’schen Variablen erweitern.
Beispiel (Thread):
public class MyThread extends Thread {
private boolean terminated = false;
public void run() {
while (!terminated) {
// Aufwecken aus dem Schlafzustand
try {
sleep(2000);
}
catch (InterruptedException ex) {
if (!terminated) {
// Arbeit nach dem Wecken
}
}
// Arbeiten
// Test auf Aufwecken während der Arbeit
if (interrupted() && terminated) {
return;
}
// Arbeiten
}
}
105
7.3 Zugriff auf gemeinsam genutzte Ressourcen
}
public void terminate() {
terminated = true;
interrupt();
}
Der Aufruf der terminate-Methode teilt dem Thread mit, dass er sich beenden soll. Dabei ist zu beachten, dass ein aktiver Thread unter Umständen erst nach einer gewissen
Zeit das Stopsignal sieht. Es ist daher sinnvoll, die Variable terminated in regelmäßigen Abständen zu überprüfen.
7.3 Zugriff auf gemeinsam genutzte Ressourcen
In Java dürfen verschiedene Thread nur unter sehr genau definierten Bedingungen gemeinsam auf Variablen zugreifen. Sicher ist immer der synchronisierte Fall, wie er in
Abschnitt 7.4 vorgestellt wird. Allerdings kostet die Synchronisation sehr viel Zeit. Glücklicherweise gibt es bestimmte Szenarien, in denen sie nicht erforderlich ist. Beispielsweise ist der Zugriff auf Refenrenzen sowie alle primitiven Datentypen außer long und
double atomar. Das heißt, dass während eines solchen Zugriffs kein Kontextwechsel
erfolgt. Damit könnte die Diskussion eigentlich beendet sein. Problem treten aber besonders auf Prozessoren mit mehreren Kernen auf. Es zwar garantiert, dass die Zugriffe
unteilbar sind. Es ist aber nicht garantiert, dass ein Thread, der nach dem Schreiben
lesend auf die Variable zugreift, die Änderung überhaupt sehen wird. Der Quelltext unterstellt die sogenannte „Sequential Consistency“. Damit ist gemeint, dass ein Thread die
Änderungen eines anderen Threads, die dieser vorher vorgenommen hat, sehen kann.
Solch ein Modell wird von der virtuellen Maschine aber gar nicht unterstützt. Das hängt
damit zusammen, dass jeder Thread seinen eigenen Speicher besitzt. Dieser kann sich
im Falle von Mehrkern-Prozessoren durchaus auch auf verschiedenen Prozessorkernen
befinden. Aus Geschwindigkeitsgründen werden diese separaten Speicher nicht nach jedem Zugriff untereinander abgeglichen. Der lokale Speicher wird nur dann automatisch
mit dem Hauptspeicher synchronisert, wenn sich der Thread beendet. Das ist in der Regel aber viel zu spät. Für ein manuelles Zurückschreiben der Änderungen reicht es aus,
eine gemeinsam genutzte Variable als volatile zu kennzeichnen.
volatile int sharedMemory = 42;
Beschreibt ein Thread solch eine Variable, dann wird sein gesamter Zustand inklusive
der als volatile deklarierten Variablen in den Hauptspeicher zurück geschrieben. Liest
ein Thread dagegen solch eine Variable, wird zunächst der Inhalt der Variablen aus dem
Hautpspeicher erneut gelesen, weil er sich inzwischen ja geändert haben kann. Dabei
wird aber nicht nur der Inhalt dieser einen Variable gelesen, sondern es wird der gesamte Arbeitsspeicher des Threads aufgefrischt. Der Aufwand beim Zugriff auf volatileVariablen ist also nicht zuvernachlässigen. Es ist aber deutlich geringer als bei manueller
Synchronisation (siehe Abschnitt 7.4).
Statt Variablen, die als volatile deklariert werden, können seit Java 5 auch Klassen
aus dem Paket java.util.concurrent.atomic verwendet werden. Diese kapseln
primitive Daten mit thread-sicherem Zugriff und bieten darüber hinaus auch weitere Operationen auf den Daten an (z.B. getAndIncrement).
106
7.4 Synchronisation von Threads
Bei Konstanten ist der Fall einfacher: Bei ihrer Initialisierung werden die Daten im Hauptspeicher aktualisiert, und alle Thread können dann problemlos darauf zugreifen, weil der
erste Zugriff ein Auffrischen des thread-eigenen Speichers bewirkt.
7.4 Synchronisation von Threads
Zur einfachen Synchronisation von Threads existieren in der Basisklasse aller Klassen
Object mehrere Methoden. Das Objekt, auf dem die Methoden aufgerufen werden,
dient dabei als Monitor. Alle folgenden Methoden sollten nur innerhalb synchronisierter
Blöcke oder Methoden, die den Monitor als Synchronisationsobjekt verwenden, aufgerufen werden. Synchronisierte Methoden und Blöcke werden in den Folgeabschnitten
beschrieben. Wird die Synchronisation verwendet, dann ist sichergestellt, dass die beteiligten Threads auch auf Mehrkern-Prozessoren immer mit den richtigen Speicherwerten
arbeiten, weil jede manuelle Synchronisation automatisch ein Rückschreiben und eine
Auffrischung der Thread-Daten erzwingt.
wait: Beim Aufruf der Methode wird der gerade aktive Thread an diesem Synchronisationsobjekt blockiert. Optional ist die Angabe einer maximalen Wartezeit. Da
diese Methode nur innerhalb eines synchronisierten Abschnitts (Block oder Methode) aufgerufen werden soll, wird beim Blockieren der Abschnitt wieder freigegeben.
Sobald der Thread deblockiert wird, wird er wieder Eigentümer des Abschnitts und
blockiert ihn somit. wait löst eine InterruptedException aus, wenn der wartende Thread ein Unterbrechungssignal während seines Wartens empfangen hat.
notify: Ein am Monitor wartender Thread wird deblockiert. Im Falle mehrerer an
diesem Monitor wartender Threads hängt es von der Implementierung der virtuellen
Maschine ab, welcher Thread deblockiert wird.
notifyAll: Alle am Monitor wartenden Threads werden wieder freigegeben. Die
Threads bekommen der Reihe nach den synchronisierten Abschnitt zugewiesen, so
dass sichergestellt ist, dass sich immer nur ein Thread in dem kritischen Abschnitt
befindet.
In Java 5 wurden weitere Klassen zur Synchronisation eingeführt. Diese befinden sich
im Paket java.util.concurrent und dessen Unterpaketen.
7.4.1
Synchronisierte Methoden
Sie stellen sicher, dass nur ein Thread zu einem Zeitpunkt die Methode betreten kann.
Beispiel für synchronisierte Methoden:
public class Timer {
// ...
public synchronized void set(long time) {
/* ... */
}
public synchronized void set(Date date) {
/* ... */
}
}
107
7.5 Threads als Daemons
Befinden sich mehrere Methoden mit dem Merkmal synchronized in einer Klasse, so
ist sichergestellt, dass immer nur eine dieser Methoden zu einem Zeitpunkt aufgerufen
werden kann. Die Methode verwendet das Objekt, auf dem sie aufgerufen werden, als
Monitor.
Die set-Methode aus dem vorherigen Beispiel entspricht somit:
public void set(long time){
synchronized (this) {
/* ... */
}
Solche synchronisierten Blöcke sind im Folgeabschnitt beschrieben.
Wird eine statische Methode als synchronized markiert, so wird als Monitor das Klassenobjekt der Klasse verwendet (Klasse java.lang.Class). So ist sichergestellt, dass
nur eine synchronisierte, statische Methode der Klasse zu einem Zeitpunkt ausgeführt
werden kann,
7.4.2
Synchronisierte Blöcke
Es handelt sich um Monitore, die einen gegenseitigen Ausschluss mehrerer Threads in
diesem Block bewirken. Die Synchronisation findet immer an einem Objekt statt, wobei
das Objekt als der Monitor agiert.
Beispiel, in dem object eine Referenz auf das Monitorobjekt ist:
synchronized (object) {
// Kritischer Abschnitt
}
7.5 Threads als Daemons
Ein Thread wird durch den Aufruf der Methode setDaemon als so genannter Daemon
(„Dämon“) markiert. Dieses hat eine Auswirkung auf die Lebensdauer der Anwendung.
Normalerweise beendet die JVM (Java Virtual Machine) das laufende Programm erst
dann automatisch, wenn alle Threads beendet wurden. Die Ausnahme sind die Daemons, die hierzu nicht berücksichtigt werden. So bietet es sich an, gewisse Teilaufgaben,
die als Hintergrundthreads implementiert sind, als Daemons zu markieren. Sie haben
keinen Einfluss auf die Lebensdauer des Programmes und müssen daher auch nicht
manuell beendet werden. Im Zusammenhang mit Swing stellt sich die Frage der Beendigung häufig nicht, da der Event-Thread ohnehin nicht als Daemon läuft und so eine
direkte Beendigung der Anwendung durch das Verlassen der main-Methode verhindert.
108
8
Abbildungsverzeichnis
2.1
2.2
2.3
2.4
2.5
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
3.13
3.14
3.15
3.16
3.17
3.18
3.19
3.20
3.21
3.22
3.23
3.24
3.25
3.26
3.27
Kommunikation bei einem Thin-Client . . . . . . . . . . .
Kommunikation bei einem Rich Thin Client . . . . . . . . .
Kommunikation bei einem Rich Fat Client . . . . . . . . .
Architektur einer Fat-Client-Anwendung . . . . . . . . . .
Architekturschichten anhand eines Beispiels . . . . . . . .
Struktur der Oberfläche . . . . . . . . . . . . . . . . . . .
Beispiel unter Windows 7, MacOS X und Linux (GTK) . .
Struktur einer SWT-Anwendung . . . . . . . . . . . . . . .
Zielplattform einstellen . . . . . . . . . . . . . . . . . . . .
Zielplattform einrichten . . . . . . . . . . . . . . . . . . . .
Abhängigkeit von Dateien der Zielplattform . . . . . . . .
Struktur einer JFace-Anwendung . . . . . . . . . . . . . .
Windows 7 . . . . . . . . . . . . . . . . . . . . . . . . . .
LinuX, GNOME 2.28.2 . . . . . . . . . . . . . . . . . . . .
Windows XP . . . . . . . . . . . . . . . . . . . . . . . . .
MacOS X . . . . . . . . . . . . . . . . . . . . . . . . . . .
Englische Texte . . . . . . . . . . . . . . . . . . . . . . . .
Deutsche Texte . . . . . . . . . . . . . . . . . . . . . . . .
Vergrößerter Dialog . . . . . . . . . . . . . . . . . . . . .
Verkleinerter Dialog . . . . . . . . . . . . . . . . . . . . .
Hierarchie Button . . . . . . . . . . . . . . . . . . . . . .
Hierarchie Label . . . . . . . . . . . . . . . . . . . . . . .
Anordnung im Fill-Layout . . . . . . . . . . . . . . . . . . .
Horizontale Anordnung im Fill-Layout . . . . . . . . . . . .
Manuelle Verkleinerung im Fill-Layout . . . . . . . . . . .
Vertikale Anordnung im Fill-Layout . . . . . . . . . . . . .
Anordnung im Row-Layout . . . . . . . . . . . . . . . . . .
Horizontale Anordnung im Row-Layout . . . . . . . . . . .
Manueller Verkleinerung im Row-Layout (ohne Umbruch)
Manueller Verkleinerung im Row-Layout (mit Umbruch) . .
Vertikale Anordnung im Row-Layout . . . . . . . . . . . .
Row-Layout mit veränderter Widget-Breite . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
8
9
11
12
14
16
17
19
19
20
21
25
25
25
25
26
26
26
26
27
27
27
28
28
28
29
29
30
30
30
32
Abbildungsverzeichnis
3.28
3.29
3.30
3.31
3.32
3.33
3.34
3.35
3.36
3.37
3.38
3.39
3.40
3.41
3.42
3.43
3.44
3.45
3.46
3.47
3.48
3.49
3.50
3.51
3.52
3.53
3.54
3.55
3.56
3.57
3.58
3.59
3.60
3.61
3.62
3.63
3.64
3.65
3.66
3.67
3.68
3.69
3.70
3.71
3.72
3.73
3.74
Anordnung im Grid-Layout . . . . . . . . . . . . . . . . . . . .
Beispieldialog für das Grid-Layout . . . . . . . . . . . . . . .
Rasterhilfslinien auf dem Dialog . . . . . . . . . . . . . . . . .
Platzierung des Labels im Grid-Layout . . . . . . . . . . . . .
Platzierung des Textfeldes im Grid-Layout . . . . . . . . . . .
Platzierung einer Taste im Grid-Layout . . . . . . . . . . . . .
Platzierung einer Taste im Grid-Layout ohne Streckung . . . .
Verschachtelung von Containern mit jeweils eigenen Layouts
Beispiel für ineinander geschachtelte Layouts . . . . . . . . .
Größenänderungen bei ineinander geschachtelten Layouts .
Anordnung im Form-Layout . . . . . . . . . . . . . . . . . . .
Beispiel 1 für die Anordnung im Form-Layout . . . . . . . . .
Beispiel 2 für die Anordnung im Form-Layout . . . . . . . . .
Beispiel 3 für die Anordnung im Form-Layout . . . . . . . . .
Automatische Randerkennung im Form-Layout . . . . . . . .
Keine automatische Randerkennung im Form-Layout . . . . .
Screenshot des Beispiels . . . . . . . . . . . . . . . . . . . .
Einfügen des Titels . . . . . . . . . . . . . . . . . . . . . . . .
Einfügen der Abbruch-Taste . . . . . . . . . . . . . . . . . . .
Einfügen der Ok-Taste . . . . . . . . . . . . . . . . . . . . . .
Einfügen des Bildes . . . . . . . . . . . . . . . . . . . . . . .
Einfügen des Textfeldes . . . . . . . . . . . . . . . . . . . . .
Dialog mit Form-Layout . . . . . . . . . . . . . . . . . . . . .
Einfügen des Titels . . . . . . . . . . . . . . . . . . . . . . . .
Einfügen des Textfeldes . . . . . . . . . . . . . . . . . . . . .
Einfügen der Ok-Taste . . . . . . . . . . . . . . . . . . . . . .
Einige Widgets aus dem Nebula-Projekt . . . . . . . . . . . .
Einige Widgets aus der RCPToolbox . . . . . . . . . . . . . .
Komponenten in einem Menü . . . . . . . . . . . . . . . . . .
Radio-Tasten in einem Menü . . . . . . . . . . . . . . . . . .
Ausgabe des ersten Menü-Beispiels . . . . . . . . . . . . . .
Hinzufügen der Checkbox zum Menü . . . . . . . . . . . . . .
Hinzufügen der Radio-Taste zum Menü . . . . . . . . . . . .
Begriffe zur Tastatursteuerung in Menüs . . . . . . . . . . . .
Popup-Menü auf verschiedenen Plattformen . . . . . . . . . .
Menü in einem System-Tray auf verschiedenen Plattformen .
Toolbar auf verschiedenen Plattformen . . . . . . . . . . . . .
Elemente einer Coolbar . . . . . . . . . . . . . . . . . . . . .
Beispiel einer Coolbar . . . . . . . . . . . . . . . . . . . . . .
Vereinfachte Klassenhierachie . . . . . . . . . . . . . . . . . .
Relative Positionierung von Controls . . . . . . . . . . . . . .
Hintergrundfarbe eines Controls . . . . . . . . . . . . . . . . .
Tooltip an einem Control . . . . . . . . . . . . . . . . . . . . .
Gesperrtes Control . . . . . . . . . . . . . . . . . . . . . . . .
Allgemeines Bobachter-Muster . . . . . . . . . . . . . . . . .
Ablauf im Bobachter-Muster . . . . . . . . . . . . . . . . . . .
Ereigniszustellung unter SWT . . . . . . . . . . . . . . . . . .
110
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
35
36
36
37
37
38
38
38
39
40
42
42
43
43
43
44
44
45
45
46
46
47
49
49
49
53
54
54
55
56
57
57
58
59
60
61
62
63
63
65
65
65
65
68
69
69
Abbildungsverzeichnis
3.75
3.76
3.77
3.78
3.79
3.80
3.81
3.82
3.83
3.84
3.85
3.86
3.87
3.88
3.89
3.90
3.91
3.92
3.93
3.94
3.95
3.96
3.97
Aktion für Menüeintrag und Toolbar-Taste . . . . . . . . . .
Darstellungen aus einer Aktion . . . . . . . . . . . . . . . .
Ereigniszustellung unter JFace . . . . . . . . . . . . . . . .
Aktion zur Dokumenterzeugung . . . . . . . . . . . . . . . .
Menü aus Aktionen erzeugen . . . . . . . . . . . . . . . . .
Coolbar aus Aktionen erzeugen . . . . . . . . . . . . . . . .
Ergebnis auf einigen Fenstersystemen . . . . . . . . . . . .
Struktur im MVC-Ansatz . . . . . . . . . . . . . . . . . . . .
Ablauf im MVC . . . . . . . . . . . . . . . . . . . . . . . . .
Wichtige Klassen für eine Tabelle . . . . . . . . . . . . . . .
Wichtige Klassen für eine JFace-Tabelle . . . . . . . . . . .
Modellklassen für die JFace-Tabelle . . . . . . . . . . . . .
JFace-Tabelle für eine Liste von Personen . . . . . . . . . .
Interaktion zwischen JFace-Tabelle und Modell . . . . . . .
Ausgabe der JFace-Tabelle (noch unvollständig) . . . . . .
Ansichtsklassen für die JFace-Tabelle . . . . . . . . . . . .
Ausgabe der JFace-Tabelle mit Formatierer . . . . . . . . .
Fehlende dynamische Spaltenanpassung bei einer Tabelle
Korrekte dynamische Spaltenanpassung bei einer Tabelle .
Sortierung einer Tabelle . . . . . . . . . . . . . . . . . . . .
Eingabe in eine JFace-Tabelle . . . . . . . . . . . . . . . . .
Interaktion mit einem Editor in einer JFace-Tabelle . . . . .
Anzeige und Eingabe in eine JFace-Tabelle . . . . . . . . .
111
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
75
75
75
76
78
78
79
80
80
81
85
86
87
87
89
90
91
92
93
93
96
97
98
9
Literaturverzeichnis
[Dau07]
[ISO3166]
Daum B.: Rich-Client-Entwicklung mit Eclipse 3.3, dpunkt-Verlag, 2007
ISO-Norm 3166 (Ländercodes),
http://www.iso.org/iso/country_codes/
iso_3166_code_lists/
english_country_names_and_code_elements.htm
[ISO639]
ISO-Norm 631 (Sprachcodes),
http://www.loc.gov/standards/iso639-2/php/
code_list.php
[JavaBsp]
Java-Quelltextbeispiele (alles Mögliche): http://www.java2s.com
[JFaceBsp] Code-Beispiele zu JFace
http://wiki.eclipse.org/index.php/JFaceSnippets
[Layouts]
http://www.eclipse.org/articles/article.php?
file=Article-Understanding-Layouts/index.html
[Mar06]
Marinilli M.: Professional Java User Interfaces, Wiley & Sons, 2006
[McA10]
McAffer J., Lemieux J. M.: Eclipse Rich Client Platform, Addison-Wesley
Longman, 2010
[RCPOnline] Online-Version der ersten Seiten eines neuen RCP-Buches http://www.
ralfebert.de/rcpbuch/
[Sca05]
Scarpino M, et.al.: SWT/JFace in Action, Manning Publications Co., 2005
[SWT]
Tutorials und Artikel zu SWT http://www.eclipse.org/swt/
[SWTBsp] Code-Beispiele zu SWT http://www.eclipse.org/swt/snippets/
[SurrPa]
Behandlung von Zeichenketten bei Internationalisierung
http://java.sun.com/mailers/techtips/corejava/2006/
tt0822.html
[UIGuide]
Design-Richtlinien für grafische Oberflächen mit SWT und JFace
http://www.eclipse.org/articles/Article-UI-Guidelines/
Index.html
[War07]
Warner R., Harris R.: The Definite Guide to SWT and JFace, Apress, 2007
Abbildungsverzeichnis
[Wue08]
Wütherich G., Hartmann N., Kolb B., Lübken M.: Die OSGi Service Platform, dpunkt-Verlag, 2008
113
10
Stichwortverzeichnis
Symbole
.NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
Action . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74, 76
run . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
ApplicationWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
addSelectionListener . . . . . . . . . . . . . . . . . . . 71
CCombo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51, 96
CLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Canvas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
CellConstraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
CellEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
CellLabelProvider . . . . . . . . . . . . . . . . . . . . . . . . 90, 96
CheckboxCellEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
ColorCellEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
ColumnPixelData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
ColumnWeightData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
ComboBoxCellEditor . . . . . . . . . . . . . . . . . . . . . . . 96, 99
ComboViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Combo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Composite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38, 53, 67
ControlEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
addSelectionListener . . . . . . . . . . . . . . . . . . . 71
setBackgroundImage . . . . . . . . . . . . . . . . . . . . . . 65
setBackground . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
setEnabled . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
setFont . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
setForeground . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
setLayoutData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
setMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
setToolTipText . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
setVisible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Ereignis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
CoolBarManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
CoolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
CoolItem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61
Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51, 52
DeferredContentProvider . . . . . . . . . . . . . . . . . . . . 95
DialogCellEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
DragDetectEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
EditingSupport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Event
index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
FillLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
FocusEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
FormAttachment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
FormData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40
FormLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39, 47
GridDataFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
GridData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34
GridLayoutFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
GridLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Group . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
HelpEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
ILazyContentProvider . . . . . . . . . . . . . . . . . . . . . . . . 95
IStructuredContentProvider .. . . . . . . . . . . 87, 95
KeyEvent. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .66
Label . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
ListViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52, 85
MenuDetectEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
MenuItem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .55
MenuManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
MouseEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
MouseMoveEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Object
notifyAll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
notify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
wait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
PaintEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
PanelBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
ProgressBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
RowData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
RowLayoutFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
RowLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Scale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
ScrollBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Scrollable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
getHorizontalBar . . . . . . . . . . . . . . . . . . . . . . . . .67
getVertikalBar . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
SelectionAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
SelectionEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
SelectionListener . . . . . . . . . . . . . . . . . . . . . . . . 70, 73
widgetDefaultSelected . . . . . . . . . . . . . . . . . . 71
widgetSelected. . . . . . . . . . . . . . . . . . . . . . . .71, 72
ShellAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
ShellEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
doIt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
ShellListener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
shellActivated . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
shellClosed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
shellDeactivated . . . . . . . . . . . . . . . . . . . . . . . . .74
shellDeiconified . . . . . . . . . . . . . . . . . . . . . . . . .74
shellIconified . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Slider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Spinner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
StyledCellLabelProvider . . . . . . . . . . . . . . . . . . . . 96
StyledText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
TableColumnLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
ColumnPixelData . . . . . . . . . . . . . . . . . . . . . . . . . . 92
ColumnWeightData . . . . . . . . . . . . . . . . . . . . . . . . .92
Stichwortverzeichnis
TableColumn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
setRedraw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
setText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
setWidth. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .84
TableEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
TableItem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81, 82
setText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
TableViewerColumn . . . . . . . . . . . . . . . . . . . . . . . . 89, 91
TableViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85, 88, 91
Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 81, 84, 85
virtuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
TextCellEditor. . . . . . . . . . . . . . . . . . . . . . . . . . . . .96, 98
Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
run . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
ToolBarManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
ToolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
TraverseEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
TrayItem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .59
Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
TreeViewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 85
TypedEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
ViewerFilter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
ViewerSorter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Ereignis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Ereignisbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
A
Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
F
Fat Client
plattformabhängig.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
plattformunabhängig .. . . . . . . . . . . . . . . . . . . . . . . . . . 9
Fenstersystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Fill-Layout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
merginHeight . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
merginWidth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
spacing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Form-Layout .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
FormAttachment . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
FormData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40
Form-Layout (JGoodies) . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
CellConstraints . . . . . . . . . . . . . . . . . . . . . . . . . . 48
PanelBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Dialog Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
G
Geschäftslogik. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .11
Geschachtelte Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Grid-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
GridDataFactory . . . . . . . . . . . . . . . . . . . . . . . . . . 35
GridData. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34
GridLayoutFactory . . . . . . . . . . . . . . . . . . . . . . . 34
H
I
Accelerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55, 57
Anwendungskern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
B
J
C
K
Java Development Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Java ME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Java Runtime Environment . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Java Web Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
JDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
JFace
Tabellen-Modell.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86
JRE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53
Benutzungsschnittstelle .. . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Beobachter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Beobachter-Muster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Betriebssystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Block
synchronisierter . . . . . . . . . . . . . . . . . . . . . . . . 107, 108
Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Kalender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Klassifikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9, 12
C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9, 10
C#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10
Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Aktivierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Farben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Größe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Position . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Tooltip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
Coolbar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61
L
Layout
absolutes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Fill-. . . . . . . . . . . . . . . . . . . . . . . . . .siehe Fill-Layout 27
Form- . . . . . . . . . . . . . . . . . . . . siehe Form-Layout 39
Form- (JGoodies) siehe Form-Layout (JGoodies)
47
Größenänderungen.. . . . . . . . . . . . . . . . . . . . . . . . . .26
Grid- . . . . . . . . . . . . . . . . . . . . . . siehe Grid-Layout 32
Internationalisierung .. . . . . . . . . . . . . . . . . . . . . . . . . 25
Komponentengröße . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Row- . . . . . . . . . . . . . . . . . . . . . . siehe Row-Layout 29
Layout-Manager .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Layouts
Geschachtelte . . siehe Geschachtelte Layouts 38
Listenauswahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
D
Datumseingabe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51
Dialogsteuerung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11, 12
E
Eclipse RCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Ereignis
SelectionAdapter . . . . . . . . . . . . . . . . . . . . . . . . . 73
SelectionEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Beobachter-Muster . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Fenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
115
Stichwortverzeichnis
M
Sortierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
View. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .89
virtuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Texteingabefeld
einzeiliges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Thin Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104, 106
MAX_PRIORITY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
NORM_PRIORITY . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
PN_PRIORITY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
atomare Zugriffe. . . . . . . . . . . . . . . . . . . . . . . . . . . . .106
beenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
blockieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Daemon. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108
erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
freigeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
gegenseitiger Ausschluss . . . . . . . . . . . . . . . . . . . 108
gemeinsame Ressourcen . . . . . . . . . . . . . . . . . . . 106
Priorität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
starten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
synchronisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
unterbrechen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Toolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Menü . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Accelerator . . . . . . . . . . . . siehe Accelerator 55, 58
Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Eintrag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Mnemonic . . . . . . . . . . . . . . siehe Mnemonic 55, 58
Popup- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Radio-Taste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
System-Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Tastatursteuerung .. . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Trennstrich. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .55, 57
Methode
synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
synchronisierte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Mnemonic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55, 57
Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
Monitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
freigeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
warten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Wartezeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107
Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
MVC
Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
View. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79
V
View. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79
N
W
NetBeans Platform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11, 16, 64
Windows CE/Mobile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Windows-Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
O
Objekt
Monitor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108
Synchronisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
X
XAML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
P
Z
Passworteingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Popup-Menü. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .59
Zeiteingabe .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52
Q
QT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9, 14
R
Rich Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Rich Fat Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Rich Thin Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Row-Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
RowData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Runnable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .104
S
Schieberegler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Sequential Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Smart Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9, 14
SWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Symbian OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Synchronisationsobjekt .. . . . . . . . . . . . . . . . . . . . . . . . . . 107
Synchronisierter Abschnitt . . . . . . . . . . . . . . . . . . . . . . . . 107
System-Tray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
T
Tabelle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53, 81
Ansicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Filterung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95
JFace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
116