Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
8 <strong>Das</strong> <strong>SWT</strong><br />
<strong>Das</strong> Standard Widget Toolkit stellt eine Anzahl elementarer GUI-Klassen<br />
zur Verfügung. In diesem Kapitel geben wir zunächst eine Übersicht<br />
über die Funktionsgruppen des <strong>SWT</strong> und diskutieren die Vorund<br />
Nachteile des <strong>SWT</strong> im Vergleich zu den Java-AWT-Bibliotheken.<br />
Dann erläutern wir die wichtigsten Funktionsgruppen im Detail.<br />
Dabei werden wir jedoch keine vollständige API-Beschreibung<br />
vorlegen. Stattdessen beschränken wir uns auf wesentliche Merkmale<br />
der einzelnen <strong>SWT</strong>-Funktionsgruppen und deren Zusammenspiel. Die<br />
API-Dokumentation der einzelnen <strong>SWT</strong>-Packages findet man im<br />
Eclipse-Hilfesystem unter Platform-Plugin Developer Guide>Reference>API<br />
Reference>Workbench.<br />
8.1 Übersicht über die <strong>SWT</strong>-Funktionsgruppen<br />
Die <strong>SWT</strong>-Klassen sind auf die folgenden Packages aufgeteilt:<br />
org.eclipse.swt<br />
org.eclipse.swt.accessibility<br />
org.eclipse.swt.awt<br />
org.eclipse.swt.browser<br />
org.eclipse.swt.custom<br />
Dieses Package enthält alle <strong>SWT</strong>-spezifischen<br />
Konstanten und Exceptions. Siehe Abschnitt 8.3.<br />
Dieses Package enthält Klassen für die<br />
Implementierung behindertengerechter GUIs.<br />
Siehe Abschnitt 8.14.<br />
Dieses Package enthält die Klasse <strong>SWT</strong>-AWT für<br />
die Einbettung von AWT-Elementen in das <strong>SWT</strong>.<br />
Siehe Abschnitt 8.8.1.<br />
Dieses Package enthält die Klassen für die<br />
Implementierung des Browser-Widgets.<br />
Siehe Abschnitt 8.5.14.<br />
Dieses Package enthält Widgets für die Eclipse-<br />
Plattform, die nicht direkt auf das Windowing-<br />
System abgebildet werden können, sondern in<br />
Java implementiert sind. Siehe Abschnitt 8.5.13.<br />
147
148<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
org.eclipse.swt.dnd<br />
org.eclipse.swt.events<br />
org.eclipse.swt.graphics<br />
org.eclipse.swt.internal<br />
org.eclipse.swt.layout<br />
org.eclipse.swt.ole.win32<br />
org.eclipse.swt.printing<br />
org.eclipse.swt.program<br />
org.eclipse.swt.widgets<br />
8.2 Vor- und Nachteile des <strong>SWT</strong><br />
Die Frage ist natürlich: Wann sollte man das <strong>SWT</strong> für die Implementierung<br />
eines GUI verwenden und wann Swing? Im folgenden<br />
Abschnitt sehen wir uns einige Vor- und Nachteile des <strong>SWT</strong> im Vergleich<br />
zu Swing an.<br />
8.2.1 Vorteile des <strong>SWT</strong><br />
Dieses Package unterstützt Funktionen des<br />
Datentransfers wie Drag&Drop oder Operationen<br />
auf die Zwischenablage (Clipboard).<br />
Siehe Abschnitt 8.10.<br />
Dieses Package enthält alle <strong>SWT</strong>-spezifischen<br />
Klassen (Events) und Interfaces (Listener) für die<br />
Ereignisverarbeitung. Siehe Abschnitt 8.4.3.<br />
Dieses Package enthält Klassen für grafische<br />
Operationen. Siehe Abschnitt 8.7.<br />
Dieses Package enthält interne <strong>SWT</strong>-Klassen.<br />
<strong>SWT</strong>-Anwendungen sollten nicht direkt auf diese<br />
Klassen zugreifen, da sich deren API ohne<br />
Vorwarnung ändern kann.<br />
Dieses Package enthält verschiedene Layout-<br />
Klassen für die automatische Positionierung von<br />
<strong>SWT</strong>-Elementen. Siehe Abschnitt 8.6.<br />
Dieses Package unterstützt OLE für 32-Bit-<br />
Windows-Betriebssysteme. Siehe Abschnitt 8.12.<br />
Dieses Package unterstützt die Ausgabe auf<br />
Druckern.Siehe Abschnitt 8.9.<br />
Dieses Package enthält nur die Klasse program.<br />
Deren Instanzen repräsentieren Dateiassoziationen<br />
im zu Grunde liegenden Betriebssystem und<br />
unterstützen den Start externer Programme.<br />
Dieses Package enthält alle Widget-Klassen des<br />
<strong>SWT</strong>-API. Im Wesentlichen ist es dieses<br />
Package, das den Funktionsumfang des <strong>SWT</strong><br />
definiert. Siehe Abschnitt 8.5.<br />
Die Vorteile von <strong>SWT</strong> liegen in der nahtlosen Einbettung der Applikation<br />
in die jeweilige Ablaufumgebung. Da die <strong>SWT</strong>-Widgets nicht –<br />
wie z.B. das Swing – die nativen Oberflächen emulieren, sondern lediglich<br />
als Adapter für die entsprechenden Betriebssystemdienste wirken,<br />
lassen sich mit dem <strong>SWT</strong> programmierte Benutzeroberflächen vom<br />
Endanwender praktisch nicht von den Oberflächen nativer Applikationen<br />
unterscheiden. Ein Button sieht unter Windows 2000 genauso
8.2 Vor- und Nachteile des <strong>SWT</strong><br />
aus wie ein Windows-2000-Button, unter Windows XP genauso wie<br />
ein Windows-XP-Button und auf dem Mac genauso wie ein Mac-Button.<br />
<strong>Das</strong> ist unter Swing oft nicht gegeben. Hier gibt es zwar eine<br />
Anzahl von vordefinierten Skins, jedoch ist oft nicht das Richtige<br />
dabei.<br />
Bei der Ansprechgeschwindigkeit ist Eclipse auch im Vorteil. <strong>Das</strong><br />
<strong>SWT</strong> unterscheidet sich verständlicherweise auch hier nicht von nativen<br />
Applikationen, da direkt auf die Ereignisverarbeitung des jeweiligen<br />
Betriebs- oder Fenstersystems aufgesetzt wird. Swing ist dagegen<br />
in der Interaktion oft etwas träge. Zudem ist <strong>SWT</strong> nicht so ressourcenhungrig<br />
wie Swing.<br />
Da die Eclipse-Plattform selbst mit Hilfe des <strong>SWT</strong> programmiert<br />
ist, ist das <strong>SWT</strong> natürlich auch die beste Wahl, wenn man Eclipse-Plugins<br />
entwickeln und GUI-Elemente der Eclipse-Plattform für das Plugin<br />
nutzen will.<br />
Last but not least: Da <strong>SWT</strong>-Widgets direkt auf die entsprechenden<br />
Dienste des nativen Windowing-Systems aufsetzen, darf erwartet werden,<br />
dass das <strong>SWT</strong> sich robuster und toleranter gegenüber der verwendeten<br />
Hardware und gegenüber verschiedenen Grafikeinstellungen<br />
verhält als das AWT. Tatsächlich habe ich unter Windows festgestellt,<br />
dass <strong>SWT</strong>-Applikationen auch dort noch problemlos liefen, wo AWToder<br />
Swing-Applikationen auf Grund von DirectX-Inkompatibilitäten<br />
die Maschine zu einem jähen Halt brachten.<br />
8.2.2 Nachteile des <strong>SWT</strong><br />
Allerdings gibt es beim <strong>SWT</strong> gegenüber dem AWT auch einige Nachteile:<br />
! Zunächst laufen mit dem <strong>SWT</strong> realisierte Anwendungen nur auf<br />
den Plattformen ab, für die auch ein <strong>SWT</strong> implementiert wurde.<br />
<strong>Das</strong> sind zurzeit vor allen Dingen die verschiedenen Windows-<br />
Plattformen (einschließlich Windows CE), Linux mit den Fenstersystemen<br />
GTK und Motif (einschließlich 64-Bit-GTK auf AMD64),<br />
verschiedene Unix-Derivate (Solaris, QNX, AIX und HP-UX) und<br />
Mac OS X.<br />
! Im Großen und Ganzen sind die verschiedenen Implementierungen<br />
des <strong>SWT</strong> funktional äquivalent, aber bekanntlich steckt der Teufel<br />
im Detail. So gibt es einzelne Funktionen, bei denen das Verhalten<br />
von Dialogelementen auf den verschiedenen Plattformen voneinander<br />
abweicht. Plant man, ein Softwareprodukt für den Einsatz auf<br />
mehreren Plattformen zu entwickeln, ist es unbedingt erforderlich,<br />
das Produkt auf allen Plattformen gründlich zu testen.<br />
Bessere Interaktion<br />
Robuster<br />
149
150<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
! Im Gegensatz zu dem AWT erfordert das <strong>SWT</strong> eine explizite Ressourcenverwaltung.<br />
So benutzt das <strong>SWT</strong> für Bilder, Farben und<br />
Fonts Betriebssystemressourcen, die, sobald sie nicht mehr benötigt<br />
werden, mit dispose() wieder explizit freigegeben werden sollten.<br />
Wir werden die Ressourcenverwaltung ausführlich in Abschnitt<br />
8.9 diskutieren.<br />
8.3 <strong>Das</strong> Package <strong>SWT</strong><br />
Im Package org.eclipse.swt sind lediglich drei Klassen definiert: <strong>SWT</strong>,<br />
<strong>SWT</strong>Exception und <strong>SWT</strong>Error. Während die beiden letzteren Klassen der<br />
Fehlerbehandlung (abfangbare bzw. nicht abfangbare Ausnahmebedingungen)<br />
dienen, sind in der Klasse <strong>SWT</strong> alle <strong>SWT</strong>-spezifischen Konstanten<br />
zusammengefasst. Dazu gehören Konstanten für die Bezeichnung<br />
von Tasten, für vordefinierte Farben, für Layout-Variationen bei<br />
den Widgets, für Textstile, Cursorvariationen, Mausaktionen, vordefinierte<br />
Knöpfe und anderes.<br />
So bezeichnet z.B. <strong>SWT</strong>.LINE_DASHDOT eine Strich-Punkt-Linie, oder<br />
<strong>SWT</strong>.MouseDoubleClick ein Mausdoppelklick-Ereignis. Wir werden in<br />
den folgenden Beispielen einige dieser Konstanten im Einsatz sehen.<br />
8.4 Ereignisse<br />
Ereignisse bilden den grundlegenden Mechanismus, mit dem Anwendungen<br />
mit dem GUI kommunizieren. Applikationen registrieren<br />
Zuhörerinstanzen (Listener) mit den Widgets, um auf Ereignisse reagieren<br />
zu können. Meistens von Benutzeraktionen wie z.B. Mausklicks<br />
oder Tastaturbetätigungen hervorgerufen, informieren die Ereignisse<br />
über die Listener die Anwendung über die Art der Aktion.<br />
<strong>Das</strong> Package org.eclipse.swt.events enthält drei verschiedene<br />
Gruppen: Listener-Interfaces, Event-Klassen und Adapter-Klassen. Bei<br />
den Ereignissen unterscheiden wir zwischen zwei Kategorien – typisierten<br />
Ereignissen wie ControlEvent oder MouseEvent und generischen<br />
Ereignissen (Event). Entsprechend setzt sich diese Aufteilung auch bei<br />
den Listener-Interfaces fort.<br />
8.4.1 Zuhörer<br />
Für jede Ereignisart gibt es verschiedene Listener-Klassen. So kann<br />
man z.B. einer Taste (Button) mit addSelectionListener() eine SelectionListener-Instanz<br />
hinzufügen. Wird die Taste selektiert (angeklickt),<br />
wird die widgetSelected()-Methode dieser SelectionListe-
8.4 Ereignisse<br />
ner-Instanz aufgerufen. Die Methode erhält als Argument eine<br />
SelectionEvent-Instanz, die das Ereignis repräsentiert.<br />
Beispiel:<br />
public void createButton(Composite parent) {<br />
Button myButton = new Button(parent, <strong>SWT</strong>.PUSH);<br />
myButton.addSelectionListener(new SelectionListener() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Button pressed!");<br />
}<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
}<br />
});<br />
}<br />
Hier haben wir für einen neu erzeugten Button eine innere anonyme<br />
SelectionListener-Klasse als Zuhörer definiert.<br />
Zu jeder Methode add...Listener() gibt es natürlich auch eine<br />
Methode remove...Listener(). Insbesondere in komplexen Systemen<br />
sollten Komponenten, die sich bei anderen Komponenten als Zuhörer<br />
registrieren, auch wieder mit remove...Listener() abmelden, wenn sie<br />
deaktiviert werden. So kann überflüssiger Overhead vermieden werden.<br />
Später, wenn die Komponente wieder aktiviert wird, kann sie sich<br />
ja wieder mit add...Listener() anmelden.<br />
Genau aus diesem Grund sollte man auch keine Annahmen über<br />
die zeitliche Reihenfolge beim Aufruf von registrierten Listenern<br />
machen. Zwar wird intern beim Auftreten eines Ereignisses die Liste<br />
der Zuhörer sequenziell abgearbeitet, da sich jedoch Komponenten in<br />
eigener Regie an- und abmelden können, ist es praktisch unmöglich,<br />
die Übersicht zu behalten, welcher Zuhörer vor oder nach einem anderen<br />
aufgerufen wird.<br />
8.4.2 Adapter<br />
Adapter sind Standardimplementierungen der jeweiligen Interfaces. Sie<br />
enthalten leere Methoden für jede im Interface definierte Methode.<br />
Adapter dienen so dem Komfort des Programmierers. Anstatt alle<br />
Methoden eines Interface komplett implementieren zu müssen, definiert<br />
man lediglich eine Unterklasse des zugehörigen Adapters und<br />
überschreibt diejenigen Methoden, die im konkreten Anwendungsfall<br />
von Interesse sind.<br />
Im obigen Beispiel hätten wir natürlich auch einen SelectionAdapter<br />
verwenden und so auf die Angabe der leeren Methode widget-<br />
DefaultSelected() verzichten können:<br />
151
152<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
public void createButton(Composite parent) {<br />
Button myButton = new Button(parent, <strong>SWT</strong>.PUSH);<br />
myButton.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Button pressed!");<br />
}<br />
});<br />
}<br />
8.4.3 Ereignisse<br />
Alle Ereignisklassen des <strong>SWT</strong> (bis auf die Klasse Event) sind Unterklassen<br />
der Klasse TypedEvent, die ihrerseits ein Abkömmling der Klasse<br />
java.util.EventObject ist.<br />
Achtung: TypedEvent ist kein Abkömmling von Event!<br />
Jeder Ereignistyp besitzt eine Anzahl von öffentlichen Feldern, welche<br />
die spezifischen Daten des jeweiligen Ereignisses enthalten. So besitzt<br />
z.B. der Typ MouseEvent die Integer-Felder x, y, stateMask und button.<br />
Auf alle diese Felder kann direkt zugegriffen werden (also ohne eine<br />
get...()-Methode). Außerdem besitzt jedes TypedEvent noch die<br />
Methode getSource(), mit der man den Absender des TypedEvent-<br />
Objekts ermitteln kann.<br />
Bei der generischen Klasse Event dagegen kann die Art des Ereignisses<br />
über das Feld type abgefragt werden. Den Absender erhält man<br />
über das Feld widget.<br />
8.4.4 Übersicht über Listener-, Adapter- und Event-Klassen<br />
Listener<br />
Typisierte Ereignisse<br />
Ereignis Adapter<br />
ArmListener<br />
ControlListener<br />
DisposeListener<br />
FocusListener<br />
ArmEvent<br />
Tritt auf, wenn ein Widget, z.B. ein Menü, für die<br />
Selektion vorbereitet wird. <strong>Das</strong> ist z.B. der Fall, wenn<br />
die Maus über dieses Element bewegt wird.<br />
ControlEvent<br />
Tritt auf, wenn ein GUI-Element bewegt oder in der<br />
Größe verändert wird.<br />
DisposeEvent<br />
Tritt auf, wenn ein Widget entsorgt wird.<br />
FocusEvent<br />
Tritt auf, wenn ein GUI-Element den Fokus erhält<br />
oder verliert.<br />
–<br />
ControlAdapter<br />
–<br />
FocusAdapter
8.4 Ereignisse<br />
Listener<br />
Typisierte Ereignisse<br />
Ereignis Adapter<br />
HelpListener<br />
KeyListener<br />
MenuListener<br />
ModifyListener<br />
MouseListener<br />
Wird benachrichtigt, wenn die<br />
Maustasten betätigt wurden.<br />
MouseMoveListener<br />
Wird benachrichtigt, wenn der<br />
Mauszeiger sich bewegt.<br />
MouseTrackListener<br />
Wird benachrichtigt, wenn der<br />
Mauszeiger sich über GUI-Elementen<br />
bewegt oder über ihnen schwebt.<br />
PaintListener<br />
SelectionListener<br />
ShellListener<br />
TraverseListener<br />
TreeListener<br />
VerifyListener<br />
Generische Ereignisse<br />
Listener<br />
HelpEvent<br />
Tritt auf, wenn für ein GUI-Element Hilfe angefordert<br />
wird (z.B. mit F1).<br />
KeyEvent<br />
Tritt auf, wenn Tasten der Tastatur betätigt werden.<br />
–<br />
KeyAdapter<br />
MenuEvent<br />
Tritt auf, wenn Menüs ein- oder ausgeblendet werden. MenuAdapter<br />
ModifyEvent<br />
Tritt auf, nachdem Text modifiziert wurde.<br />
MouseEvent<br />
Mausereignis.<br />
MouseEvent<br />
Mausereignis.<br />
MouseEvent<br />
Mausereignis.<br />
PaintEvent<br />
Tritt auf, wenn ein GUI-Element neu gezeichnet<br />
werden muss.<br />
SelectionEvent<br />
Tritt auf, wenn ein GUI-Element selektiert wird.<br />
ShellEvent<br />
Tritt auf, wenn sich der Zustand einer Shell-Instanz<br />
ändert (normal, minimiert, maximiert).<br />
TraverseEvent<br />
Tritt bei einem Traverse-Ereignis auf, z.B. wenn der<br />
Benutzer mit TAB auf das nächste Feld geht oder<br />
wenn die traverse()-Methode aufgerufen wird.<br />
TreeEvent<br />
Tritt auf, wenn Baumknoten expandieren oder kollabieren.<br />
VerifyEvent<br />
Tritt auf, bevor Text modifiziert wird. Mittels einer<br />
entsprechenden Zuweisung an die doit-Variable des<br />
Ereignisobjekts kann die Modifikation verhindert<br />
werden (Veto).<br />
Event<br />
Nicht typisiertes Ereignis innerhalb des <strong>SWT</strong>.<br />
Wird von nicht-Widget-Objekten erzeugt.<br />
–<br />
MouseAdapter<br />
–<br />
153<br />
MouseTrackAdapter<br />
–<br />
SelectionAdapter<br />
ShellAdapter<br />
–<br />
TreeAdapter<br />
–<br />
–
154<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
8.5 Widgets<br />
In diesem Abschnitt behandeln wir die verschiedenen GUI-Elemente<br />
entlang ihrer Vererbungshierarchie (siehe Abb. 8–1), an deren Spitze<br />
die Klasse Widget steht. Dazu gehören selbstverständlich die unmittelbaren<br />
Bedienelemente wie Tasten (Button), Textfelder (Text) oder<br />
Schieberegler (Slider), aber auch Elemente, die der Gruppierung von<br />
Bedienelementen dienen wie die Klassen Group und Composite.<br />
Die Klasse Widget<br />
Alle GUI-Elemente sind Abkömmlinge der abstrakten Klasse Widget.<br />
Diese Klasse implementiert einige Basismethoden wie dispose() oder<br />
addDisposeListener(). Bei der Ausführung von dispose() sendet sie<br />
allen DisposeListener-Instanzen ein DisposeEvent-Objekt.<br />
Unmittelbarer Abkömmling der Klasse Widget ist die abstrakte<br />
Klasse Control. Instanzen dieser Klasse repräsentieren fensterbezogene<br />
GUI-Elemente und korrelieren direkt mit Ressourcen des Betriebsoder<br />
Fenstersystems.<br />
Die Klasse Control<br />
Die Klasse Control kann folgende Event-Typen an die jeweiligen Listener<br />
schicken: ControlEvent, FocusEvent, HelpEvent, KeyEvent, MouseEvent,<br />
MouseTrackEvent, MouseMoveEvent, PaintEvent. Dazu stellt Control<br />
für die Verwaltung der jeweiligen Listener-Typen verschiedene<br />
add...Listener()- und remove...Listener()-Methoden zur Verfügung.<br />
Außerdem stellt Control ein reichhaltiges Arsenal von Methoden<br />
zur Verfügung, mit denen die verschiedenen Eigenschaften der jeweiligen<br />
GUI-Elemente gesetzt und abgefragt werden können. Insbesondere<br />
sind die Methoden setVisible() und setEnabled() zu erwähnen, mit<br />
denen man GUI-Elemente zeigen und verstecken bzw. für die Eingabe<br />
freigeben oder sperren kann.<br />
Größe und Position Anfänglich wird die Größe von Control-Instanzen auf einen<br />
Default-Wert gesetzt. In vielen Fällen ist das die minimale Größe<br />
(0x0), so dass das entsprechende GUI-Element unsichtbar bleibt. Mit<br />
der Methode setBounds() kann die Größe der Instanz und auch die<br />
Position relativ zum übergeordneten Composite (siehe Abschnitt 8.5.4)<br />
gesetzt werden. Alternativ kann das übergeordnete Composite mit<br />
einem Layout (siehe Abschnitt 8.6) versehen werden, das sich selbsttätig<br />
um die Positionierung der im Composite enthaltenen Control-<br />
Instanzen kümmert. Mit der Methode pack() wird die bevorzugte<br />
Größe bzw. die aus dem Layout abgeleitete Größe neu berechnet.
Widget<br />
Abb. 8–1 Die Hierarchie der wichtigsten Widget-Klassen<br />
8.5.1 Visuelle Übersicht<br />
8.5 Widgets<br />
Caret DragSource DropTarget Menu ScrollBar Tracker<br />
CoolItem<br />
Item<br />
Control<br />
Scale Slider Scrollable ProgressBar Sash Button Label<br />
CCombo<br />
List<br />
Combo<br />
Text<br />
CoolBar<br />
Composite<br />
Group Toolbar<br />
TabFolder CTabFolder<br />
MenuItem TabItem CTabItem<br />
TableColumn TableItem ToolItem TreeItem TableTreeItem<br />
Canvas<br />
Die beste Übersicht über die verschiedenen Widgets des <strong>SWT</strong> ist in den<br />
Beispielapplikationen zu Eclipse enthalten. In Abschnitt 1.1 hatten wir<br />
Table Tree<br />
Decorations<br />
Browser<br />
Shell<br />
155
156<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Abb. 8–2 Die <strong>SWT</strong>-Controls-Beispielapplikation besitzt für die verschiedenen nativen<br />
Widget-Typen jeweils eine eigene Karteikarte. Rechts kann das Widget über Parameter<br />
konfiguriert werden. Die Namen der Parameter entsprechen den jeweiligen <strong>SWT</strong>-Konstanten.<br />
Links werden dann die Ergebnisse gezeigt. Im unteren Bereich können Ereignisse<br />
protokolliert werden (hier Selektions- und Fokusereignisse).<br />
ja bereits die Eclipse-Beispieldateien installiert. Jetzt müssen wir nur<br />
die entsprechende Beispielapplikation starten. Dazu rufen wir die<br />
Funktion Window>Show View>Other ... auf. Im folgenden Dialog<br />
wählen wir die Applikation <strong>SWT</strong> Examples><strong>SWT</strong> Controls aus. Diese<br />
Applikation erscheint dann auch prompt im Fenster rechts unten (siehe<br />
Abb. 8–2). Damit wir etwas mehr Platz haben, maximieren wir diese<br />
Applikation mit einem Doppelklick auf den Reiter.<br />
Da diese Applikation die verschiedenen Widgets ganz hervorragend<br />
visualisieren kann, werden wir in den folgenden Abschnitten auf<br />
die Abbildung einiger Widgets verzichten. Eine weitere Beispielappli-
8.5 Widgets<br />
kation Custom Controls liefert eine Übersicht über die nicht-nativen<br />
Widgets (siehe Abschnitt 8.5.13). Beide Applikationen erlauben Ihnen<br />
außerdem mit Ereignissen und Listenern zu experimentieren.<br />
8.5.2 Displays, Shells und Monitore<br />
Die Klassen Display und Shell bilden die Basis für die Konstruktion<br />
eines GUI. Dabei repräsentiert Display den GUI-Prozess (oder Thread)<br />
und Shell die jeweiligen Fenster.<br />
Display<br />
Die Klasse Display ist das Verbindungsglied zwischen Java-Applikation<br />
und Betriebssystem. Jede Applikation mit einem <strong>SWT</strong>-GUI<br />
erzeugt mindestens eine Instanz dieser Klasse. Genauer gesagt: Benutzt<br />
man nur einen einzigen Thread, so ist auch nur eine Display-Instanz<br />
notwendig. Führt man jedoch GUI-Operationen in mehreren Threads<br />
aus, so wird für jeden dieser Threads eine eigene Display-Instanz<br />
erzeugt. Mit der statischen Methode Display.getCurrent() kann man<br />
die aktive Display-Instanz für den jeweiligen Thread herausfinden.<br />
Im Unterschied zu AWT und Swing erzwingt das <strong>SWT</strong>, dass alle<br />
<strong>SWT</strong>-Objekte nur von dem Thread benutzt werden dürfen, in dem sie<br />
erzeugt wurden. Trotzdem ist es möglich, auch unter <strong>SWT</strong> mit mehreren<br />
Threads zu arbeiten. Die Klasse Display stellt zu diesem Zweck die<br />
Methoden syncExec() und asyncExec() zur Verfügung, mit denen<br />
Runnable-Objekte dem jeweiligen Thread der Display-Instanz zur Ausführung<br />
übergeben werden können. Wir werden in den folgenden<br />
Kapiteln noch des Öfteren auf diese Technik zurückgreifen.<br />
Außerdem besitzt Display Methoden, um GUI-Eigenschaften des<br />
jeweiligen Betriebssystems abzufragen wie getSystemFont() oder get-<br />
DoubleClickTime(), und verwaltet die Ressourcen des Betriebssystems.<br />
Darüber hinaus gibt es Methoden für die Widget-Verwaltung, z.B.<br />
getActiveShell() oder getFocusControl(). Mit der Methode map()<br />
können Koordinaten von Punkten oder Rechtecken vom Koordinatensystem<br />
des einen Control-Elements in das Koordinatensystem eines<br />
anderen Control-Elements übersetzt werden.<br />
Eine Display-Instanz erzeugt Ereignisse der Klasse Event (siehe<br />
Abschnitt 8.4.3) mit dem Typ <strong>SWT</strong>.Open bzw. <strong>SWT</strong>.Close. Per Programm<br />
lassen sich Ereignisse vom Typ KeyDown, KeyUp, MouseDown, MouseUp<br />
oder MouseMove über die Methode post() erzeugen. So können Benutzeroberflächen<br />
automatisiert werden. Hier ist ein Beispiel:<br />
<strong>SWT</strong>-Thread<br />
Ereignisse<br />
157
158<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Shell<br />
// Fensterkoordinate (100,50) in das Display-<br />
// Koordinatensystem übersetzen<br />
Point coord = display.map(shell, null, 100, 50);<br />
event = new Event();<br />
event.type = <strong>SWT</strong>.MouseMove;<br />
event.x = coord.x;<br />
event.y = coord.y;<br />
display.post(event);<br />
Die Klasse Shell repräsentiert ein Fenster auf dem Desktop des jeweiligen<br />
Betriebssystems. Dabei kann eine Shell-Instanz drei verschiedene<br />
Betriebszustände annehmen: maximiert, normal oder minimiert. Beim<br />
Wechsel dieser Betriebszustände erzeugen Shell-Instanzen Ereignisse<br />
vom Typ ShellEvent.<br />
Achtung: Von Shell dürfen keine Unterklassen gebildet werden. <strong>Das</strong> merkt man<br />
allerdings erst bei der Ausführung. Für die Implementierung eigener Fensterklassen<br />
verwendet man besser die JFace-Klasse Window (siehe Abschnitt 9.2).<br />
Shell-Typen Es gibt zwei verschiedene Arten von Shell-Instanzen: Top-Level-Shells<br />
für das Hauptfenster der Applikation und Dialog-Shells, die von anderen<br />
Shells abhängig sind. Welche Instanzenart erzeugt wird, hängt von<br />
dem Parameter des Konstruktors ab: Wird eine Display-Instanz übergeben,<br />
wird eine Top-Level-Shell erzeugt; wird eine andere Shell-<br />
Instanz übergeben, wird eine Dialog-Shell erzeugt.<br />
Beim Erzeugen einer Shell kann optional ein Stilparameter mit<br />
angegeben werden. Dabei können die folgenden <strong>SWT</strong>-Konstanten<br />
benutzt werden:<br />
<strong>SWT</strong>.NONE Standardfenster, betriebssystemabhängig.<br />
<strong>SWT</strong>.BORDER Fenster hat einen Rand (plattformabhängig).<br />
<strong>SWT</strong>.CLOSE Fenster hat eine Titelzeile mit einem Button zum Schließen des Fensters.<br />
<strong>SWT</strong>.MIN Fenster hat eine Titelzeile mit einem Button zum Minimalisieren des Fensters.<br />
<strong>SWT</strong>.MAX Fenster hat eine Titelzeile mit einem Button zum Maximalisieren des Fensters.<br />
<strong>SWT</strong>.NO_TRIM Fenster hat weder Titelzeile noch Rand.<br />
<strong>SWT</strong>.RESIZE Fenstergröße kann durch Ziehen mit der Maus verändert werden.<br />
<strong>SWT</strong>.TITLE Fenster hat eine Titelzeile.<br />
<strong>SWT</strong>.SHELL_TRIM<br />
<strong>SWT</strong>.DIALOG_TRIM<br />
Kombination von Stilelementen für das Top-Level-Fenster einer Applikation<br />
(<strong>SWT</strong>.CLOSE | <strong>SWT</strong>.TITLE | <strong>SWT</strong>.MIN | <strong>SWT</strong>.MAX | <strong>SWT</strong>.RESIZE).<br />
Kombination von Stilelementen für Dialog-Fenster<br />
(<strong>SWT</strong>.CLOSE | <strong>SWT</strong>.TITLE | <strong>SWT</strong>.BORDER).
8.5 Widgets<br />
Daneben gibt es noch die Konstanten <strong>SWT</strong>.APPLICATION_MODAL, <strong>SWT</strong>.<br />
MODELESS, <strong>SWT</strong>.PRIMARY_MODAL und <strong>SWT</strong>.SYSTEM_MODAL, die das modale<br />
Verhalten des Fensters bestimmen. Ein modales Fenster, das sich im<br />
Vordergrund befindet, lässt keine anderen Fenster der Applikation<br />
bzw. des Systems in den Vordergrund kommen. Ein solches Fenster<br />
sollte so instrumentiert sein, dass der Anwender das Fenster auch<br />
schließen kann.<br />
Wird kein Stilparameter angegeben, so hängt der Standardstil sowohl<br />
vom Betriebssystem als auch von der Art der Shell ab. So ist z.B. bei<br />
WindowsCE der Standardstil <strong>SWT</strong>.NONE. Bei anderen Windows-Versionen<br />
dagegen ist es SHELL_TRIM bzw. DIALOG_TRIM, je nach Art der Shell.<br />
Mit der Methode setImage() kann ein Icon spezifiziert werden,<br />
welches das Fenster repräsentiert, wenn es minimalisiert wird. Üblicherweise<br />
wird ein solches Icon auch in der Titelleiste gezeigt. Mit der<br />
Methode setImages() können Icons in verschiedenen Auflösungen<br />
spezifiziert werden, aus denen sich dann die Plattform für den jeweiligen<br />
Zweck das beste Icon aussuchen kann.<br />
Abb. 8–3 Eine Shell mit zwei Buttons (hier unter Windows2000). Die Shell wurde mit<br />
den Optionen <strong>SWT</strong>.BORDER, <strong>SWT</strong>.TITLE, <strong>SWT</strong>.CLOSE, <strong>SWT</strong>.MIN und <strong>SWT</strong>.MAX erzeugt, verfügt<br />
also über einen erhabenen Rand, eine Titelzeile und Tasten zum Schließen, Minimieren<br />
und Maximieren.<br />
Übrigens hat eine Shell nicht notwendigerweise eine rechteckige<br />
Gestalt. Durch Zuweisen einer Region kann man einer Shell eine beliebige<br />
Gestalt geben. Zunächst erzeugt man dazu eine Region-Instanz,<br />
der man dann mit add() Umrisslinien hinzufügt, oder aus der man mit<br />
subtract() auch wieder Teilbereiche entfernen kann. So kann man<br />
sogar »löchrige« Regions erzeugen. Mit setRegion() weist man dann<br />
die Region-Instanz der Shell zu. Wirksam wird eine solche Zuweisung<br />
aber nur für Shells vom Typ <strong>SWT</strong>.NO_TRIM. <strong>Das</strong> bedeutet, dass man für<br />
diese Shell das Schließen, Verschieben und das Verändern der Größe<br />
selbst organisieren muss. Abschnitt 10.3 zeigt die Konstruktion einer<br />
solchen irregulären Shell in der Praxis.<br />
Um ein erstes Beispielprogramm mit einer Shell zu schreiben, richten<br />
wir in der Workbench zunächst ein neues Projekt widgets ein und<br />
legen in diesem Projekt die Klasse widgetTest an. Diesem Projekt sind<br />
zunächst die <strong>SWT</strong>-Klassen nicht bekannt. Wir müssen die <strong>SWT</strong>-Biblio-<br />
Region<br />
159<br />
Die Workbench einrichten
160<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
thek als externe .JAR-Datei zum Classpath hinzufügen. Wie das geht,<br />
ist bereits in Abschnitt 4.10 beschrieben. Die <strong>SWT</strong>-Bibliothek ist bei<br />
Windows unter<br />
\eclipse\plugins\org.eclipse.swt.win32_3.0.0\ws\win32\swt.jar<br />
zu finden. Unter Linux-GTK benötigt man stattdessen zwei JAR-<br />
Dateien:<br />
/opt/eclipse/plugins/org.eclipse.swt.gtk_3.0.0/ws/gtk/swt.jar<br />
/opt/eclipse/plugins/org.eclipse.swt.gtk_3.0.0/ws/gtk/swt-pi.jar<br />
Für andere Betriebs- und Fenstersysteme gilt Ähnliches. 1<br />
<strong>Das</strong> erste <strong>SWT</strong>-Programm Wir können nun ein erstes Programm schreiben. Zunächst erzeugen<br />
wir eine neue Display-Instanz. Dann erzeugen wir eine Top-Level-<br />
Shell. Dies geschieht, indem man dem Konstruktor die Display-Instanz<br />
als Parameter übergibt. Im Gegensatz dazu wird dem Konstruktor<br />
einer Dialog-Shell eine andere Shell als Parameter übergeben.<br />
import org.eclipse.swt.widgets.Display;<br />
import org.eclipse.swt.widgets.Shell;<br />
public class widgetTest {<br />
}<br />
public static void main(String[] args) {<br />
// Display-Instanz erzeugen<br />
final Display display = new Display();<br />
// TopLevel-Shell erzeugen (display als parent)<br />
final Shell toplevelShell = new Shell(display);<br />
// Titelzeile setzen<br />
toplevelShell.setText("TopLevel.Titelzeile");<br />
// Shell anzeigen<br />
toplevelShell.open();<br />
// Dialog-Shell erzeugen (toplevelShell als parent)<br />
final Shell dialogShell = new Shell(toplevelShell);<br />
// Titelzeile setzen<br />
dialogShell.setText("Dialog.Titelzeile");<br />
// Shell anzeigen<br />
dialogShell.open();<br />
// Warten bis TopLevel-Shell geschlossen wird<br />
while (!toplevelShell.isDisposed()) {<br />
// Prüfen ob Ereignisse warten<br />
if (!display.readAndDispatch()) display.sleep();<br />
}<br />
}<br />
1. Je nach Eclipse-Version müssen Sie diese Pfade ggf. anpassen!
8.5 Widgets<br />
Wichtig ist hier die while-Schleife am Ende des Programms, denn im<br />
<strong>SWT</strong> ist der Programmierer für die Organisation der Ereignisschleife<br />
zuständig. Ohne diese Schleife würde die Benutzeroberfläche »einfrieren«.<br />
Hier bewirken wir mit der Methode readAndDispatch(), dass die<br />
Display-Instanz ein anstehendes Ereignis liest und an die entsprechenden<br />
GUI-Elemente weitergibt. Stehen keine weiteren Ereignisse mehr<br />
an, legen wir den Thread mit der Methode sleep() schlafen, bis ein<br />
neues Ereignis eintrifft.<br />
Nun können wir dieses kleine Programm ausführen. Dazu müssen<br />
wir eine neue Run-Konfiguration vom Typ Java-Applikation erzeugen.<br />
<strong>Das</strong> geschieht in der Run-Konfiguration, die Sie sowieso neu anlegen<br />
müssen. Rufen Sie also Run>Run ... auf und betätigen Sie die New-<br />
Taste, um eine neue Konfiguration für eine Java-Applikation anzulegen.<br />
Als Name geben Sie widgetTest ein, dergleichen unter Main Class.<br />
Unter Projekt geben Sie widgets ein.<br />
Allerdings reichen diese Angaben für das erfolgreiche Ausführen<br />
des Programms noch nicht aus. <strong>Das</strong> <strong>SWT</strong> benötigt für die Ausführung<br />
native Module, deren Ort der Java Virtual Machine noch mitgeteilt<br />
werden müssen. Der Pfad der entsprechenden Modulbibliothek wird<br />
in der Run-Konfiguration auf der Seite Arguments unter VM Arguments<br />
eingegeben. Im Falle von Windows ist der folgende Parameter<br />
einzugeben:<br />
-Djava.library.path=<br />
C:\eclipse\plugins\org.eclipse.swt.win32_3.0.0\os\win32\x86<br />
Unter Linux-GTK ist es:<br />
-Djava.library.path=<br />
/opt/eclipse/plugins/org.eclipse.swt.gtk_3.0.0/os/linux/x86<br />
Unter anderen Betriebs- bzw. Fenstersystemen finden Sie die Modulbibliothek<br />
an entsprechender Stelle. 2<br />
Anschließend dürfen Sie dann wirklich Run drücken, und wenn<br />
alles gut geht, das neue Fenster bewundern.<br />
Monitor<br />
Seit Eclipse 3.0 unterstützt das <strong>SWT</strong> auch Hardware, bei welcher der<br />
grafische Arbeitsbereich auf mehrere Monitore verteilt sein kann. <strong>Das</strong><br />
mag zunächst nach Spezialanwendung klingen, ist es aber nicht. So<br />
sind höherwertige Notebooks oft in der Lage, den Arbeitsbereich auf<br />
das Notebook-LCD und auf einen externen Monitor zu verteilen.<br />
2. Je nach Eclipse-Version müssen Sie diese Pfade ggf. anpassen!<br />
161<br />
<strong>SWT</strong> Ablauf-Konfiguration
162<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Eclipse unterstützt diese Hardware mit der <strong>SWT</strong>-Klasse Monitor.<br />
Mit der Display-Methode getMonitors() erhält man ein Array der<br />
angeschlossenen Bildschirme und mit der Methode getPrimaryMonitor()<br />
erhält man den Primärbildschirm. Insbesondere verfügt die<br />
Klasse Monitor über die Methoden getBounds() und getClientArea(),<br />
mit der die Position und Größe des Bildschirms bzw. des nutzbaren<br />
Bereiches abgefragt werden kann. So gehört z.B. die Windows-Taskleiste<br />
nicht zum nutzbaren Bereich.<br />
<strong>SWT</strong>-Applikationen, welche Dialoge, Menüs usw. positionieren,<br />
verschieben oder in der Größe verändern, sollten diese Methoden nutzen,<br />
um z.B. zu erreichen, dass ein Dialog oder ein Menü jeweils nur<br />
auf dem Primärbildschirm erscheint und nicht auf zwei Bildschirme<br />
aufgespalten wird. In Abschnitt 10.3 zeigen wir, wie die Klasse Monitor<br />
in der Praxis angewandt wird.<br />
8.5.3 Dialoge<br />
Die Klasse Dialog ist eine abstrakte Klasse, von der konkrete native<br />
Dialoge abgeleitet werden können. Der dazu notwendige Code folgt in<br />
etwa diesem Muster:<br />
public class MyDialog extends Dialog {<br />
Object result;<br />
// Konstruktor mit Stilparameter<br />
public MyDialog (Shell parent, int style) {<br />
super (parent, style);<br />
}<br />
// Konstruktor ohne Stilparameter<br />
public MyDialog (Shell parent) {<br />
this (parent, 0);<br />
// Die Null kann durch eigene Defaultstilparameter ersetzt<br />
// werden<br />
}<br />
public Object open () {<br />
// Übergeordnete Shell holen (wie im Konstruktor gesetzt)<br />
final Shell parent = getParent();<br />
// Neue Dialog-Shell erzeugen<br />
final Shell shell = new Shell(parent, <strong>SWT</strong>.DIALOG_TRIM |<br />
<strong>SWT</strong>.APPLICATION_MODAL);<br />
// Dialogtitel auf Shell-Titel übertragen<br />
shell.setText(getText());<br />
// Hier legen wir alle Widgets an<br />
// Die Variable result wird üblicherweise in der<br />
// Ereignisverarbeitung dieser Widgets gesetzt
}<br />
}<br />
shell.open();<br />
// Warten bis Dialog-Shell geschlossen wird<br />
final Display display = parent.getDisplay();<br />
while (!shell.isDisposed()) {<br />
if (!display.readAndDispatch())<br />
display.sleep();<br />
}<br />
return result;<br />
8.5 Widgets<br />
Einige konkrete Unterklassen von Dialog sind allerdings bereits im<br />
<strong>SWT</strong> enthalten. Dies sind:<br />
ColorDialog Dialog zum Auswahl einer Farbe.<br />
DirectoryDialog Dialog zur Auswahl eines Verzeichnisses im Dateisystem.<br />
FileDialog<br />
<strong>Das</strong> Aussehen und die Funktionsweise all dieser Dialoge hängt natürlich<br />
vom jeweiligen Betriebssystem ab. Am besten schauen Sie sich die<br />
verschiedenen Dialoge auf der Seite Dialog in der oben genannten<br />
Eclipse-Beispielanwendung <strong>SWT</strong> Controls einmal an.<br />
Im folgenden Code zeigen wir, wie wir den MessageBox-Dialog in<br />
einem Beispielprogramm anwenden (siehe Abb. 8–4):<br />
Vordefinierte Dialoge<br />
Dialog zur Auswahl einer Datei. Durch Stilparameter kann festgelegt werden, zu<br />
welchem Zweck die Datei ausgewählt wird: <strong>SWT</strong>.OPEN und <strong>SWT</strong>.SAVE.<br />
FontDialog Dialog zur Auswahl einer Schriftart.<br />
MessageBox<br />
PrintDialog<br />
MessageBox<br />
163<br />
Dialog zur Ausgabe einer Mitteilung. Durch Stilparameter kann festgelegt werden, mit<br />
welchen Knöpfen der Dialog instrumentiert wird. Dabei sind folgende Kombinationen<br />
möglich:<br />
<strong>SWT</strong>.OK<br />
<strong>SWT</strong>.OK | <strong>SWT</strong>.CANCEL)<br />
<strong>SWT</strong>.YES | <strong>SWT</strong>.NO)<br />
<strong>SWT</strong>.YES | <strong>SWT</strong>.NO | <strong>SWT</strong>.CANCEL)<br />
<strong>SWT</strong>.RETRY | <strong>SWT</strong>.CANCEL)<br />
<strong>SWT</strong>.ABORT | <strong>SWT</strong>.RETRY | <strong>SWT</strong>.IGNORE)<br />
Außerdem kann festgelegt werden, welches Icon mit abgebildet wird:<br />
<strong>SWT</strong>.ICON_ERROR<br />
<strong>SWT</strong>.ICON_INFORMATION<br />
<strong>SWT</strong>.ICON_QUESTION<br />
<strong>SWT</strong>.ICON_WARNING<br />
<strong>SWT</strong>.ICON_WORKING<br />
Dialog zur Auswahl eines Druckers und für die Druckereinrichtung. Siehe auch<br />
Abschnitt 8.9.
164<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
import org.eclipse.swt.<strong>SWT</strong>;<br />
import org.eclipse.swt.widgets.Display;<br />
import org.eclipse.swt.widgets.MessageBox;<br />
import org.eclipse.swt.widgets.Shell;<br />
public class widgetTest {<br />
}<br />
public widgetTest() {<br />
super();<br />
}<br />
public static void main(String[] args) {<br />
// Display-Instanz erzeugen<br />
final Display display = new Display();<br />
// TopLevel-Shell erzeugen (display als parent)<br />
final Shell toplevelShell = new Shell(display);<br />
// Titelzeile setzen<br />
toplevelShell.setText("TopLevel.Titelzeile");<br />
// Shell anzeigen<br />
toplevelShell.open();<br />
while (true) {<br />
// MessageBox erzeugen<br />
MessageBox box =<br />
new MessageBox(<br />
toplevelShell,<br />
<strong>SWT</strong>.RETRY<br />
| <strong>SWT</strong>.CANCEL<br />
| <strong>SWT</strong>.APPLICATION_MODAL<br />
| <strong>SWT</strong>.ICON_QUESTION);<br />
// Titel setzen<br />
box.setText("Test");<br />
// Nachricht setzen<br />
box.setMessage("Wollen Sie das noch einmal versuchen?");<br />
// MessageBox öffnen<br />
if (box.open() == <strong>SWT</strong>.CANCEL)<br />
break;<br />
}<br />
}<br />
Abb. 8–4 Die im Beispiel erzeugte Message-Box unter Windows XP.
8.5 Widgets<br />
Für eigene, komplexere Dialoge wird man allerdings in der Regel<br />
anstelle der oben besprochenen <strong>SWT</strong>-Klasse Dialog die gleichnamige<br />
JFace-Klasse (siehe Kapitel 9) verwenden, die wesentlich komfortabler<br />
in der Handhabung ist. Fast alle Dialoge in der Eclipse-Workbench<br />
bauen auf JFace-Dialogen auf und nicht auf dem <strong>SWT</strong>-Dialog.<br />
8.5.4 Composites, Groups und Canvas<br />
Normalerweise bringt man Widgets auf einer Shell nicht direkt an,<br />
sondern setzt noch eine oder mehrere Hierarchien von Composite-<br />
Instanzen dazwischen. Composites dienen dem Gruppieren von Widgets.<br />
So wird man beispielsweise die Buttons eines Dialogs mit Hilfe<br />
von Composites zu Gruppen zusammenfassen (besonders wichtig bei<br />
Radiobuttons, bei denen sich die Buttons innerhalb eines Composites<br />
gegenseitig auslösen) oder wird mehrere Eingabefelder sowie Labels<br />
aus Layout- und Navigationsgründen zusammen gruppieren.<br />
Will man nun Widgets einem Composite hinzufügen, so sucht man<br />
zunächst vergebens nach einer add()-Methode. Stattdessen werden<br />
GUI-Oberflächen auf andere Art und Weise konstruiert. Beim Anlegen<br />
eines neuen Widgets wird das übergeordnete Composite als Parameter<br />
übergeben, und damit ist das Widget dem Composite zugeordnet. Die<br />
Anordnung auf dem Composite erfolgt dann in der Reihenfolge, in der<br />
die einzelnen Widgets erzeugt wurden.<br />
Da auch Composites Widgets sind, wird auch beim Erzeugen einer<br />
Composite-Instanz ein übergeordnetes Composite angegeben (auch<br />
Shells sind Composites). In der Regel wird man wichtige Eigenschaften<br />
wie Hintergrund- und Vordergrundfarbe sowie Schriftart vom übergeordneten<br />
Composite übernehmen, wie im folgenden Code gezeigt.<br />
Auch die Position und Abmessungen des Composite können relativ<br />
zum übergeordneten Composite angegeben werden.<br />
// neue Composite-Instanz erzeugen<br />
final Composite composite = new Composite(parent,0);<br />
// Eigenschaften von übergeordnetem Composite übernehmen<br />
composite.setBackground(parent.getBackground());<br />
composite.setForeground(parent.getForeground());<br />
composite.setFont(parent.getFont());<br />
// Position und Abmessungen setzen<br />
composite.setBounds(X,Y,WIDTH,HEIGHT);<br />
Optional kann im Konstruktor als zweiter Parameter die Konstante<br />
<strong>SWT</strong>. NO_RADIO_GROUP angegeben werden, wenn man Radiobuttons<br />
gruppieren will, ohne ihr Auslöseverhalten zu beeinflussen.<br />
Eine Unterklasse der Klasse Composite ist die Klasse Group. Diese<br />
zeichnet zusätzlich eine Randlinie. <strong>Das</strong> Aussehen dieser Linie kann mit<br />
Composites<br />
Groups<br />
165
166<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
den Stilkonstanten <strong>SWT</strong>.SHADOW_ETCHED_IN, <strong>SWT</strong>.SHADOW_ETCHED_OUT,<br />
<strong>SWT</strong>.SHADOW_IN, <strong>SWT</strong>.SHADOW_OUT und <strong>SWT</strong>.SHADOW_NONE gesteuert werden,<br />
sofern das Betriebssystem das unterstützt. Mit setText() kann ein<br />
Titel in die Randlinie hineingesetzt werden. In vielen Fällen ist es günstiger,<br />
eine Group anstelle eines Composite zu verwenden. Insbesondere<br />
in komplexen Dialogen erlauben Groups eine bessere Navigation mit<br />
Hilfe der Tastatur, sind also benutzerfreundlicher für Behinderte (siehe<br />
auch Abschnitt 8.11).<br />
Für Composite- und Group-Instanzen, die andere Widgets enthalten,<br />
kann ein Layout gesetzt werden (siehe Abschnitt 8.6).<br />
Canvas Die Klasse Canvas ist eine Unterklasse von Composite. Sie ist nicht<br />
dazu bestimmt, andere GUI-Elemente zu enthalten, sondern dient als<br />
Zeichenfläche für Grafikoperationen. Insbesondere ist es möglich, auf<br />
der Grundlage von Canvas mit geeigneten Zeichenoperationen eigene<br />
GUI-Elemente zu realisieren.<br />
Außerdem bietet Canvas Unterstützung für eine Schreibmarke an<br />
(setCaret() und getCaret()).<br />
8.5.5 Tasten<br />
Damit kommen wir auch schon zu den Buttons. Dabei gibt es verschiedene<br />
Spielarten. Welcher Button-Typ von einem Konstruktor erzeugt<br />
wird, wird über den Stilparameter gesteuert:<br />
<strong>SWT</strong>.ARROW<br />
<strong>SWT</strong>.CHECK<br />
<strong>SWT</strong>.PUSH<br />
<strong>SWT</strong>.RADIO<br />
<strong>SWT</strong>.TOGGLE<br />
Button mit einem kleinen Pfeil. Üblicherweise<br />
verwendet, um kleine Menüs einzublenden.<br />
Kästchen (Checkbox) zum Ankreuzen. Der<br />
Text befindet sich neben dem Kästchen.<br />
Taste mit Beschriftung auf der Tastenfläche.<br />
Radiobutton. Mehrere Radiobuttons innerhalb<br />
einer Gruppe lösen sich gegenseitig aus.<br />
Wie <strong>SWT</strong>.PUSH, nur dass beim ersten Klick die<br />
Taste gedrückt bleibt und beim zweiten Klick<br />
wieder auslöst.
8.5 Widgets<br />
Daneben gibt es noch Möglichkeiten, das Aussehen und die Ausrichtung<br />
der Buttons zu beeinflussen:<br />
<strong>SWT</strong>.FLAT Der Button wird nicht dreidimensional gezeichnet, sondern »flach«.<br />
<strong>SWT</strong>.BORDER Der Button wird mit einem Rahmen umgeben.<br />
Diese Attribute werden allerdings nicht von allen Plattformen unterstützt.<br />
Mit den Methoden setText() und setImage() können Buttons mit<br />
einem Text und einer Grafik versehen werden. Bei den Arten PUSH und<br />
TOGGLE erscheinen Text und Grafik auf der Fläche des Buttons, bei den<br />
Arten CHECK und RADIO rechts neben dem Button. Buttons der Art ARROW<br />
zeigen weder Text noch Grafik an.<br />
Die beiden Methoden schließen sich gegenseitig aus. Entweder:<br />
oder:<br />
final Button button = new Button(composite,<strong>SWT</strong>.PUSH);<br />
button.setText("Drück mich!");<br />
// Auf Klickereignisse reagieren<br />
button.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Taste wurde gedrückt");<br />
}<br />
});<br />
final Button button = new Button(composite,<strong>SWT</strong>.PUSH);<br />
Display display = composite.getDisplay();<br />
final Image image = new Image(display, "bilder/grafik.gif");<br />
button.setImage(image);<br />
// Auf Klickereignisse reagieren<br />
button.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Taste wurde gedrückt");<br />
}<br />
});<br />
// Bild entsorgen, wenn der Button entsorgt wird<br />
button.addDisposeListener(new DisposeListener() {<br />
public void widgetDisposed(DisposeEvent e) {<br />
image.dispose();<br />
}<br />
});<br />
Im zweiten Fall war es notwendig, die Image-Ressource wieder freizugeben,<br />
sobald sie nicht mehr gebraucht wurde, denn Bilder belegen<br />
Ressourcen im Betriebssystem.<br />
Texte und Bilder<br />
167
168<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Tipp: Eine gute Quelle für Bilder für Buttons, Werkzeugleisten und anderes<br />
sind die jeweiligen icons-Verzeichnisse der verschiedenen Eclipse-Plugins,<br />
z.B. \eclipse\plugins\org.eclipse.pde.ui_3.0.0\icons\obj16.<br />
8.5.6 Schieberegler, Skalen und Fortschrittsbalken<br />
Slider und Scale<br />
Die beiden Klassen Slider und Scale dienen der Eingabe eines numerischen<br />
Werts mittels eines Schiebereglers. Üblicherweise wird Slider<br />
für die Positionierung von Fensterinhalten verwendet (Scroll), während<br />
Scale für die Einstellung numerischer Parameter verwendet wird,<br />
z.B. für Lautstärke, Helligkeit, Kontrast, etc. (Abb. 8–5)<br />
Abb. 8–5 Slider und Scale, jeweils innerhalb einer Group.<br />
Die folgenden Stilkonstanten beeinflussen das Aussehen dieser Widgets:<br />
<strong>SWT</strong>.HORIZONTAL<br />
<strong>SWT</strong>.VERTICAL<br />
<strong>SWT</strong>.BORDER<br />
Horizontale oder vertikale Darstellung<br />
Skalen werden mit einem Rahmen umgeben. Wirkungslos bei<br />
Slider.<br />
<strong>Das</strong> folgende Beispiel erzeugt einen einfachen Schieberegler:<br />
final Slider slider = new Slider(composite,<strong>SWT</strong>.HORIZONTAL);<br />
// Minimalwert setzen<br />
slider.setMinimum(0);<br />
// Maximalwert setzen<br />
slider.setMaximum(1000);<br />
// Inkrement für Pfeiltasten setzen<br />
slider.setIncrement(50);<br />
// Inkrement für Klick auf Schiebefläche setzen<br />
slider.setPageIncrement(200);<br />
// aktuelle Position setzen<br />
slider.setSelection(500);<br />
// Größe des Handgriffs setzen
slider.setThumb(200);<br />
// Auf Schiebeereignisse reagieren<br />
slider.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Schieberegler verstellt: "<br />
+slider.getSelection());<br />
}<br />
});<br />
8.5 Widgets<br />
Mit entsprechenden get...-Methoden können diese Werte auch abgefragt<br />
werden. Scale hat die gleichen Methoden, allerdings gibt es hier<br />
die Methoden setThumb() und getThumb() nicht.<br />
ProgressBar<br />
Die Klasse ProgressBar dient zur Darstellung von Fortschrittsbalken.<br />
<strong>Das</strong> API ähnelt stark dem der Klasse Slider, allerdings erzeugen ProgressBar-Instanzen<br />
keine Ereignisse. Außerdem kommen noch zwei<br />
Stilkonstanten hinzu:<br />
! <strong>SWT</strong>.SMOOTH bewirkt, dass der Balken nicht durchbrochen gezeichnet<br />
wird.<br />
! <strong>SWT</strong>.INDETERMINATE bewirkt, dass der Balken endlos durchläuft.<br />
Hier ist also die Anwendung von setSelection() nicht erforderlich.<br />
Die Anwendung dieser Klasse in der Praxis ist nicht ganz einfach, da<br />
der Balken nur aktualisiert wird, wenn die Ereignisschleife nicht blockiert<br />
ist.<br />
Scrollable und ScrollBar<br />
Eine Reihe von Widgets sind von vornherein mit Schiebern ausgestattet,<br />
und zwar alle Abkömmlinge von Scrollable. Welche Schieber bei<br />
diesen Widgets aktiv sein sollen, kann mit den Stilkonstanten<br />
<strong>SWT</strong>.H_SCROLL und <strong>SWT</strong>.V_SCROLL gesteuert werden. Die Klasse Scrollable<br />
benutzt als Schieberegler übrigens nicht Slider-Instanzen, sondern<br />
Instanzen der Klasse ScrollBar.<br />
ScrollBar ist im Unterschied zu Slider und Scale keine Unterklasse<br />
von Control.<br />
8.5.7 Textfelder und Beschriftungen<br />
Instanzen der Klasse Text werden benutzt, um Text anzuzeigen, einzugeben<br />
oder zu verändern (Abb. 8–6). Mit Hilfe von Stilkonstanten<br />
können Text-Instanzen konfiguriert werden:<br />
169
170<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
<strong>SWT</strong>.MULTI<br />
<strong>SWT</strong>.SINGLE<br />
Bestimmt, ob das Textfeld mehrere oder nur eine Zeile hat.<br />
<strong>SWT</strong>.READ_ONLY Text im Textfeld kann nicht vom Benutzer verändert werden.<br />
<strong>SWT</strong>.WRAP Automatischer Umbruch wird unterstützt.<br />
Abb. 8–6 <strong>Das</strong> obere Feld ist ein Feld vom Typ Text, das untere ist vom Typ StyledText<br />
(siehe auch Abschnitt 8.5.13). Für beide Felder wurde die Schriftart Eras Book gesetzt, im<br />
unteren Feld wurden noch Formatierungen vorgenommen. Außerdem wurde für beide<br />
Felder eine vertikale Scrollbar gewählt.<br />
Instanzen der Klasse Text erzeugen folgende Ereignistypen:<br />
SelectionEvent<br />
Wenn die Enter-Taste gedrückt wird, wird die SelectionListener-<br />
Methode widgetDefaultSelected() aufgerufen<br />
ModifyEvent Nachdem Text verändert wurde.<br />
VerifyEvent<br />
Bevor Text verändert wird. Durch Zuweisung des Werts false an<br />
die Variable doit des Ereignisobjekts kann die Veränderung des<br />
Textes verhindert werden (Veto).<br />
<strong>Das</strong> folgende Beispiel erzeugt ein Textfeld mit einem VerifyListener<br />
für die Gültigkeitsprüfung:<br />
final Text text = new Text(composite,<strong>SWT</strong>.SINGLE);<br />
text.setText("Eingabetext");<br />
text.addSelectionListener(new SelectionAdapter() {<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
System.out.println("Enter gedrückt: "+text.getSelection());<br />
}<br />
});<br />
text.addModifyListener(new ModifyListener() {<br />
public void modifyText(ModifyEvent e) {<br />
System.out.println("Text nach Modifikation: "+text.getText());<br />
}<br />
});<br />
text.addVerifyListener(new VerifyListener() {<br />
public void verifyText(VerifyEvent e) {<br />
String s = text.getText();
}<br />
});<br />
System.out.println("Text vor Modifikation: "+s);<br />
// Veto: Eingaben länger als 10 Zeichen verhindern<br />
if (s.length() >= 10) e.doit = false;<br />
8.5 Widgets<br />
Die Klasse Text besitzt ein reichhaltiges Arsenal an Methoden für die<br />
Texteingabe. Insbesondere gibt es auch Methoden, um Textinhalte mit<br />
dem systemweiten Clipboard auszutauschen (cut(), copy(), paste()).<br />
Instanzen der Klasse Label dienen meist dazu, andere Widgets zu<br />
beschriften. Daneben können Label benutzt werden, um eine Grafik<br />
bzw. eine horizontale oder vertikale Linie abzubilden. Der jeweilige<br />
Einsatzzweck und das Aussehen eines Labels können über folgende<br />
Stilkonstanten beeinflusst werden.<br />
<strong>SWT</strong>.SEPARATOR Stellt eine horizontale oder vertikale Linie dar.<br />
<strong>SWT</strong>.HORIZONTAL<br />
<strong>SWT</strong>.VERTICAL<br />
<strong>SWT</strong>.SHADOW_IN<br />
<strong>SWT</strong>.SHADOW_OUT<br />
<strong>SWT</strong>.SHADOW_NONE<br />
<strong>SWT</strong>.CENTER<br />
<strong>SWT</strong>.LEFT<br />
<strong>SWT</strong>.RIGHT<br />
Der folgende Code zeigt die Erzeugung eines Textlabels:<br />
final Label label = new Label(composite, <strong>SWT</strong>.NULL);<br />
label.setText("Eingabe");<br />
Bildlabel werden mit der Methode setImage() erzeugt. Wie bei Buttons<br />
(Abschnitt 8.5.6) sollten Image-Instanzen wieder freigegeben werden,<br />
wenn sie nicht mehr benötigt werden.<br />
8.5.8 Tabellen, Listen und Combos<br />
Stellt Orientierung einer Linie fest.<br />
Schattendarstellung einer Linie.<br />
Ausrichtung eines Text- oder Bildlabels.<br />
<strong>SWT</strong>.WRAP Automatischer Umbruch bei Textlabels.<br />
Tabellen und Listen dienen der tabellarischen Darstellung von Inhalten,<br />
wobei auch die Auswahl von Elementen unterstützt wird. Eine<br />
Platz sparende Variante für die Listenauswahl sind Combos (Abb. 8–7).<br />
Beschriftungen<br />
171
172<br />
<strong>SWT</strong>.SINGLE<br />
<strong>SWT</strong>.MULTI<br />
<strong>SWT</strong>.FULL_SELECTION<br />
<strong>SWT</strong>.CHECK<br />
<strong>SWT</strong>.VIRTUAL<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Abb. 8–7 Von links nach rechts: Tabelle, Liste und Combo. Oben sieht man das Combo<br />
im Normalzustand, darunter nach einem Klick auf die Pfeiltaste. Bei der Tabelle wurden<br />
die Gitterlinien und die Kopfzeile sichtbar geschaltet.<br />
Tabellen<br />
Die Klasse Table ist für die Darstellung von Tabellen verantwortlich.<br />
Zusätzlich zu den Composite-Stilkonstanten stehen noch folgende<br />
Stilkonstanten zur Verfügung:<br />
Der Benutzer kann nur eine bzw. mehrere Tabellenzeilen auswählen.<br />
Die gesamte Tabellenzeile ist selektierbar (normalerweise kann nur das erste<br />
Element selektiert werden).<br />
Vor jeder Tabellenzeile wird eine Checkbox abgebildet. Der Zustand der Checkbox kann<br />
mit den Methoden setChecked() und getChecked() gesetzt bzw. abgefragt werden.<br />
Diese Konstante zeigt eine virtuelle Tabelle an, d.h. eine Tabelle, deren Elemente<br />
erst erzeugt werden, wenn sie tatsächlich benötigt werden. Damit können sehr große<br />
Tabellen realisiert werden. Beim Aufbau einer virtuellen Tabelle muss die Anzahl der<br />
Tabelleneinträge mit der Methode setItemCount() gesetzt werden. Sobald ein neues<br />
Tabellenelement (TableItem) benötigt wird, wird es von der Tabelle erzeugt. Dann<br />
wird ein <strong>SWT</strong>.SetData-Ereignis ausgelöst, wobei das Event-Objekt das neue Tabellenelement<br />
enthält. Im Listener kann dann das Tabellenelement fertiggestellt werden,<br />
bevor es dann von der Tabelle zur Anzeige gebracht wird.<br />
Table-Instanzen erzeugen SelectionEvent-Objekte, wenn ein Tabellenelement<br />
selektiert wird. Beim SelectionListener wird die Methode<br />
widgetDefaultSelected() aufgerufen, wenn auf einem Tabellenelement<br />
Enter gedrückt oder ein Doppelklick ausgeführt wurde.<br />
Tabellenspalten Zur Konfiguration der einzelnen Tabellenspalten werden jeder<br />
Tabelle TableColumn-Objekte zugeordnet. <strong>Das</strong> geschieht auf die gleiche<br />
Weise, in der Widgets auf einem Composite angebracht werden. Die<br />
Table-Instanz wird dem TableColumn()-Konstruktor als Parameter
8.5 Widgets<br />
übergeben. Dann kann man für jedes TableColumn-Objekt noch mit<br />
setText() eine Spaltenüberschrift und mit setWidth() die Breite in<br />
Pixeln setzen.<br />
Die Breite von Tabellenspalten kann vom Benutzer verändert werden.<br />
Auch sind die Spaltenköpfe als Tasten ausgelegt. Folglich erzeugen<br />
TableColumn-Objekte eine Anzahl von Ereignissen. Ein ControlEvent<br />
wird erzeugt, wenn die Tabellenspalte verschoben oder in der<br />
Größe verändert wird. Ein SelectionEvent wird erzeugt, wenn der<br />
Spaltenkopf angeklickt wird.<br />
Die Ausrichtung der Spalten kann durch die Stilkonstanten<br />
<strong>SWT</strong>.LEFT, <strong>SWT</strong>.CENTER und <strong>SWT</strong>.RIGHT bestimmt werden. Mit der<br />
Methode showColumn() können wir eine bestimmte Spalte der Tabelle<br />
in den sichtbaren Anzeigebereich bringen.<br />
Auf ähnliche Weise werden die Tabellenzeilen als TableItem-<br />
Objekte erzeugt. Mit der Methode setText() wird der Inhalt der<br />
Tabellenzeilen gesetzt. Als Parameter kann ein String oder – im Fall<br />
mehrspaltiger Tabellen – ein Stringarray angegeben werden. Seit<br />
Eclipse V3 kann man sogar mit setForeground(), setFont() bzw. set-<br />
Background() die Textfarbe, die Schriftart bzw. Hintergrundfarbe für<br />
jedes einzelne TableItem setzen.<br />
Mit den Table-Methoden setHeaderVisible() und setLinesVisible()<br />
können die Spaltenüberschriften und Trennlinien angezeigt oder<br />
unterdrückt werden.<br />
Im folgenden Code erzeugen wir eine Tabelle mit drei Spalten und<br />
zwei Zeilen:<br />
final Table table = new Table(composite,<br />
<strong>SWT</strong>.SINGLE | <strong>SWT</strong>.H_SCROLL |<br />
<strong>SWT</strong>.V_SCROLL | <strong>SWT</strong>.BORDER |<br />
<strong>SWT</strong>.FULL_SELECTION );<br />
// Drei Tabellenspalten erzeugen<br />
final TableColumn col1 = new TableColumn(table,<strong>SWT</strong>.LEFT);<br />
col1.setText("Spalte 1");<br />
col1.setWidth(80);<br />
final TableColumn col2 = new TableColumn(table,<strong>SWT</strong>.LEFT);<br />
col2.setText("Spalte 2");<br />
col2.setWidth(80);<br />
final TableColumn col3 = new TableColumn(table,<strong>SWT</strong>.LEFT);<br />
col3.setText("Spalte 3");<br />
col3.setWidth(80);<br />
// Spaltenköpfe und Trennlinien sichtbar machen<br />
table.setHeaderVisible(true);<br />
table.setLinesVisible(true);<br />
// Zwei Tabellenreihen erzeugen<br />
Tabellenzeilen<br />
173
174<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Listen<br />
final TableItem item1 = new TableItem(table,0);<br />
item1.setText(new String[] {"a","b","c"});<br />
final TableItem item2 = new TableItem(table,0);<br />
item2.setText(new String[] {"d","c","e"});<br />
// SelectionListener hinzufügen<br />
table.addSelectionListener(new SelectionAdapter() {<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
processSelection("Enter gedrückt: ");<br />
}<br />
public void widgetSelected(SelectionEvent e) {<br />
processSelection("Tabellenelement ausgewählt: ");<br />
}<br />
private void processSelection(String message) {<br />
// Ausgewählte Tabellenzeilen holen<br />
TableItem[] selection = table.getSelection();<br />
// Wegen <strong>SWT</strong>.SINGLE ist nur eine Zeile ausgewählt<br />
TableItem selectedRow = selection[0];<br />
// Die einzelnen Tabellenelemente für Ausgabe aufbereiten<br />
String s = selectedRow.getText(0)+", "+<br />
selectedRow.getText(1)+", "+selectedRow.getText(2);<br />
System.out.println(message + s);<br />
}<br />
});<br />
Will man lediglich eine einspaltige Liste von String-Elementen zur<br />
Selektion anbieten, so gibt es mit der Klasse List eine einfachere Möglichkeit.<br />
List-Instanzen erzeugen die gleichen Ereignisse wie Table-<br />
Instanzen, jedoch wird die Methode widgetDefaultSelected() nur bei<br />
einem Doppelklick aufgerufen. Mit den <strong>SWT</strong>-Konstanten <strong>SWT</strong>.SINGLE<br />
und <strong>SWT</strong>.MULTI kann bestimmt werden, ob nur eine oder aber mehrere<br />
Listeneinträge selektiert werden können.<br />
Im folgenden Code bauen wir eine Liste mit drei Einträgen auf.<br />
Wir erlauben und verarbeiten die Selektion mehrerer Einträge:<br />
final List list = new List(composite,<strong>SWT</strong>.MULTI);<br />
list.add("Element1");<br />
list.add("Element2");<br />
list.add("Element3");<br />
list.addSelectionListener(new SelectionAdapter() {<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
processSelection("Enter gedrückt: ");<br />
}<br />
public void widgetSelected(SelectionEvent e) {<br />
processSelection("Listeneintrag ausgewählt: ");<br />
}
private void processSelection(String message) {<br />
// Ausgewählte Element holen<br />
String[] selection = list.getSelection();<br />
// Für die Ausgabe aufbereiten<br />
StringBuffer sb = new StringBuffer();<br />
for (int i = 0; i < selection.length; i++) {<br />
sb.append(selection[i]+" ");<br />
}<br />
System.out.println(message + sb);<br />
}<br />
});<br />
Combos<br />
8.5 Widgets<br />
Schließlich gibt es noch die Klasse Combo, die Listenauswahl und Texteingabe<br />
kombiniert.<br />
Instanzen der Klasse Combo erzeugen folgende Ereignistypen:<br />
SelectionEvent<br />
ModifyEvent<br />
Die folgenden Stilkonstanten beeinflussen die Funktionsweise und das<br />
Aussehen der Combo-Instanzen:<br />
<strong>SWT</strong>.DROP_DOWN<br />
<strong>SWT</strong>.READ_ONLY<br />
Wenn die Enter-Taste gedrückt wird, wird die Selection-<br />
Listener-Methode widgetDefaultSelected() aufgerufen.<br />
Wird ein Listenelement ausgewählt, so wird die Methode<br />
widgetSelected() aufgerufen.<br />
Wird aufgerufen, wenn Text durch Tastatureingabe oder<br />
Listenauswahl verändert wird.<br />
Die Auswahlliste wird erst beim Klick auf die Pfeiltaste<br />
angezeigt.<br />
Es ist nur möglich, vorgegebene Werte auszuwählen, jedoch<br />
nicht, Werte im Textfeld einzugeben.<br />
<strong>SWT</strong>.SIMPLE Die Auswahlliste ist immer sichtbar.<br />
<strong>Das</strong> folgende Beispiel erzeugt ein solches Combo-GUI-Element:<br />
final Combo combo = new Combo(composite,<strong>SWT</strong>.DROP_DOWN);<br />
// Listenelement erzeugen<br />
combo.add("Element1");<br />
combo.add("Element2");<br />
combo.add("Element3");<br />
// Vorbelegung des Textfeldes<br />
combo.setText("Select");<br />
// SelectionListener hinzufügen<br />
combo.addSelectionListener(new SelectionAdapter() {<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
175
176<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
System.out.println("Enter gedrückt: " + combo.getText());<br />
}<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Listeneintrag ausgewählt: " +<br />
combo.getText());<br />
}<br />
});<br />
// ModifyListener hinzufügen<br />
combo.addModifyListener(new ModifyListener() {<br />
public void modifyText(ModifyEvent e) {<br />
System.out.println("Text wurde modifiziert: "+combo.getText());<br />
}<br />
});<br />
CCombo <strong>Das</strong> (nicht native) Widget CCombo ist dem Combo-Widget sehr ähnlich.<br />
Es kann jedoch auch randlos auftreten und kann so in Tabellenzellen<br />
angewandt werden.<br />
8.5.9 Bäume<br />
Die Klasse Tree ist für die Darstellung von Bäumen verantwortlich<br />
(Abb. 8–8). Dabei werden die Funktionsweise und das Aussehen des<br />
Baums durch folgende Stilkonstanten beeinflusst:<br />
<strong>SWT</strong>.SINGLE<br />
<strong>SWT</strong>.MULTI<br />
<strong>SWT</strong>.CHECK<br />
Der Benutzer kann nur eine bzw. mehrere Baumknoten<br />
auswählen.<br />
Vor jedem Baumknoten wird eine Checkbox abgebildet. Der<br />
Zustand der Checkbox kann mit den Methoden setChecked()<br />
und getChecked() gesetzt bzw. abgefragt werden.<br />
Abb. 8–8 Zwei Bäume: links nur mit Textknoten; rechts wurden den Textknoten Bilder<br />
zugewiesen.
Tree-Instanzen erzeugen folgende Ereignistypen:<br />
SelectionEvent<br />
TreeEvent<br />
8.5 Widgets<br />
Bei einem Doppelklick oder Drücken der Enter-Taste wird<br />
die SelectionListener-Methode widgetDefaultSelected()<br />
aufgerufen.<br />
Wird ein Knoten ausgewählt, so wird die Methode<br />
widgetSelected() aufgerufen.<br />
Wird ein Knoten expandiert, wird die Methode treeExpanded()<br />
der TreeListener-Instanz aufgerufen. Wird ein Knoten<br />
kollabiert, wird treeCollapsed() aufgerufen. Der entsprechende<br />
Knoten wird im Feld item des TreeEvent-Objekts<br />
übergeben.<br />
Die einzelnen Baumknoten werden als TreeItem-Instanzen erzeugt.<br />
Dabei wird dem TreeItem()-Konstruktor entweder der Baum oder ein<br />
anderer Baumknoten als Argument übergeben. Textinhalte für TreeItem-Instanzen<br />
können mit setText() gesetzt werden, wobei mit set-<br />
Font() die Schriftart des Textes gesetzt werden kann. Zusätzlich zum<br />
Text kann mit setImage() jeder TreeItem-Instanz eine Grafik zugeordnet<br />
werden. Wie bei Buttons (Abschnitt 8.5.6) sollten alle Image-Instanzen<br />
wieder freigegeben werden, wenn sie nicht mehr benötigt werden.<br />
Der folgende Code erzeugt einen einfachen Baum mit drei Knoten,<br />
wobei der erste Knoten zwei Kindknoten hat:<br />
final Tree tree = new Tree(composite,<strong>SWT</strong>.SINGLE);<br />
// Oberste Knotenebene erzeugen<br />
final TreeItem node1 = new TreeItem(tree,<strong>SWT</strong>.NULL);<br />
node1.setText("Knoten 1");<br />
final TreeItem node2 = new TreeItem(tree,<strong>SWT</strong>.NULL);<br />
node2.setText("Knoten 2");<br />
final TreeItem node3 = new TreeItem(tree,<strong>SWT</strong>.NULL);<br />
node3.setText("Knoten 3");<br />
// Zweite Knotenebene erzeugen<br />
final TreeItem node11 = new TreeItem(node1,<strong>SWT</strong>.NULL);<br />
node11.setText("Knoten 1.1");<br />
final TreeItem node12 = new TreeItem(node1,<strong>SWT</strong>.NULL);<br />
node12.setText("Knoten 1.2");<br />
// SelectionListener hinzufügen<br />
tree.addSelectionListener(new SelectionAdapter() {<br />
public void widgetDefaultSelected(SelectionEvent e) {<br />
System.out.println("Enter gedrückt: " +<br />
tree.getSelection()[0].getText());<br />
}<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println("Baumknoteneintrag ausgewählt: " +<br />
tree.getSelection()[0].getText());<br />
}<br />
177
178<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
});<br />
// TreeListener hinzufügen<br />
tree.addTreeListener(new TreeAdapter() {<br />
public void treeCollapsed(TreeEvent e) {<br />
System.out.println("Knoten kollabiert: " +<br />
((TreeItem) e.item).getText());<br />
}<br />
public void treeExpanded(TreeEvent e) {<br />
System.out.println("Knoten expandiert: " +<br />
((TreeItem) e.item).getText());<br />
}<br />
});<br />
Bei größeren Bäumen wird man in der Regel darauf verzichten, den<br />
Baum gleich komplett aufzubauen. Stattdessen bietet sich hier an,<br />
Baumknoten erst dann vollständig aufzubauen, wenn sie sichtbar werden,<br />
also wenn der entsprechende Elternknoten expandiert wird.<br />
8.5.10 Verschiebbare Trennleisten<br />
Die Klasse Sash ist für verschiebbare Trennleisten verantwortlich.<br />
Diese Trennleisten können benutzt werden, um ein Composite in verschiedene<br />
Bereiche aufzuteilen. Die Trennleiste kann vom Benutzer<br />
verschoben werden, so dass sich die Größe der Teilbereiche verändert.<br />
<strong>Das</strong> verlangt einigen Aufwand an Ereignisbehandlung, da der Sash<br />
nicht selbst eine Repositionierung der Nachbarelemente vornimmt.<br />
Sash-Instanzen erzeugen Ereignisse vom Typ SelectEvent. Die Ausrichtung<br />
einer Trennleiste kann mit den Stilkonstanten <strong>SWT</strong>.HORIZONTAL<br />
und <strong>SWT</strong>.VERTICAL bestimmt werden (Abb. 8–9).<br />
Anstelle dieser manuellen Methode kann auch die Klasse SashForm<br />
verwendet werden, um durch verschiebbare Rahmen getrennte Komponenten<br />
untereinander zu koordinieren (siehe Abschnitt 8.5.13).<br />
8.5.11 Pultordner<br />
Die Klasse TabFolder implementiert einen Pultordner, also eine Einheit,<br />
bei der Seiten über das Anklicken von Reitern ausgewählt werden<br />
können. Jede TabFolder-Instanz ist ein Composite, das ein oder mehrere<br />
TabItem-Instanzen enthalten kann. Jedes TabItem-Objekt entsprícht<br />
einem Reiter, die Beschriftung des Reiters kann mit der Methode set-<br />
Text() gesetzt werden. Mit setControl() kann jedem TabItem-Objekt<br />
eine Control-Instanz (z.B. ein Composite) zugeordnet werden. Diese<br />
wird zur Anzeige gebracht, wenn das entsprechende TabItem-Objekt
8.5 Widgets<br />
selektiert wird. Die Control-Instanz muss auf dem TabFolder erzeugt<br />
worden sein (also mit Angabe der TabFolder-Instanz im Konstruktor).<br />
TabFolder unterstützt lediglich die Stilkonstante <strong>SWT</strong>.BORDER.<br />
TabFolder-Instanzen erzeugen SelectionEvents, wenn ein TabItem<br />
ausgewählt wird.<br />
Der folgende Code zeigt als Beispiel einen Pultordner mit zwei Reitern:<br />
import org.eclipse.swt.<strong>SWT</strong>;<br />
import org.eclipse.swt.events.SelectionAdapter;<br />
import org.eclipse.swt.events.SelectionEvent;<br />
import org.eclipse.swt.layout.FillLayout;<br />
import org.eclipse.swt.widgets.*;<br />
public class widgetTest {<br />
public static void main(String[] args) {<br />
// Display-Instanz erzeugen<br />
final Display display = new Display();<br />
// TopLevel-Shell erzeugen (display als parent)<br />
final Shell toplevelShell = new Shell(display);<br />
// Titelzeile setzen<br />
toplevelShell.setText("TopLevel.Titelzeile");<br />
// Shell komplett ausfüllen<br />
toplevelShell.setLayout(new FillLayout());<br />
// Pultordner erzeugen<br />
TabFolder folder = new TabFolder(toplevelShell, <strong>SWT</strong>.NONE);<br />
// Selektionsereignisse protokollieren<br />
folder.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println(<br />
"Reiter selektiert: " + ((TabItem)<br />
(e.item)).getText());<br />
}<br />
});<br />
// Pultordner komplett ausfüllen<br />
folder.setLayout(new FillLayout());<br />
Composite page1 = createTabPage(folder, "tab1");<br />
// Auf page1 können nun weitere GUI-Elemente angeordnet werden<br />
// ...<br />
Composite page2 = createTabPage(folder, "tab2");<br />
// Auf page2 können nun weitere GUI-Elemente angeordnet werden<br />
// ...<br />
// Shell anzeigen<br />
toplevelShell.open();<br />
// Ereignisschleife<br />
while (!toplevelShell.isDisposed()) {<br />
179
180<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
}<br />
}<br />
}<br />
if (!display.readAndDispatch())<br />
display.sleep();<br />
private static Composite createTabPage(TabFolder folder,<br />
String label) {<br />
// Einen Reiter erzeugen und beschriften<br />
TabItem tab = new TabItem(folder, <strong>SWT</strong>.NONE);<br />
tab.setText(label);<br />
// Einen Composite als Seite erzeugen<br />
Composite page = new Composite(folder, <strong>SWT</strong>.NONE);<br />
// ... und dem Reiter zuordnen<br />
tab.setControl(page);<br />
return page;<br />
}<br />
CTabFolder <strong>Das</strong> (nicht native) Widget CTabFolder ist dem TabFolder-Widget recht<br />
ähnlich, erlaubt jedoch die Positionierung der Reiter (CTabItem) am<br />
Kopf (<strong>SWT</strong>.TOP) oder Fuß (<strong>SWT</strong>.BOTTOM) des Ordners und verwendet Reiter<br />
mit geschwungenem Umriss.<br />
8.5.12 Werkzeugleisten und Menüs<br />
Werkzeugleisten<br />
Mit der Klasse ToolBar können Werkzeugleisten implementiert werden.<br />
Jede ToolBar-Instanz ist ein Composite, das ein oder mehrere ToolItem-Instanzen<br />
enthält.<br />
Mit den folgenden Stilkonstanten kann das Erscheinungsbild von<br />
Werkzeugleisten bestimmt werden:<br />
<strong>SWT</strong>.FLAT Zweidimensionale statt dreidimensionaler Darstellung.<br />
Plattformabhängig.<br />
<strong>SWT</strong>.WRAP Automatischer Umbruch<br />
<strong>SWT</strong>.RIGHT Rechtsbündige Ausrichtung<br />
<strong>SWT</strong>.HORIZONTAL<br />
<strong>SWT</strong>.VERTICAL<br />
Horizontale oder vertikale Ausrichtung<br />
ToolItem-Instanzen repräsentieren die Tasten auf der Werkzeugleiste.<br />
Die Art der Taste kann über eine Stilkonstante bestimmt werden:
<strong>SWT</strong>.PUSH Normale Taste mit sofortiger Auslösung<br />
<strong>SWT</strong>.CHECK Arretierende Taste<br />
<strong>SWT</strong>.RADIO Radiotaste mit gegenseitiger Auslösung<br />
<strong>SWT</strong>.SEPARATOR Passives Element zum Trennen von Tastengruppen<br />
<strong>SWT</strong>.DROP_DOWN Normale Taste mit assoziierter Pfeiltaste<br />
8.5 Widgets<br />
Die Beschriftung einer Taste wird mit der Methode setText() gesetzt.<br />
Bildtasten werden mit der Methode setImage() erzeugt. Zusätzliche<br />
Bilder, mit setHotImage() (für Tasten unter dem Mauszeiger) und set-<br />
DisabledImage() (für inaktive Tasten) gesetzt, können die verschiedenen<br />
Betriebszustände der Werkzeugtaste anzeigen. Wie bei Buttons<br />
(Abschnitt 8.5.6) sollten die jeweiligen Image-Instanzen wieder freigegeben<br />
werden, wenn sie nicht mehr benötigt werden. Mit setToolTip-<br />
Text() kann ein Text gesetzt werden, der angezeigt wird, wenn die<br />
Maus über die Taste bewegt wird.<br />
ToolItem-Instanzen erzeugen bei Betätigung SelectionEvent-Ereignisse.<br />
Bei DROP_DOWN-Tasten muss ermittelt werden, ob die Haupttaste<br />
oder die Pfeiltaste betätigt wurde. Dies kann über die Abfrage<br />
(event.detail == <strong>SWT</strong>.ARROW) geschehen. Die Applikation kann dann<br />
eine Menüliste aufbauen, welche die Auswahl einer Funktion erlaubt.<br />
Verschiebbare Werkzeuggruppen (CoolBar)<br />
Mit der Klasse CoolBar können mehrere ToolBar-Instanzen als so<br />
genannte CoolItems zu verschiebbaren Gruppen zusammengefasst werden.<br />
Ein gutes Beispiel für eine CoolBar ist die Werkzeugleiste der<br />
Eclipse-Workbench. Jede einzelne Toolbar-Instanz wird dabei in eine<br />
CoolItem-Instanz eingebettet. Diese CoolItem-Instanzen werden auf die<br />
CoolBar platziert und sind dort verschiebbar. Die Zuordnung zwischen<br />
CoolItem und Toolbar erfolgt mit der Methode setControl(). Dabei<br />
muss anfänglich für jede CoolItem-Instanz eine minimale Größe gesetzt<br />
werden. Im zweiten Beispiel dieses Abschnitts zeigen wir, wie das geht.<br />
Wird für eine CoolItem-Instanz der Stil <strong>SWT</strong>.DROP_DOWN gesetzt, so<br />
erscheint ein Pfeilsymbol, wenn in einer Werkzeuggruppe nicht alle<br />
Werkzeuge abgebildet werden können. Dazu ist allerdings eine entsprechende<br />
Ereignisverarbeitung mit Aufbau eines Drop-down-Menüs<br />
zu implementieren, ähnlich wie das für Drop-down-Tasten notwendig<br />
ist.<br />
181
182<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Menüs<br />
Menüs können mit Hilfe der Klasse Menu aufgebaut werden. Mit den<br />
folgenden Stilkonstanten kann das Erscheinungsbild von Menüs<br />
bestimmt werden:<br />
<strong>SWT</strong>.BAR Menüleiste<br />
<strong>SWT</strong>.DROP_DOWN Drop-down-Menü<br />
<strong>SWT</strong>.POP_UP Pop-up-Menü<br />
Menu-Instanzen erzeugen Ereignisse vom Typ HelpEvent und MenuEvent.<br />
Erscheint ein Menü auf dem Bildschirm, wird die Methode menuShown()<br />
der MenuListener-Instanz aufgerufen. Verschwindet das Menü,<br />
wird die Methode menuHidden() aufgerufen.<br />
MenuItem Die Menüeinträge werden von MenuItem-Instanzen repräsentiert.<br />
Die Art des Eintrags kann über eine Stilkonstante bestimmt werden:<br />
<strong>SWT</strong>.CHECK<br />
Menüeintrag mit Check-Symbol. Bei Betätigung erscheint und<br />
verschwindet das Check-Symbol wechselweise.<br />
<strong>SWT</strong>.CASCADE Menüeintrag für kaskadierende Menüs<br />
<strong>SWT</strong>.PUSH Normaler Menüeintrag<br />
<strong>SWT</strong>.RADIO Menüeintrag mit gegenseitiger Auslösung<br />
<strong>SWT</strong>.SEPARATOR Passive Trennlinie<br />
Die Beschriftung eines Eintrags wird mit der Methode setText()<br />
gesetzt.<br />
MenuItem-Instanzen erzeugen bei Betätigung Ereignisse vom Typ<br />
SelectionEvent, ArmEvent und HelpEvent. ArmEvents werden ausgelöst,<br />
wenn der Mauszeiger über den Menüeintrag positioniert wird.<br />
Will man beispielsweise eine klassische Menüleiste aufbauen, so<br />
legt man zunächst die Menüleiste selbst als Menu-Instanz vom Typ<br />
<strong>SWT</strong>.BAR an. Als übergeordnetes Composite muss dabei die Shell angegeben<br />
sein, für welche die Menüleiste aufgebaut werden soll. Freilich ist<br />
dann die Menüleiste noch nicht für diese Shell aktiviert. Dies muss von<br />
der Shell-Instanz aus geschehen, und zwar mit der Methode setMenu-<br />
Bar(). Die einzelnen Menütitel werden dann als kaskadierende Menu-<br />
Item-Instanzen angelegt. Deren Untermenüs werden als unabhängige<br />
<strong>SWT</strong>.DROP_DOWN-Menüs unter der Shell-Instanz angelegt und den<br />
Menütiteln mit setMenu()zugeordnet.<br />
<strong>Das</strong> folgende Beispiel zeigt den Aufbau eines einfachen Menüs mit<br />
einem einzigen Menütitel:
Menüleiste anlegen<br />
Menu menuBar = new Menu(toplevelShell, <strong>SWT</strong>.BAR);<br />
toplevelShell.setMenuBar(menuBar);<br />
// Menütitel anlegen<br />
MenuItem fileTitle = new MenuItem(menuBar, <strong>SWT</strong>.CASCADE);<br />
fileTitle.setText("File");<br />
// Untermenü für diesen Menütitel anlegen<br />
Menu fileMenu = new Menu(toplevelShell, <strong>SWT</strong>.DROP_DOWN);<br />
fileTitle.setMenu(fileMenu);<br />
// Menüeintrag anlegen<br />
MenuItem item = new MenuItem(fileMenu, <strong>SWT</strong>.NULL);<br />
item.setText("Exit");<br />
// Ereignisverarbeitung für Menüeintrag<br />
item.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
toplevelShell.close();<br />
}<br />
});<br />
8.5 Widgets<br />
Im nächsten Beispiel erzeugen wir eine CoolBar, die aus zwei verschiebbaren<br />
Gruppen mit insgesamt fünf verschiedenen Tasten besteht. Darunter<br />
ist auch eine Drop-down-Taste, in deren Ereignisverarbeitung<br />
ein Menü mit zwei Einträgen aufgebaut wird.<br />
// CoolBar erzeugen<br />
final CoolBar coolbar = new CoolBar(composite, <strong>SWT</strong>.NULL);<br />
// ToolBar als Bestandteil der CoolBar erzeugen<br />
final ToolBar toolbar1 = new ToolBar(coolbar, <strong>SWT</strong>.NULL);<br />
// Pushbutton erzeugen<br />
final ToolItem toolitem1 = new ToolItem(toolbar1, <strong>SWT</strong>.PUSH);<br />
toolitem1.setText("Push");<br />
toolitem1.setToolTipText("Push button");<br />
// Ereignisverarbeitung für pushbutton<br />
toolitem1.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
System.out.println(<br />
"Werkzeugtaste gedrückt: " + toolitem1.getText());<br />
}<br />
});<br />
// Checkbutton erzeugen<br />
final ToolItem toolitem2 = new ToolItem(toolbar1, <strong>SWT</strong>.CHECK);<br />
toolitem2.setText("Check");<br />
toolitem2.setToolTipText("Check button");<br />
// CoolItem erzeugen<br />
final CoolItem coolitem1 = new CoolItem(coolbar, <strong>SWT</strong>.NULL);<br />
// Die Toolbar diesem Coolitem zuordnen<br />
183
184<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
coolitem1.setControl(toolbar1);<br />
// Größe der Toolbar berechnen<br />
Point size = toolbar1.computeSize(<strong>SWT</strong>.DEFAULT, <strong>SWT</strong>.DEFAULT);<br />
// Benötigte Größe des CoolItem berechnen<br />
size = coolitem1.computeSize(size.x, size.y);<br />
// auf diese Größe setzen<br />
coolitem1.setSize(size);<br />
// Die minimale Breite entspricht der des ersten Buttons<br />
coolitem1.setMinimumSize(toolitem1.getWidth(), size.y);<br />
// Zweite ToolBar erzeugen<br />
final ToolBar toolbar2 = new ToolBar(coolbar, <strong>SWT</strong>.NULL);<br />
// Zwei Radiobuttons erzeugen<br />
final ToolItem toolitem3a = new ToolItem(toolbar2, <strong>SWT</strong>.RADIO);<br />
toolitem3a.setText("Radio");<br />
toolitem3a.setToolTipText("Radio button a");<br />
final ToolItem toolitem3b = new ToolItem(toolbar2, <strong>SWT</strong>.RADIO);<br />
toolitem3b.setText("Radio");<br />
toolitem3b.setToolTipText("Radio button b");<br />
// Separator<br />
new ToolItem(toolbar2, <strong>SWT</strong>.SEPARATOR);<br />
// Drop-down-Menü-Button erzeugen<br />
final ToolItem toolitem5 = new ToolItem(toolbar2, <strong>SWT</strong>.DROP_DOWN);<br />
toolitem5.setText("Drop-down-Menu");<br />
// Ereignisverarbeitung für Drop-down-Menü-Button<br />
toolitem5.addSelectionListener(<br />
// die Menüerzeugung findet in Klasse DropDownSelectionListener statt<br />
new DropDownSelectionListener(composite.getShell()));<br />
// zweiten CoolItem erzeugen, Toolbar zuordnen und Größe setzen<br />
final CoolItem coolitem2 = new CoolItem(coolbar, <strong>SWT</strong>.NULL);<br />
coolitem2.setControl(toolbar2);<br />
size = toolbar2.computeSize(<strong>SWT</strong>.DEFAULT, <strong>SWT</strong>.DEFAULT);<br />
size = coolitem2.computeSize(size.x, size.y);<br />
coolitem2.setSize(size);<br />
coolitem2.setMinimumSize(toolitem3a.getWidth(), size.y);<br />
Die Klasse DropDownSelectionListener, die für den Menüaufbau<br />
zuständig ist, ist folgendermaßen definiert:<br />
class DropDownSelectionListener extends SelectionAdapter {<br />
private Menu menu;<br />
private Composite parent;<br />
public DropDownSelectionListener(Composite parent) {<br />
this.parent = parent;<br />
}
8.5 Widgets<br />
public void widgetSelected(final SelectionEvent e) {<br />
// Wir erzeugen das Menü, wenn es noch nicht existiert<br />
if (menu == null) {<br />
menu = new Menu(parent);<br />
final MenuItem menuItem1 = new MenuItem(menu, <strong>SWT</strong>.NULL);<br />
menuItem1.setText("Eintrag1");<br />
// SelectionListener für menuItem1 setzen<br />
menuItem1.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent m) {<br />
processMenuEvent(e, menuItem1);<br />
}<br />
});<br />
menuItem1.addArmListener(new ArmListener() {<br />
public void widgetArmed(ArmEvent m) {<br />
System.out.println("Maus über Menü-Eintrag 1");<br />
}<br />
});<br />
final MenuItem menuItem2 = new MenuItem(menu, <strong>SWT</strong>.NULL);<br />
menuItem2.setText("Eintrag2");<br />
// SelectionListener für menuItem1 setzen<br />
menuItem2.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent m) {<br />
processMenuEvent(e, menuItem2);<br />
}<br />
});<br />
menuItem2.addArmListener(new ArmListener() {<br />
public void widgetArmed(ArmEvent m) {<br />
System.out.println("Maus über Menü-Eintrag 2");<br />
}<br />
});<br />
}<br />
// Prüfen, ob die Pfeiltaste gedrückt wurde<br />
if (e.detail == <strong>SWT</strong>.ARROW) {<br />
if (menu.isVisible()) {<br />
// Ausblenden, wenn Menü bereits sichtbar<br />
menu.setVisible(false);<br />
} else {<br />
// ToolItem und ToolBar aus dem Event auslesen<br />
final ToolItem toolItem = (ToolItem) e.widget;<br />
final ToolBar toolBar = toolItem.getParent();<br />
// Position und Abmessung des ToolItem<br />
Rectangle toolItemBounds = toolItem.getBounds();<br />
// relative Position auf absolute Position umrechnen<br />
Point point =<br />
toolBar.toDisplay(<br />
185
186<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
}<br />
new Point(toolItemBounds.x, toolItemBounds.y));<br />
// Menü positionieren<br />
menu.setLocation(point.x, point.y +<br />
toolItemBounds.height);<br />
// Menü sichtbar machen<br />
menu.setVisible(true);<br />
}<br />
} else {<br />
final ToolItem toolItem = (ToolItem) e.widget;<br />
System.out.println(<br />
"Werkzeugtaste gedrückt: " + toolItem.getText());<br />
}<br />
}<br />
private void processMenuEvent(<br />
final SelectionEvent e,<br />
final MenuItem item) {<br />
// Text des Menüeintrags holen<br />
final String s = item.getText();<br />
// ToolItem holen<br />
final ToolItem toolItem = (ToolItem) e.widget;<br />
// Text des ToolItem durch Text des Menüeintrags ersetzen<br />
toolItem.setText(s);<br />
// Menü wieder verstecken<br />
menu.setVisible(false);<br />
}<br />
8.5.13 Spezielle Widgets<br />
<strong>Das</strong> Package org.eclipse.swt.custom enthält einige weitere Widgets,<br />
die auf verschiedenen Plattformen nicht als native GUI-Elemente zur<br />
Verfügung stehen. Deshalb sind alle Widgets in diesem Package reine<br />
Java-Implementierungen.<br />
Wir hatten bereits die Widgets CCombo und CTabFolder kennen<br />
gelernt. In der folgenden Tabelle listen wir einige weitere dieser Widget-Klassen<br />
auf:
BusyIndicator<br />
ControlEditor<br />
PopupList<br />
SashForm<br />
StyledText<br />
TableTree<br />
TableEditor<br />
TreeEditor<br />
TableTreeEditor<br />
8.5 Widgets<br />
Hier ist ein Beispiel für die Anwendung der Klasse SashForm. Zwei SashForm-Instanzen<br />
werden erzeugt, zunächst eine horizontale und dann<br />
innerhalb dieser eine vertikale. Beide SashForm-Komponenten haben<br />
List-Widgets als Kindelemente.<br />
187<br />
Diese Klasse dient dazu, den Mauszeiger als Tätigkeitssymbol (Sanduhr etc.) darzustellen.<br />
Dazu wird die Methode showWhile(display, runnable)verwendet, wobei der zweite Parameter<br />
vom Typ java.lang.Runnable sein muss. In dessen run()-Methode muss die komplette<br />
Verarbeitung ausgeführt werden; währenddessen wird das Tätigkeitssymbol angezeigt.<br />
Mit dieser Klasse kann einem Composite ein anderes GUI-Element zugeordnet werden.<br />
Wird das Composite verschoben oder in der Größe verändert, so verändert sich ggf. auch<br />
die Position des zugeordneten Elements. Üblicherweise verwendet man ControlEditor, um<br />
einem nicht editierfähigen Composite GUI-Elemente für die Eingabe von Werten zuzuordnen.<br />
Die Eclipse-API-Referenzdokumentation enthält ein Beispiel, in dem einer Canvas-<br />
Instanz (siehe Abschnitt 8.7) ein Button zugeordnet wird, bei dessen Betätigung die Hintergrundfarbe<br />
der Zeichenfläche geändert werden kann.<br />
Diese Klasse funktioniert ähnlich wie die List-Klasse (siehe Abschnitt 8.5.8), jedoch<br />
erscheint die Liste in einer eigenen Shell über der im Konstruktor spezifizierten Shell.<br />
Üblicherweise wird diese Klasse verwendet, um innerhalb einer Tabellenzelle Werte aus<br />
einer Liste auszuwählen.<br />
Diese Klasse ist als eine Unterklasse von Composite implementiert und organisiert seine<br />
Kindelemente horizontal oder vertikal (wie angegeben) und durch verschiebbare Rahmen<br />
(Sash) getrennt (siehe Abschnitt 8.5.10). Jedem Kindelement kann ein Gewicht zugeordnet<br />
werden, um Breite bzw. Höhe des Kindelements vorzugeben. Mit der Methode setMaximizedControl()<br />
kann ein einzelnes Kindelement temporär maximiert und die anderen<br />
minimiert werden.<br />
Diese Klasse implementiert ein ein- oder mehrzeiliges Texteingabefeld ähnlich der Klasse<br />
Text. Zusätzlich werden jedoch bestimmte Textattribute unterstützt (Vorder- und Hintergrundfarbe,<br />
Textfont, fetter, kursiver und normaler Textstil). Diese Funktionalität reicht für<br />
Programmeditoren aus, für die Textverarbeitung jedoch nicht.<br />
Die Textformatierung erfolgt mit Hilfe der Methoden getStyleRangeAtOffset(), get-<br />
StyleRanges(), setStyleRange(), setStyleRanges(), mit denen StyleRange-Instanzen abgefragt<br />
und gesetzt werden können. Außerdem gibt es die Methoden getLineBackground()<br />
und setLineBackground() zum Abfragen und Setzen der Hintergrundfarbe einer Zeile.<br />
Alternativ zu diesen Methoden kann eine eigene Stilverarbeitung mit Hilfe von Line-<br />
StyleListener- bzw. LineBackgroundListener-Instanzen implementiert werden.<br />
Der Textinhalt eines StyledText-Widgets muss das Interface StyledTextContent implementieren.<br />
Insofern können Sie auch eigene Inhaltsmodelle definieren. Mit der Method set-<br />
Content() kann ein StyledText-Widget initialisiert werden.<br />
Diese Klasse entspricht in ihrer Funktion der Klasse Tree (siehe Abschnitt 8.5.9). Allerdings<br />
weicht die grafische Darstellung ab: Die Baumstruktur erscheint als eine Serie von hierarchisch<br />
eingerückten Tabellen, die Linien, welche die Zweige des Baums andeuten sollen,<br />
entfallen. Die einzelnen Baumknoten werden durch TableTreeItem-Instanzen gebildet.<br />
Diese Klassen gleichen dem oben erwähnten ControlEditor, jedoch spezialisiert auf die<br />
Klassen Table, Tree und TableTree. Die Eclipse-API-Referenzdokumentation enthält<br />
Beispiele, in denen einzelnen TableItem-, TreeItem- und TableTreeItem-Instanzen ein<br />
Textfeld zugeordnet wird, das die Modifikation der jeweiligen Elemente erlaubt.
188<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
// Äußere SashForm erzeugen<br />
SashForm sf1 = new SashForm(toplevelShell, <strong>SWT</strong>.HORIZONTAL);<br />
// Innere SashForm erzeugen<br />
SashForm sf2 = new SashForm(sf1, <strong>SWT</strong>.VERTICAL);<br />
// Inhalt für vertikale SashForm erzeugen<br />
List list1 = new List(sf2, <strong>SWT</strong>.NONE);<br />
list1.setItems(new String[]{"red", "green", "blue"});<br />
List list2 = new List(sf2, <strong>SWT</strong>.NONE);<br />
list2.setItems(new String[]{"A", "B", "C"});<br />
// Gleichmäßig gewichten<br />
sf2.setWeights(new int[] {100,100});<br />
// Inhalt für horizontale SashForm erzeugen<br />
List list3 = new List(sf1, <strong>SWT</strong>.NONE);<br />
list3.setItems(<br />
new String[]{"one", "two", "three", "four", "five", "six"});<br />
// Unterschiedlich gewichten<br />
sf1.setWeights(new int[] {100,200});<br />
Abb. 8–9 <strong>Das</strong> Ergebnis: eine horizontale und eine vertikale Trennleiste, welche drei<br />
Listenfelder voneinander trennen. Beide Trennleisten können mit der Maus verschoben<br />
werden. Wird die Größe des Fensters geändert, verschieben sich die Trennleisten entsprechend.<br />
8.5.14 <strong>Das</strong> Browser-Widget<br />
Seit Eclipse V3 ist auch ein Webbrowser als Widget verfügbar und<br />
zwar als Klasse Browser in Package org.eclipse.swt.browser. Damit<br />
wird es möglich, auf einfache Art und Weise HTML-Inhalte in <strong>SWT</strong>-<br />
Applikationen anzuzeigen. <strong>Das</strong> Eclipse-Team hat freilich hier keinen<br />
eigenen Browser implementiert, sondern benutzt die nativen Browser<br />
der Ablaufplattform. Unter Windows implementiert die Klasse Browser<br />
eine OLE-Einbettung des Internet Explorers. Unter Linux wird Mozilla<br />
benutzt, unter Mac OS X der Safari-Browser. Angenehm an dieser<br />
Strategie ist, dass das Browser-Widget genauso mächtig ist wie die verwendeten<br />
Webbrowser. Sicherheits- und andere Einstellungen im<br />
Internet Explorer oder in Mozilla beeinflussen auch das Browser-Widget.<br />
Nachteilig an dieser Strategie ist, dass sich das Widget in vielerlei<br />
Hinsicht nicht wie ein Standard-Widget verhält. So kann man z.B. die-
8.6 Layouts<br />
sem Widget kein eigenes Kontextmenü zuordnen (der Browser hat ja<br />
schon eines), MouseListener und KeyListener empfangen keine Ereignisse,<br />
und auf der Oberfläche des Widgets kann man weder herummalen<br />
noch andere Widgets darauf anordnen.<br />
Dafür verfügt das Browser-Widget aber über spezielle Methoden,<br />
um z.B. eine Webseite an einer spezifizierten Adresse anzuzeigen<br />
(setURL()) oder die URL der aktuellen Seite abzufragen (getURL()).<br />
Mit der Methode setText() kann dem Browser direkt ein HTML-Text<br />
zur Anzeige übergeben werden. Daneben gibt es Methoden für die<br />
Navigation wie back(), isBackEnabled(), forward(), isForwardEnabled(),<br />
refresh() und stop().<br />
Außerdem kann das Browser-Widget mit verschiedenen Listenern<br />
(CloseWindowListener, LocationListener, OpenWindowListener, ProgressListener,<br />
StatusTextListener, TitleListener, Visibility-<br />
WindowListener) instrumentiert werden, die auf Status- oder Inhaltsänderungen<br />
des eingebetteten Browsers reagieren können.<br />
In Abschnitt 10.5 zeigen wir das Browser-Widget in einer praktischen<br />
Anwendung. Weitere Beispiele finden Sie in [Daum2004].<br />
8.6 Layouts<br />
Layouts werden benutzt, um GUI-Elemente auf einem Composite automatisch<br />
anzuordnen. <strong>Das</strong> Layout errechnet die Position und Größe<br />
jedes GUI-Elements, das auf dem Composite angebracht ist. Sollte das<br />
Composite in der Größe verändert werden – durch Programmaktion<br />
oder durch Benutzereinwirkung –, so wird die Anordnung der GUI-<br />
Elemente automatisch neu berechnet.<br />
Normalerweise werden dabei alle GUI-Elemente über einen Kamm<br />
geschoren. Allerdings ist es auch möglich, das Layout individueller<br />
GUI-Elemente durch die Zuordnung spezifischer Layoutdaten zu<br />
beeinflussen. <strong>Das</strong> erfolgt mit der Control-Methode setLayoutData().<br />
Eclipse stellt fünf vordefinierte Layout-Klassen zur Verfügung.<br />
Außerdem besteht die Möglichkeit, selbst Layout-Klassen zu erstellen.<br />
Die Namen der vordefinierten Layout-Klassen folgen alle dem Muster<br />
»*Layout«. Die Namen der zugehörigen Klassen für die individuellen<br />
Layoutdaten folgen dem Muster »*Data«. Bis auf die Klasse StackLayout,<br />
die im Package org.eclipse.swt.custom (siehe Abschnitt 8.5.13)<br />
enthalten ist, sind alle anderen vordefinierten Layouts im Package<br />
org.eclipse.swt.layout enthalten.<br />
An dieser Stelle sei auch auf den ausgezeichneten Artikel Understanding<br />
Layouts in <strong>SWT</strong> von Carolyn MacLeod und Shantha Ramachandran<br />
[MacLeod2002] hingewiesen.<br />
189
190<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Hinweis: In manchen Fällen ist es einfacher und benutzerfreundlicher, an<br />
Stelle eines Composites mit einem Layout eine SashForm-Komponente (siehe<br />
Abschnitt 8.5.13) zu verwenden. Diese erlaubt dem Endbenutzer eine eigenständige<br />
Aufteilung des vorhandenen Raumes.<br />
8.6.1 Visuelle Übersicht<br />
Die beste Übersicht über die verschiedenen Layouts ist in den Beispielapplikationen<br />
zu Eclipse enthalten. Dazu rufen wir die Funktion Window>Show<br />
View>Other ... auf. Im folgenden Dialog wählen wir die<br />
Applikation <strong>SWT</strong> Examples><strong>SWT</strong> Layouts aus. Diese Applikation<br />
erscheint dann auch prompt im Fenster rechts unten (Abb. 8–10).<br />
Damit wir etwas mehr Platz haben, maximieren wir diese Applikation<br />
mit einem Doppelklick auf den Reiter.<br />
Abb. 8–10 Verschiedene FillLayout-, RowLayout-, GridLayout- und FormLayout-Konfigurationen<br />
können mit der <strong>SWT</strong> Layouts-Beispielapplikation durchprobiert werden. Ein<br />
Druck auf den Code-Button zeigt den erzeugten Source-Code.<br />
So kann man diese Beispielapplikation zur Not als GUI-Designer verwenden.<br />
Da diese Applikation die verschiedenen Layouts am besten visualisieren<br />
kann, werden wir im Folgenden auf eine Abbildung der verschiedenen<br />
Layouts verzichten.<br />
8.6.2 Die Klasse FillLayout<br />
FillLayout ist das einfachste der vordefinierten Layouts. Es besagt<br />
schlicht, dass die GUI-Elemente das Composite komplett ausfüllen. Es<br />
gibt weder Zwischenräume zwischen den einzelnen GUI-Elementen<br />
noch Randbereiche, und auch ein automatischer Umbruch bei ungenügendem<br />
Platz ist nicht möglich. Alle GUI-Elemente haben die gleiche<br />
Größe, wobei die Höhe vom höchsten GUI-Element und die Breite
8.6 Layouts<br />
vom breitesten GUI-Element bestimmt wird. Typische Einsatzfälle für<br />
diese Layout-Klasse sind z.B. Werkzeugleisten, bei denen die einzelnen<br />
Tasten unmittelbar aneinander liegen, oder auch der Fall, wenn ein<br />
einzelnes GUI-Element ein Composite komplett ausfüllt.<br />
Normalerweise werden die GUI-Elemente in horizontaler Richtung<br />
aneinander positioniert, allerdings lässt sich durch die Zuweisung<br />
der Stilkonstante <strong>SWT</strong>.VERTICAL an das type-Feld des Layouts auch eine<br />
vertikale Ausrichtung erzwingen:<br />
FillLayout fillLayout = new FillLayout();<br />
fillLayout.type = <strong>SWT</strong>.VERTICAL;<br />
composite.setLayout(fillLayout);<br />
new Button(composite, <strong>SWT</strong>.RADIO).setText("One");<br />
new Button(composite, <strong>SWT</strong>.RADIO).setText("Two");<br />
new Button(composite, <strong>SWT</strong>.RADIO).setText("Three");<br />
Bei FillLayout besteht keine Möglichkeit, die Größe der einzelnen<br />
GUI-Elemente individuell zu setzen.<br />
8.6.3 Die Klasse RowLayout<br />
Ähnlich wie beim FillLayout ordnet das RowLayout die GUI-Elemente<br />
in einer Reihe an. Dabei gibt es jedoch zusätzliche Optionen:<br />
type wie bei FillLayout<br />
Ist dieses Feld auf den Wert true gesetzt (Standardeinstellung), so werden<br />
wrap<br />
überzählige GUI-Elemente in eine neue Reihe umgebrochen, wenn der Platz nicht<br />
ausreicht.<br />
pack<br />
justify<br />
marginLeft<br />
marginTop<br />
marginRight<br />
marginBottom<br />
spacing<br />
<strong>Das</strong> folgende Beispiel zeigt, wie die Felder einer RowLayout-Instanz<br />
gesetzt werden können:<br />
191<br />
Ist dieses Feld auf den Wert true gesetzt (Standardeinstellung), so werden alle<br />
GUI-Elemente in ihrer natürlichen Größe und so weit links wie möglich dargestellt.<br />
Andernfalls füllen die GUI-Elemente den kompletten verfügbaren Platz aus, ähnlich<br />
wie bei FillLayout.<br />
Ist dieses Feld auf den Wert true gesetzt, so werden alle GUI-Elemente gleichmäßig<br />
über den verfügbaren Raum verteilt. Die Standardeinstellung ist »false«.<br />
Diese Felder kontrollieren die Größe des Randbereichs in Pixeln.<br />
Dieses Feld kontrolliert den Minimalabstand zwischen<br />
GUI-Elementen in Pixeln.
192<br />
numColumns<br />
makeColumnsEqualWidth<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
RowLayout rowLayout = new RowLayout();<br />
rowLayout.wrap = false;<br />
rowLayout.pack = false;<br />
rowLayout.justify = true;<br />
rowLayout.type = <strong>SWT</strong>.VERTICAL;<br />
rowLayout.marginLeft = 10;<br />
rowLayout.marginTop = 5;<br />
rowLayout.marginRight = 10;<br />
rowLayout.marginBottom = 8;<br />
rowLayout.spacing = 5;<br />
composite.setLayout(rowLayout);<br />
RowData Höhe und Breite jedes einzelnen GUI-Elements innerhalb einer RowLayout-Instanz<br />
können individuell mittels RowData-Instanzen gesetzt werden.<br />
Im folgenden Beispiel erzeugen wir zwei Tasten und setzen deren<br />
Höhe und Breite.<br />
Button button1 = new Button(composite, <strong>SWT</strong>.PUSH);<br />
button1.setText("70x20");<br />
button1.setLayoutData(new RowData(70, 20));<br />
Button button2 = new Button(composite, <strong>SWT</strong>.PUSH);<br />
button2.setText("50x35");<br />
button2.setLayoutData(new RowData(50, 35));<br />
8.6.4 Die Klasse GridLayout<br />
Die Klasse GridLayout ist die wohl nützlichste und mächtigste Klasse<br />
der vordefinierten Layout-Klassen. Allerdings ist sie auf Grund der vielen<br />
verschiedenen Einstellungsmöglichkeiten auch recht kompliziert zu<br />
handhaben. Wer etwas Erfahrung mit der Gestaltung von HTML-Seiten<br />
mit Hilfe von Tabellen hat, weiß, wovon ich rede.<br />
<strong>Das</strong> GridLayout hat in der Tat einige Ähnlichkeiten mit HTML-<br />
Tabellen. Auch hier gibt es Reihen und Spalten, und es ist möglich,<br />
horizontal oder vertikal angrenzende Tabellenelemente miteinander zu<br />
verschmelzen.<br />
Die folgenden Optionen stehen für das GridLayout zur Verfügung:<br />
Anzahl der Spalten. Die Anzahl der Reihen ergibt sich aus der Anzahl der GUI-<br />
Elemente und der Anzahl der Spalten.<br />
Ist dieses Feld auf den Wert true gesetzt, so wird allen Spalten die gleiche Breite<br />
zugeordnet. Die Standardeinstellung ist »false«.<br />
marginHeight Dieses Feld kontrolliert die Größe des Randbereichs oben und unten in Pixeln.<br />
marginWidth Dieses Feld kontrolliert die Größe des Randbereichs links und rechts in Pixeln.<br />
horizontalSpacing Dieses Feld kontrolliert den Minimalabstand zwischen den Spalten in Pixeln.<br />
verticalSpacing Dieses Feld kontrolliert den Minimalabstand zwischen den Reihen in Pixeln.
8.6 Layouts<br />
<strong>Das</strong> folgende Beispiel zeigt, wie die einzelnen Optionen für eine Grid-<br />
Layout-Instanz gesetzt werden:<br />
GridLayout gridLayout = new GridLayout();<br />
gridLayout.numColumns = 3;<br />
gridLayout.marginWidth = 10;<br />
gridLayout.makeColumnsEqualWidth = true;<br />
gridLayout.marginHeight = 5;<br />
gridLayout.horizontalSpacing = 6;<br />
gridLayout.verticalSpacing = 4;<br />
gridLayout.makeColumnsEqualWidth = true;<br />
composite.setLayout(gridLayout);<br />
Die Layout-Optionen, die mit Hilfe von GridData-Instanzen für individuelle<br />
GUI-Elemente innerhalb einer GridLayout-Instanz gesetzt werden<br />
können, sind recht üppig. So verfügen GridData-Instanzen über die<br />
folgenden Felder:<br />
grabExcessHorizontalSpace<br />
grabExcessVerticalSpace<br />
heightHint<br />
Bestimmte Attribute können bereits im GridData()-Konstruktor gesetzt<br />
werden. Dazu stehen folgende Stilkonstanten zur Verfügung:<br />
GridData<br />
Ist dieses Feld auf den Wert true gesetzt, so füllt das GUI-Element den eventuell<br />
verbleibenden horizontalen Raum aus. Die Standardeinstellung ist »false«.<br />
Ist dieses Feld auf den Wert true gesetzt, so füllt das GUI-Element den eventuell<br />
verbleibenden vertikalen Raum aus. Die Standardeinstellung ist »false«.<br />
193<br />
Spezifiziert eine minimale Höhe in Pixeln. Ist hier ein Wert angegeben, so ist bei dem<br />
jeweiligen GUI-Element eine etwaige vertikale Scroll-Funktion außer Kraft gesetzt!<br />
Spezifiziert, wie das GUI-Element horizontal in seiner Zelle ausgerichtet wird. Die<br />
folgenden Konstanten können angegeben werden:<br />
GridData.BEGINNING (Standard)<br />
horizontalAlignment<br />
GridData.CENTER<br />
GridData.END<br />
GridData.FILL<br />
horizontalIndent Spezifiziert in Pixeln, wie weit ein GUI-Element von links eingerückt wird.<br />
horizontalSpan<br />
verticalAlignment<br />
verticalSpan<br />
widthHint<br />
Spezifiziert, wie viele Zellen das GUI-Element in horizontaler Richtung verbraucht<br />
(die Zellen werden miteinander verschmolzen).<br />
Spezifiziert, wie das GUI-Element vertikal in seiner Zelle ausgerichtet wird. Die<br />
folgenden Konstanten können angegeben werden:<br />
GridData.BEGINNING<br />
GridData.CENTER (Standard)<br />
GridData.END<br />
GridData.FILL<br />
Spezifiziert, wie viele Zellen das GUI-Element in vertikaler Richtung verbraucht<br />
(die Zellen werden miteinander verschmolzen).<br />
Spezifiziert eine minimale Breite in Pixeln. Ist hier ein Wert angegeben, so ist die<br />
horizontale Scroll-Funktion außer Kraft gesetzt!
194<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Konstante Äquivalent<br />
GridData.GRAB_HORIZONTAL grabExcessHorizontalSpace = true<br />
GridData.GRAB_VERTICAL grabExcessVerticalSpace = true<br />
GridData.HORIZONTAL_ALIGN_BEGINNING<br />
horizontalAlignment =<br />
GridData.BEGINNING<br />
GridData.HORIZONTAL_ALIGN_CENTER horizontalAlignment = GridData.CENTER<br />
GridData.HORIZONTAL_ALIGN_END horizontalAlignment = GridData.END<br />
GridData.HORIZONTAL_ALIGN_FILL horizontalAlignment = GridData.FILL<br />
GridData.VERTICAL_ALIGN_BEGINNING verticalAlignment = GridData.BEGINNING<br />
GridData.VERTICAL_ALIGN_CENTER verticalAlignment = GridData.CENTER<br />
GridData.VERTICAL_ALIGN_END verticalAlignment = GridData.END<br />
GridData.VERTICAL_ALIGN_FILL verticalAlignment = GridData.FILL<br />
GridData.FILL_HORIZONTAL<br />
HORIZONTAL_ALIGN_FILL |<br />
GRAB_HORIZONTAL<br />
GridData.FILL_VERTICAL VERTICAL_ALIGN_FILL | GRAB_VERTICAL<br />
GridData.FILL_BOTH FILL_VERTICAL | FILL_HORIZONTAL<br />
In Abschnitt 10.3 finden Sie ein Beispiel für die Anwendung der Grid-<br />
Layout-Klasse.<br />
Sollten alle diese Layout-Möglichkeiten nicht ausreichen, kann<br />
man immer noch Composite-Instanzen mit Hilfe von GridLayouts<br />
schachteln, eine Technik, die von HTML-Seite her wohl bekannt ist.<br />
8.6.5 Die Klasse FormLayout<br />
Die Klasse FormLayout wurde mit Eclipse 2.0 eingeführt. Sie erlaubt es,<br />
die einzelnen GUI-Elemente auf einer zweidimensionalen Fläche<br />
jeweils aneinander bzw. an die Ränder des übergeordneten Composite<br />
zu docken. <strong>Das</strong> geschieht mit Hilfe von FormAttachment-Instanzen.<br />
Für FormLayout stehen nur die folgenden Optionen zur Verfügung:<br />
marginHeight Dieses Feld kontrolliert die Größe des Randbereichs oben und unten in Pixeln.<br />
marginWidth Dieses Feld kontrolliert die Größe des Randbereichs links und rechts in Pixeln.<br />
FormData Die eigentlichen Layout-Möglichkeiten stecken in den Klassen Form-<br />
Data und FormAttachment. FormData erlaubt folgende Optionen für<br />
jedes einzelne GUI-Element:
8.6 Layouts<br />
height Die gewünschte Höhe des GUI-Elements in Pixeln.<br />
width Die gewünschte Breite des GUI-Elements in Pixeln.<br />
top<br />
bottom<br />
left<br />
right<br />
Dabei gibt es für FormAttachment-Instanzen zwei Spielarten:<br />
! Angabe einer relativen Position im Composite<br />
! Angabe relativ zu einem anderen GUI-Element<br />
Für die erste Methode der Positionierung stehen zwei Konstruktor-<br />
Varianten zur Verfügung:<br />
und<br />
FormAttachment fa = new FormAttachment(prozent, offset);<br />
FormAttachment fa = new FormAttachment(zaehler, nenner, offset);<br />
Die Position p ermittelt sich dann aus der Höhe oder Breite d des Composite<br />
wie folgt:<br />
p = d*zaehler/nenner+offset<br />
Wurde nur ein Prozentwert angegeben, so ist die Formel natürlich:<br />
p = d*prozent/100+offset<br />
Nehmen wir z.B. an, dass unser Composite 400 Pixel breit ist und 300<br />
Pixel hoch ist. Erzeugen wir nun eine FormAttachment-Instanz mit<br />
FormAttachment(30,10) und weisen sie dem top-Feld einer FormData-<br />
Instanz zu, so ergibt sich:<br />
p = 30/100*300+5 = 95<br />
Der obere Rand unseres GUI-Elements ist also 95 Pixel vom oberen<br />
Rand der Client-Area des Composite entfernt. Würden wir dieselbe<br />
Instanz stattdessen dem bottom-Feld zuweisen, so wäre der untere<br />
Rand unseres GUI-Elements 95 Pixel vom unteren der Client-Area des<br />
Composite entfernt.<br />
Würden wir dagegen dieselbe Instanz dem left-Feld zuweisen, so<br />
ergäbe sich<br />
p = 30/100*400+5 = 125<br />
Eine FormAttachment-Instanz, die angibt, auf was sich die obere/<br />
untere/linke/rechte Kante des GUI-Elements beziehen soll.<br />
Der linke Rand unseres GUI-Elements ist also 125 Pixel vom linken<br />
Rand der Client-Area des Composite entfernt. Entsprechendes gilt für<br />
die Zuweisung an das right-Feld.<br />
FormAttachment<br />
Composite-Position<br />
195
196<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Referenz-GUI-Element Für die zweite Methode der Positionierung gibt es drei Konstruktor-Varianten:<br />
Zwischen GUI-Elementen<br />
hin- und herschalten<br />
FormAttachment (control, offset, alignment)<br />
FormAttachment (control, offset)<br />
FormAttachment (control)<br />
Der Parameter control gibt dabei die Control-Instanz (also das GUI-<br />
Element) an, auf die wir uns beziehen.<br />
Der Parameter offset gibt den Abstand zwischen den GUI-Elementen<br />
an. Wird der Parameter weggelassen, ist der Abstand Null.<br />
Der Parameter alignment gibt an, auf welche Kante des Referenzelements<br />
wir uns beziehen. Für Zuweisungen an top- und bottom-Felder<br />
können hier die Stilkonstanten <strong>SWT</strong>.TOP, <strong>SWT</strong>.BOTTOM und <strong>SWT</strong>.CENTER<br />
verwendet werden. Für Zuweisungen an left- und right-Felder sind<br />
es die Konstanten <strong>SWT</strong>.LEFT, <strong>SWT</strong>.RIGHT und <strong>SWT</strong>.CENTER. Wird der Parameter<br />
alignment weggelassen, so wird die nächstliegende Kante<br />
benutzt.<br />
8.6.6 Die Klasse StackLayout<br />
Diese Klasse ist im Unterschied zu den vorigen vier vordefinierten Layouts<br />
nicht im Package org.eclipse.swt.layout enthalten, sondern im<br />
Package org.eclipse.swt.custom. Sie unterscheidet sich von anderen<br />
Layout-Klassen auch darin, dass innerhalb eines Composite immer nur<br />
ein GUI-Element sichtbar ist. Der Grund dafür ist, dass alle GUI-Elemente<br />
gleich groß gemacht werden und an der gleichen Stelle übereinander<br />
positioniert sind. Damit ist immer nur das oberste Element sichtbar.<br />
Die Klasse StackLayout ist dann sinnvoll, wenn man zwischen<br />
GUI-Elementen hin- und herschalten will. Man braucht lediglich die<br />
gewünschte Control-Instanz an die höchste Position zu bringen.<br />
Für StackLayout stehen die folgenden Optionen zur Verfügung:<br />
marginHeight Dieses Feld kontrolliert die Größe des Randbereichs oben und unten in Pixeln.<br />
marginWidth Dieses Feld kontrolliert die Größe des Randbereichs links und rechts in Pixeln.<br />
topControl Die sichtbare Control-Instanz.<br />
Im folgenden Beispiel legen wir zwei Button-Instanzen übereinander.<br />
Wird eine Taste gedrückt, wird jeweils die andere Taste sichtbar:<br />
// Neues Composite erzeugen<br />
final Composite stackComposite = new Composite(composite,<strong>SWT</strong>.NULL);<br />
final StackLayout stackLayout = new StackLayout();<br />
// Text-Buttons erzeugen<br />
final Button buttonA = new Button(stackComposite, <strong>SWT</strong>.PUSH);
uttonA.setText("Taste A");<br />
final Button buttonB = new Button(stackComposite, <strong>SWT</strong>.PUSH);<br />
buttonB.setText("Taste B");<br />
// Auf Klickereignisse reagieren<br />
buttonA.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
stackLayout.topControl = buttonB;<br />
// Neues Layout erzwingen<br />
stackComposite.layout();<br />
// Fokus auf sichtbare Taste setzen<br />
buttonB.setFocus();<br />
}<br />
});<br />
buttonB.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
stackLayout.topControl = buttonA;<br />
// Neues Layout erzwingen<br />
stackComposite.layout();<br />
// Fokus auf sichtbare Taste setzen<br />
buttonA.setFocus();<br />
}<br />
});<br />
// Layout initialisieren<br />
stackLayout.topControl = buttonA;<br />
stackLayout.marginWidth = 10;<br />
stackLayout.marginHeight = 5;<br />
// Layout setzen<br />
stackComposite.setLayout(stackLayout);<br />
8.7 Grafik<br />
8.7 Grafik<br />
Die Interfaces und Klassen für grafische Operationen sind im Package<br />
org.eclipse.swt.graphics enthalten. Im Wesentlichen orientiert sich<br />
der Funktionsumfang dieser Bibliothek an den grafischen Fähigkeiten<br />
der unterstützten Plattformen. Obwohl sie etwas mehr bietet als die<br />
grafischen AWT-Grundfunktionen, reicht der Funktionsumfang nicht<br />
an den des Java2D-API heran. In Abschnitt 8.8 werden wir diskutieren,<br />
wie man den Funktionsumfang erweitern kann.<br />
8.7.1 Der Grafikkontext<br />
Die Klasse GC enthält alle für Zeichenoperationen benötigten Methoden<br />
– Methoden wie drawLine(), drawOval(), drawPolygon(), setFont(),<br />
getFontMetrics(), u.v.a.m.<br />
197
198<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Gezeichnet werden kann auf Instanzen aller Klassen, die das Drawable-Interface<br />
implementieren. Insbesondere sind das die Klassen<br />
Image, Control und ihre Unterklassen wie Canvas und Display. Dabei<br />
wird Image besonders für Double-Buffering-Zwecke eingesetzt, Canvas<br />
dient normalerweise als Zeichenfläche für Grafikausgaben (z.B. Diagramme)<br />
oder auch als Zeichenfläche für den Endbenutzer, Display<br />
dient der bildschirmfüllenden Ausgabe von Grafiken.<br />
Die Auswahl des Zeichenmediums erfolgt im Konstruktor GC().<br />
Erzeugt man einen Grafikkontext mit Hilfe des GC()-Konstruktors,<br />
muss man die GC-Instanz mit dispose() wieder entsorgen, wenn<br />
sie nicht mehr benötigt wird, denn eine GC-Instanz belegt Betriebssystemressourcen.<br />
Im Regelfall wird man jedoch nicht selbst einen Grafikkontext<br />
erzeugen, sondern einen Grafikkontext benutzen, der von<br />
einem PaintEvent übergeben wird. Denn die goldene Regel bei der<br />
Grafikverarbeitung lautet:<br />
Alle grafischen Operationen werden innerhalb eines PaintListener-Objekts<br />
ausgeführt, also innerhalb der paintControl()-Ereignisverarbeitung einer<br />
Control-Instanz.<br />
Wir zeigen das an einem Beispiel, in dem wir eine grüne Zierlinie um<br />
eine Composite-Instanz zeichnen:<br />
composite.addPaintListener(new PaintListener () {<br />
public void paintControl(PaintEvent event){<br />
// Display aus Ereignis holen<br />
Display display = event.display;<br />
// Grüne Systemfarbe holen - braucht nicht entsorgt zu werden<br />
Color green = display.getSystemColor(<strong>SWT</strong>.COLOR_DARK_GREEN);<br />
// <strong>Das</strong> Ereignis liefert auch den Grafikkontext<br />
GC gc = event.gc;<br />
// Linienfarbe setzen<br />
gc.setForeground(green);<br />
// Größe des nutzbaren Bereichs im Composite<br />
Rectangle rect = ((Composite) event.widget).getClientArea();<br />
// Nun ein Rechteck zeichnen<br />
gc.drawRectangle(rect.x + 2, rect.y + 2,<br />
rect.width - 4, rect.height - 4);<br />
}<br />
});
8.7.2 Farben<br />
8.7 Grafik<br />
In einem Grafikkontext setzt man Linien- und Textfarben – wie oben<br />
gezeigt – mit Hilfe der Methode setForeground(). Füllfarben werden<br />
mit setBackground() gesetzt.<br />
Dazu muss man sich allerdings erst einmal Farbobjekte besorgen.<br />
<strong>Das</strong> kann auf zwei verschiedene Arten erfolgen:<br />
! Man holt sich eine Systemfarbe von einer Device-Instanz. Da Display<br />
eine Unterklasse von Device ist, können wir eine Systemfarbe<br />
mit Hilfe der Methode getSystemColor() von der Display-Instanz<br />
eines Widgets holen. Die dazu notwendigen COLOR_...-Konstanten<br />
sind in der Klasse <strong>SWT</strong> definiert.<br />
Farbobjekte, die man sich auf diese oder andere Weise von anderen<br />
Instanzen holt, dürfen nicht mit dispose() freigegeben werden,<br />
denn sie werden eventuell noch anderenorts benötigt!<br />
! Man fertigt Farbobjekte selbst an, z.B.<br />
Color red = new Color(device, 255,0,0)<br />
oder<br />
Color blue = new Color(device, new RGB(0,255,0));<br />
Der Parameter device muss dabei vom Typ Device sein. RGB ist eine<br />
geräteunabhängige Klasse zur Darstellung von RGB-Farbtupeln.<br />
Auf Ausgabegeräten mit 24 Bit Farbtiefe ist die Farbdarstellung<br />
exakt, bei Ausgabegeräten mit geringerer Farbtiefe wird die Farbe<br />
von Eclipse so gut wie möglich angenähert [Moody2001].<br />
Solch selbst angefertigte Farbobjekte müssen mit dispose() wieder<br />
entsorgt werden, wenn sie nicht mehr gebraucht werden.<br />
8.7.3 Schriftarten<br />
Ähnlich wie mit Farben verhält es sich auch mit Fonts. Der aktuelle<br />
Font wird im Grafikkontext mit setFont() gesetzt.<br />
! Den aktuellen Systemfont kann man sich von einer Device-Instanz<br />
mit Hilfe der Methode getSystemFont() holen. Einen solchen Font<br />
darf man nicht mit dispose() entsorgen.<br />
! Neue Font-Instanzen können mit Hilfe eines Konstruktors erzeugt<br />
werden, z.B.<br />
Font font = new Font(device,"Arial",12,<strong>SWT</strong>.ITALIC)<br />
oder<br />
Font font = new Font(device,new FontData("Arial",12,<strong>SWT</strong>.ITALIC))<br />
FontData ist eine geräteunabhängige Repräsentation einer Schriftart.<br />
Font-Instanzen müssen mit dispose() wieder entsorgt werden,<br />
wenn sie nicht mehr gebraucht werden.<br />
199
200<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Im folgenden Beispiel holen wir uns den aktuellen Systemfont, produzieren<br />
eine kursive Variante, konfigurieren den Grafikkontext mit diesem<br />
Font und zeichnen das Wort »Hello«:<br />
// Display-Instanz holen<br />
Display display = composite.getDisplay();<br />
// Systemfont holen<br />
Font systemFont = display.getSystemFont();<br />
// FontData-Objekte enthalten die Fonteigenschaften.<br />
// Bei manchen Betriebssystemen kann ein Font mehrere<br />
// FontData-Instanzen besitzen. Wir verwenden nur die Erste.<br />
FontData[] data = systemFont.getFontData();<br />
FontData data0 = data[0];<br />
// Den Fontstil auf kursiv setzen<br />
data0.setStyle(<strong>SWT</strong>.ITALIC);<br />
// Neuen Font erzeugen<br />
Font italicFont = new Font(display, data0);<br />
// Font im Grafikkontext setzen<br />
gc.setFont(italicFont);<br />
// Nicht vergessen: italicFont.dispose() im<br />
// DisposeListener von composite aufrufen.<br />
// Text an Position (4,4) mit transparentem Hintergrund zeichnen.<br />
gc.drawText("Hello",4,4,true);<br />
Die Klasse GC stellt noch einige weitere Methoden für die Textverarbeitung<br />
zur Verfügung. So liefert z.B. die Methode getFontMetrics() ein<br />
FontMetrics-Objekt, das die charakteristischen Maße des aktuellen<br />
Fonts enthält. Mit den Methoden stringExtent() und textExtent()<br />
kann man ermitteln, welche Abmessungen eine Zeichenkette hätte,<br />
wenn sie unter dem aktuellen Font gezeichnet würde. stringExtent()<br />
ignoriert dabei TAB- und CR-Zeichen.<br />
8.7.4 Bilder<br />
Die Klasse Image ist für die geräteabhängige Darstellung von Bildern<br />
verantwortlich. Image-Instanzen können auf verschiedene Arten<br />
erzeugt werden: durch Angabe eines java.io.Stream-Objekts, durch<br />
Angabe eines Dateinamens (absolut oder relativ zum Projekt) oder<br />
durch Angabe eines ImageData-Objekts.<br />
Die Klasse ImageData ist für die geräteunabhängige Darstellung<br />
von Bildern verantwortlich. Instanzen dieser Klasse können ebenfalls<br />
durch Angabe eines java.io.Stream-Objekts oder durch Angabe eines<br />
Dateinamen erzeugt werden. Sie können auch mit Hilfe der Methode<br />
getImageData() aus einer Image-Instanz geholt werden. So kann man<br />
jederzeit zwischen geräteabhängiger und geräteunabhängiger Darstellung<br />
wechseln.
8.7 Grafik<br />
Image und ImageData unterstützen Bilder sowohl im direkten RGB-<br />
Format als auch im Indexformat. Auch Transparenz wird unterstützt<br />
(alpha-Kanal bei RGB, Transparentfarbe im indizierten Format). Mindestens<br />
die folgenden Dateiformate werden gelesen: BMP, GIF, JPG,<br />
PNG, TIFF und ICO. In Abschnitt 8.5.5 hatten wir bereits gezeigt, wie<br />
ein Bild von einer Datei gelesen werden kann.<br />
Im folgenden Beispiel benutzen wir eine Image-Instanz für den<br />
doppelt gepufferten Bildaufbau. Double Buffering wird oft verwendet,<br />
um ein Flackern des Bildschirms zu vermeiden. Man legt zunächst eine<br />
genügend große Image-Instanz an, erzeugt dann eine GC-Instanz auf<br />
diese Image-Instanz und führt alle Zeichenoperationen innerhalb dieses<br />
GC-Kontextes aus. Anschließend zeichnet man das gesamte Image<br />
komplett auf das Ziel-Drawable.<br />
// Canvas erzeugen<br />
final Canvas canvas = new Canvas(composite,<strong>SWT</strong>.BORDER);<br />
// Weisse Systemfarbe holden<br />
Color white = canvas.getDisplay().getSystemColor(<strong>SWT</strong>.COLOR_WHITE);<br />
// Canvashintergrund setzen<br />
canvas.setBackground(white);<br />
// PaintListener zufügen<br />
canvas.addPaintListener(new PaintListener() {<br />
public void paintControl(PaintEvent e) {<br />
// Display aus Ereignis holen<br />
Display display = e.display;<br />
// Rote und schwarze Systemfarben holen<br />
// - brauchen nicht entsorgt zu werden<br />
Color black = display.getSystemColor(<strong>SWT</strong>.COLOR_BLACK);<br />
Color red = display.getSystemColor(<strong>SWT</strong>.COLOR_RED);<br />
// <strong>Das</strong> Ereignis liefert auch den Grafikkontext<br />
GC gc = e.gc;<br />
// <strong>Das</strong> Widget, das das Ereignis verursacht hat<br />
Composite source = (Composite) e.widget;<br />
// Größe der nutzbaren Fläche<br />
Rectangle rect = source.getClientArea();<br />
// Puffer aufbauen<br />
Image buffer = new Image(display,rect.width,rect.height);<br />
// Neuer Grafikkontext für Puffer<br />
GC bufferGC = new GC(buffer);<br />
// verschiedene Zeichenoperationen<br />
bufferGC.setBackground(red);<br />
bufferGC.fillRectangle(5,5,rect.width-10,rect.height-10);<br />
bufferGC.setForeground(black);<br />
bufferGC.drawRectangle(5,5,rect.width-10,rect.height-10);<br />
bufferGC.setBackground(source.getBackground());<br />
Double Buffering<br />
201
202<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
}<br />
});<br />
bufferGC.fillRectangle(10,10,rect.width-20,rect.height-20);<br />
// Nun das gepufferte Bild auf Canvas zeichnen<br />
gc.drawImage(buffer,0,0);<br />
// Den Grafikkontext des Puffers entsorgen<br />
bufferGC.dispose();<br />
// Den Puffer entsorgen<br />
buffer.dispose();<br />
Im System verwendete Bilder lassen sich von der aktuellen Display-<br />
Instanz mit der Methode getSystemImage() holen. Dabei stehen die folgenden<br />
Konstanten für die Identifizierung des Bildes zur Verfügung:<br />
<strong>SWT</strong>.ICON_ERROR, <strong>SWT</strong>.ICON_INFORMATION, <strong>SWT</strong>.ICON_QUESTION, <strong>SWT</strong>.ICON_<br />
WARNING.<br />
8.7.5 Der Mauszeiger<br />
Ebenfalls im Package org.eclipse.swt.graphics befindet sich die<br />
Klasse Cursor, welche den Mauszeiger repräsentiert. Um der aktuellen<br />
Schreibmarke eine neue Gestalt zu geben, muss explizit eine neue<br />
Instanz dieser Klasse erzeugt werden. Dabei wird das aktuelle Display<br />
als Parameter übergeben, außerdem wird eine Stilkonstante angegeben,<br />
welche die Gestalt des Mauszeigers festlegt. Dabei hält natürlich<br />
die konkrete Ausprägung von der Ablaufplattform ab:<br />
CURSOR_ARROW Pfeil<br />
CURSOR_WAIT Warten<br />
CURSOR_CROSS Fadenkreuz<br />
CURSOR_APPSTARTING Start einer Anwendung<br />
CURSOR_HELP Hilfe<br />
CURSOR_SIZEALL Gesamtgröße ändern<br />
CURSOR_SIZENESW Größenänderung auf NO/SW-Achse<br />
CURSOR_SIZENS Größenänderung auf N/S-Achse<br />
CURSOR_SIZENWSE Größenänderung auf NW/SE-Achse<br />
CURSOR_SIZEWE Größenänderung auf W/O-Achse<br />
CURSOR_SIZEN Größenänderung Nordrichtung<br />
CURSOR_SIZES Größenänderung Südrichtung<br />
CURSOR_SIZEE Größenänderung Ostrichtung<br />
CURSOR_SIZEW Größenänderung Westrichtung<br />
CURSOR_SIZENE Größenänderung Nordostrichtung
CURSOR_SIZESE Größenänderung Südostrichtung<br />
CURSOR_SIZESW Größenänderung Südwestrichtung<br />
CURSOR_SIZENW Größenänderung Nordwestrichtung<br />
CURSOR_UPARROW Pfeil nach oben<br />
CURSOR_IBEAM Schreibmarke<br />
CURSOR_NO unzulässige Operation<br />
CURSOR_HAND Hand zum Verschieben<br />
8.8 Ein Widget mit Swing<br />
Seit Eclipse V3 besteht auch die Möglichkeit, die Pixel des Mauszeigers<br />
auch direkt über die Angabe einer ImageData-Instanz (siehe Abschnitt<br />
8.7.4) anzugeben. Eine zweite ImageData-Instanz kann eine zusätzliche<br />
Maske definieren.<br />
Wichtig ist, dass die Cursor-Instanz wieder mit dispose() freigegeben<br />
werden musss, wenn sie nicht mehr benötigt wird. <strong>Das</strong> Gleiche gilt<br />
für die ImageData-Instanzen.<br />
8.8 Ein Widget mit Swing<br />
Auf Grund seiner Nähe zum jeweiligen Betriebssystem stellt das <strong>SWT</strong><br />
einen neuen Ansatz für die Implementierung der untersten Schicht<br />
einer grafischen Benutzeroberfläche dar. Es stellt sich allerdings die<br />
Frage: Was ist mit den oberen Schichten?<br />
Wenn es um Dinge wie Fenster, Dialoge und Menüs geht, ist die<br />
Antwort einfach. Funktionalität, z.B. mit Swing bereitgestellt, steht in<br />
Eclipse als JFace-Bibliothek zur Verfügung (siehe Kapitel 9).<br />
Schwierig wird es allerdings, wenn es um andere Grafikschichten<br />
geht, z.B. eine leistungsfähige Grafikschicht, wie sie von Java2D oder<br />
Java3D bereitgestellt wird, SVG-Verarbeitung wie in Batik (www.<br />
apache.org) oder Bitmap-Manipulationen wie in Java Advanced Imaging<br />
(JAI). Alle diese APIs passten bisher nicht zum <strong>SWT</strong>. Fortgeschrittene<br />
Funktionen wie Kantenglättung, transparente Grafiken und Textrotation<br />
blieben deshalb außen vor.<br />
Mit Eclipse 3 hat sich das gründlich geändert. Nun ist es möglich,<br />
Swing- und AWT-Inhalte innerhalb von <strong>SWT</strong>-Composites zu präsentieren.<br />
Damit lassen sich auch grundsätzlich höhere Grafikschichten,<br />
die auf Swing oder dem AWT aufbauen, in <strong>SWT</strong>-Anwendungen integrieren.<br />
Und plötzlich macht dann Swing wieder Spaß, dank <strong>SWT</strong>.<br />
Unter Windows funktioniert das unter JRE 1.3 und 1.4, für die anderen<br />
Plattformen wird JRE 1.5 benötigt.<br />
203
204<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
8.8.1 Eingebettete Inhalte<br />
Ermöglicht wird das durch die neue Stilkonstante EMBEDDED in der<br />
Klasse <strong>SWT</strong>. Ein Composite, das mit dieser Stilkonstante erzeugt wird,<br />
kann <strong>SWT</strong>-fremde Inhalte enthalten (allerdings keine zusätzlichen<br />
<strong>SWT</strong>-Inhalte). In Eclipse 3 können das zunächst java.awt.Frame-Komponenten<br />
sein, die mit Hilfe der Factory-Methode <strong>SWT</strong>_AWT.new_<br />
Frame() erzeugt werden können. Also z.B.<br />
Composite awtContainer = new Composite(parent, <strong>SWT</strong>.EMBEDDED);<br />
java.awt.Frame myFrame = <strong>SWT</strong>_AWT.new_Frame(awtContainer);<br />
Nun kann man nach Herzenslust AWT- und Swing-Komponenten auf<br />
die übliche Art zu der so erzeugten Frame-Instanz hinzufügen.<br />
Außerdem verfügt die Klasse <strong>SWT</strong>_AWT noch über die Methode<br />
new_Shell(). Diese erzeugt für einen gegebenen AWT-Canvas eine<br />
<strong>SWT</strong>-Shell, so dass der AWT-Canvas zwar in einem eigenen Fenster,<br />
aber noch innerhalb der <strong>SWT</strong>-Applikation ausgeführt wird.<br />
8.8.2 Ereignisse<br />
Wie verhält es sich nun mit Ereignissen? Nun, das ist nicht besonders<br />
schwierig: den AWT- und Swing-Komponenten werden auf die übliche<br />
Art und Weise Listener zugeordnet, die auf die jeweiligen Ereignisse<br />
reagieren. Vorsicht ist allerdings geboten, wenn man aus einer solchen<br />
Ereignisverarbeitung auf <strong>SWT</strong>-Ressourcen zugreifen will. <strong>SWT</strong> und<br />
AWT laufen in verschiedenen Threads ab. Deshalb müssen diese<br />
Zugriffe, wie bereits in Abschnitt 8.5.2 diskutiert, in ein Runnable<br />
gekapselt und mit Hilfe der Display-Methode syncExec(), asyncExec()<br />
oder timerExec() ausgeführt werden.<br />
Und umgekehrt, beim Zugriff von der <strong>SWT</strong>-Ereignisverarbeitung<br />
auf AWT- bzw. Swing-Komponenten, funktioniert’s ganz ähnlich: auch<br />
hier wird die eigentliche Verarbeitung in ein Runnable gekapselt und<br />
dann mit Hilfe der AWT-Methode EventQueue.invokeLater() ausgeführt.<br />
<strong>Das</strong> wird vom AWT zwar nicht erzwungen (wie es das <strong>SWT</strong><br />
macht), wird aber dringend empfohlen.<br />
Im folgenden Beispiel zeigen wir diese Techniken im Zusammenhang.<br />
Dieses Beispiel zeigt auch, wie man <strong>SWT</strong>-GUI-Elemente auf eine<br />
AWT-Oberfläche platzieren kann (in einer eigenen Shell). <strong>Das</strong> Beispiel<br />
implementiert einen Java2D-Canvas innerhalb einer <strong>SWT</strong>-Shell. Eine in<br />
<strong>SWT</strong> implementierte Taste erlaubt es, den Canvas zu löschen. Klickt<br />
man auf den Canvas, erscheint ein in <strong>SWT</strong> implementiertes Texteingabefeld<br />
auf dem Canvas. Mit einem weiteren Klick verschwindet dieses<br />
Feld wieder, und der eingegebene Text wird auf den Canvas geschrieben.
import java.util.ArrayList;<br />
import java.util.Iterator;<br />
import org.eclipse.swt.<strong>SWT</strong>;<br />
import org.eclipse.swt.awt.<strong>SWT</strong>_AWT;<br />
import org.eclipse.swt.events.*;<br />
import org.eclipse.swt.graphics.*;<br />
import org.eclipse.swt.layout.*;<br />
import org.eclipse.swt.widgets.*;<br />
public class <strong>SWT</strong>2D {<br />
// Shell für Popup-Editor<br />
Shell eShell = null;<br />
// Text-Widget für Editor<br />
Text eText = null;<br />
// Liste eingegebener Zeichenketten<br />
ArrayList wordList = new ArrayList(12);<br />
public static void main(String[] args) {<br />
<strong>SWT</strong>2D swtawt = new <strong>SWT</strong>2D();<br />
swtawt.run();<br />
}<br />
8.8 Ein Widget mit Swing<br />
Zunächst wird die <strong>SWT</strong>-Shell erzeugt. Darin werden in einem GridLayout<br />
das Container-Composite (EMBEDDED) für den AWT-Canvas und<br />
später noch eine Taste platziert.<br />
private void run() {<br />
// Top-Level Shell erzeugen<br />
final Display display = new Display();<br />
final Shell shell = new Shell(display);<br />
shell.setText("Java 2D-Beispiel");<br />
// GridLayout für Canvas und Taste<br />
shell.setLayout(new GridLayout());<br />
// Container für AWT-Canvas erzeugen<br />
final Composite canvasComp = new Composite(shell, <strong>SWT</strong>.EMBEDDED);<br />
// Vorzugsgröße setzen<br />
GridData data = new GridData();<br />
data.widthHint = 600;<br />
data.heightHint = 500;<br />
canvasComp.setLayoutData(data);<br />
Dann wird mit Hilfe der Klasse <strong>SWT</strong>_AWT ein AWT-Frame im <strong>SWT</strong>-<br />
Composite erzeugt. Auf die übliche Art wird diesem Frame dann ein<br />
AWT-Canvas hinzugefügt. Vom Canvas holen wir uns den grafischen<br />
Kontext, auf dem wir später die Zeichenoperationen durchführen.<br />
Außerdem sichern wir uns die anfängliche affine Transformation dieses<br />
grafischen Kontexts, um später nach Rotationsoperationen den<br />
205
206<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Grafikkontext wieder auf den Grundzustand zurückbringen zu können.<br />
Außerdem schalten wir die Kantenglättung ein – eine weitere<br />
Schönheit von Java2D.<br />
// AWT-Frame für Canvas erzeugen<br />
java.awt.Frame canvasFrame = <strong>SWT</strong>_AWT<br />
.new_Frame(canvasComp);<br />
// Canvas erzeugen und zufügen<br />
final java.awt.Canvas canvas = new java.awt.Canvas();<br />
canvasFrame.add(canvas);<br />
// Graphischen Kontext holen und in Java2D-Kontext umwandeln<br />
final java.awt.Graphics2D g2d = (java.awt.Graphics2D) canvas<br />
.getGraphics();<br />
// Kantenglättung einschalten<br />
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,<br />
RenderingHints.VALUE_ANTIALIAS_ON);<br />
// Ursprüngliche Transformation merken<br />
final java.awt.geom.AffineTransform origTransform = g2d<br />
.getTransform();<br />
Nun erzeugen wir die Löschtaste. In ihrer Ereignisverarbeitung wird<br />
ein Neuzeichnen des Canvas veranlasst, und zwar durch den Aufruf<br />
der redraw()-Methode für das <strong>SWT</strong>-Container-Composite.<br />
// Clear-Taste anlegen und positionieren<br />
Button clearButton = new Button(shell, <strong>SWT</strong>.PUSH);<br />
clearButton.setText("Clear");<br />
data = new GridData();<br />
data.horizontalAlignment = GridData.CENTER;<br />
clearButton.setLayoutData(data);<br />
// Ereignisverarbeitung für Taste<br />
clearButton<br />
.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
// Wortliste löschen und Canvas neu zeichnen<br />
wordList.clear();<br />
canvasComp.redraw();<br />
}<br />
});<br />
Bei einem Mausklick auf den Canvas (hier befinden wir uns also in der<br />
AWT-Ereignisverarbeitung) wird das Texteingabefeld wechselweise<br />
sichtbar oder unsichtbar geschaltet. Nur bei der allerersten Benutzung<br />
wird dieser kleine Editor neu erzeugt. Da AWT-Canvassen natürlich<br />
keine <strong>SWT</strong>-Widgets zugefügt werden können, erzeugen wir diesen Editor<br />
in einer eigenen Shell. Wichtig ist, diese Shell nicht-modal anzule-
8.8 Ein Widget mit Swing<br />
gen, so dass auch nach Öffnen der Shell der Canvas für Mausklicks<br />
zugänglich bleibt.<br />
Die Technik, diese Shell mit setVisible() abwechselnd sichtbar<br />
und unsichtbar zu schalten, ist einem wechselweisen Neuanlegen und<br />
Schließen vorzuziehen. Nicht nur werden damit Ressourcen geschont,<br />
auch werden damit unschöne Effekte vermieden. Nach einem close()<br />
würden Teile der close()-Ereignisverarbeitung erst nach Ablauf der<br />
AWT-Ereignisverarbeitung (also auch nach canvasComp.redraw()) ausgeführt,<br />
so dass an der Position des Editors ein hässlicher weißer Fleck<br />
zurückbleiben würde. <strong>Das</strong> ist bei Verwendung der Methode set-<br />
Visible(false) nicht der Fall.<br />
// Mausklicks auf dem Canvas verarbeiten<br />
canvas<br />
.addMouseListener(new java.awt.event.MouseListener() {<br />
public void mouseClicked(<br />
java.awt.event.MouseEvent e) {}<br />
public void mouseEntered(<br />
java.awt.event.MouseEvent e) {}<br />
public void mouseExited(<br />
java.awt.event.MouseEvent e) {}<br />
public void mousePressed(<br />
java.awt.event.MouseEvent e) {<br />
// Popup-Editor verwalten<br />
display.syncExec(new Runnable() {<br />
public void run() {<br />
if (eShell == null) {<br />
// Neue Shell anlegen: nicht-modal!<br />
eShell = new Shell(shell, <strong>SWT</strong>.NO_TRIM<br />
| <strong>SWT</strong>.MODELESS);<br />
eShell.setLayout(new FillLayout());<br />
// Texteingabefeld<br />
eText = new Text(eShell, <strong>SWT</strong>.BORDER);<br />
eText.setText("Textrotation im <strong>SWT</strong>?");<br />
eShell.pack();<br />
// Positionieren (Displaykoordinaten)<br />
java.awt.Rectangle bounds = canvas<br />
.getBounds();<br />
org.eclipse.swt.graphics.Point pos = canvasComp<br />
.toDisplay(bounds.width / 2,<br />
bounds.height / 2);<br />
Point size = eShell.getSize();<br />
eShell.setBounds(pos.x, pos.y, size.x,<br />
207
208<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
}<br />
}<br />
});<br />
size.y);<br />
// Shell öffnen<br />
eShell.open();<br />
} else if (!eShell.isVisible()) {<br />
// Editor versteckt, sichtbar machen<br />
eShell.setVisible(true);<br />
} else {<br />
// Editor sichtbar - Text holen<br />
String t = eText.getText();<br />
// und Editor unsichtbar machen<br />
eShell.setVisible(false);<br />
// Text zur Liste zufügen und Canvas neu zeichnen.<br />
wordList.add(t);<br />
canvasComp.redraw();<br />
}<br />
public void mouseReleased(<br />
java.awt.event.MouseEvent e) {}<br />
});<br />
Schließlich zeigen wir noch die Routine für das Zeichnen des Canvas-<br />
Inhaltes. Dies geschieht in einem PaintListener, der dem <strong>SWT</strong>-Container<br />
des Canvas zugeordnet ist. Wir setzen Java2D-Textrotation ein,<br />
um den eingegebenen Text sternförmig anzuordnen. Da es sich bei den<br />
verwendeten Ressourcen (Color, Font) um AWT-Ressourcen handelt,<br />
müssen diese auch nicht wie im <strong>SWT</strong> nach der Verwendung mit dispose()<br />
entsorgt werden. Die Java Garbage Collection wird sich schon<br />
darum kümmern.<br />
// Den Canvas neu zeichnen<br />
canvasComp.addPaintListener(new PaintListener() {<br />
public void paintControl(PaintEvent e) {<br />
// Die Verarbeitung der AWT-Event-Warteschlange übergeben<br />
java.awt.EventQueue.invokeLater(new Runnable() {<br />
public void run() {<br />
// Canvasmittelpunkt<br />
java.awt.Rectangle bounds = canvas.getBounds();<br />
int originX = bounds.width / 2;<br />
int originY = bounds.height / 2;<br />
// Canvas rücksetzen<br />
g2d.setTransform(origTransform);<br />
g2d.setColor(java.awt.Color.WHITE);<br />
g2d.fillRect(0, 0, bounds.width, bounds.height);
}<br />
}<br />
8.8 Ein Widget mit Swing<br />
// Font setzen<br />
g2d.setFont(new java.awt.Font("Myriad",<br />
java.awt.Font.PLAIN, 32));<br />
double angle = 0d;<br />
// Sternförmige Anordnung vorbereiten<br />
double increment = Math.toRadians(30);<br />
Iterator iter = wordList.iterator();<br />
while (iter.hasNext()) {<br />
// Textfarben im RGB-Farbkreis bestimmen<br />
float red = (float) (0.5 + 0.5 * Math<br />
.sin(angle));<br />
float green = (float) (0.5 + 0.5 * Math<br />
.sin(angle + Math.toRadians(120)));<br />
float blue = (float) (0.5 + 0.5 * Math<br />
.sin(angle + Math.toRadians(240)));<br />
g2d.setColor(new java.awt.Color(red, green,<br />
blue));<br />
// Text zeichnen<br />
String text = (String) iter.next();<br />
g2d.drawString(text, originX + 50, originY);<br />
// Rotation für die nächste Textausgabe<br />
g2d.rotate(increment, originX, originY);<br />
angle += increment;<br />
}<br />
}<br />
});<br />
}<br />
});<br />
// Shell fertigstellen und öffnen<br />
shell.pack();<br />
shell.open();<br />
// <strong>SWT</strong>-Ereignisschleife<br />
while (!shell.isDisposed()) {<br />
if (!display.readAndDispatch()) display.sleep();<br />
}<br />
display.dispose();<br />
Abbildung 8–11 zeigt das Ergebnis.<br />
209
210<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
Abb. 8–11 Textrotation und geglättete Kanten im <strong>SWT</strong>? Mit eingebettetem Java2D-<br />
Canvas kein Problem.<br />
8.9 Ausgabe auf Drucker<br />
Druckausgaben werden mit Hilfe der Klassen PrintDialog, Printer-<br />
Data und Printer bewerkstelligt. PrintDialog ist eine Unterklasse der<br />
uns schon bekannten abstrakten Klasse Dialog und repräsentiert den<br />
Druckauswahldialog des jeweiligen Betriebssystems. Als Ergebnis liefert<br />
PrintDialog entweder eine PrinterData-Instanz oder null zurück.<br />
Die PrinterData-Instanz enthält alle im Druckerauswahldialog gemachten<br />
Angaben wie Anzahl der Kopien, Druckbereich etc. Durch Abfrage<br />
der entsprechenden Felder (copyCount, scope etc.) kann man diese<br />
Angaben auswerten.<br />
Anschließend erzeugt man dann eine Instanz der Printer-Klasse,<br />
die eine Device-Unterklasse ist. Diese Instanz benutzt man, um einen<br />
neuen Grafikkontext zu erzeugen. Auf diesen Grafikkontext werden<br />
dann alle Ausgaben durchgeführt, die notwendig sind, um die Druckseiten<br />
zu füllen.
8.9 Ausgabe auf Drucker<br />
Zunächst wird die startJob()-Methode der Printer-Instanz aufgerufen,<br />
um einen neuen Druckauftrag zu erzeugen. Dann wird für<br />
jede Seite zunächst die startPage()-Methode aufgerufen, die grafischen<br />
Ausgaben auf den Grafikkontext durchgeführt und dann die<br />
endPage()-Methode aufgerufen. Sind alle Seiten ausgedruckt, wird der<br />
Druckauftrag mit endJob() geschlossen. Zum Schluss müssen noch der<br />
Grafikkontext und das Printer-Objekt mit dispose() entsorgt werden.<br />
Der folgende Beispielcode zeigt, wie es geht:<br />
// Taste für Druckausgabe erzeugen<br />
final Button primtButton = new Button(composite, <strong>SWT</strong>.PUSH);<br />
primtButton.setText("Drucken");<br />
// Auf Klickereignisse reagieren<br />
primtButton.addSelectionListener(new SelectionAdapter() {<br />
public void widgetSelected(SelectionEvent e) {<br />
// Shell holen<br />
Shell shell = composite.getShell();<br />
// Druckdialog erzeugen<br />
PrintDialog printDialog = new PrintDialog(shell);<br />
// und ausführen<br />
PrinterData printerData = printDialog.open();<br />
// Prüfen, ob OK gedrückt<br />
if (printerData != null) {<br />
// Neue Printer-Instanz erzeugen<br />
Printer printer = new Printer(printerData);<br />
// Grafikkontext für diesen Drucker erzeugen<br />
GC gc = new GC(printer);<br />
// Druckauftrag öffnen<br />
if (!printer.startJob("Hello"))<br />
System.out.println("Printer start job failed");<br />
else {<br />
// Erste Seite drucken<br />
if (!printer.startPage())<br />
System.out.println("Printer start page 1 failed");<br />
else {<br />
// grüne Systemfarbe vom Drucker holen und setzen<br />
Color green =<br />
printer.getSystemColor(<strong>SWT</strong>.COLOR_DARK_GREEN);<br />
gc.setForeground(green);<br />
// Text zeichnen<br />
gc.drawText("Hello World", 4, 4, true);<br />
// Seite beenden<br />
printer.endPage();<br />
}<br />
211
212<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
}<br />
});<br />
}<br />
// Zweite Seite drucken<br />
if (!printer.startPage())<br />
System.out.println("Printer start page 2 failed");<br />
else {<br />
// blaue Systemfarbe vom Drucker holen und setzen<br />
Color blue = printer.getSystemColor(<strong>SWT</strong>.COLOR_BLUE);<br />
gc.setForeground(blue);<br />
// Text zeichnen<br />
gc.drawText("Hello Eclipse", 4, 4, true);<br />
// Seite beenden<br />
printer.endPage();<br />
}<br />
// Druckauftrag beenden<br />
printer.endJob();<br />
}<br />
// aufräumen<br />
gc.dispose();<br />
printer.dispose();<br />
Diese Routine zeigt allerdings nur den einfachsten Fall. Die Logik wird<br />
komplizierter, wenn man PrinterData-Angaben wie die Anzahl der<br />
Kopien, kollationierten Ausdruck oder Seitenbereiche berücksichtigen<br />
will. Auch ist es angeraten, sich vom Printer-Objekt mit Hilfe der<br />
Methode getDPI() die Druckauflösung zu holen und die grafischen<br />
Operationen entsprechend zu transformieren.<br />
8.10 Datentransfer<br />
Der <strong>SWT</strong>-Datentransfer umfasst sowohl den Austausch von Daten<br />
über die Zwischenablage als auch die Drag&Drop-Operationen mit<br />
der Maus. Die Klassen des Datentransfers befinden sich in Package<br />
org.eclipse.swt.dnd.<br />
8.10.1 Die Zwischenablage<br />
Als Zwischenablage wird die systemweite Zwischenablage der Ablaufplattform<br />
verwendet, zu der <strong>SWT</strong> einen Zugang bereitstellt. Implementiert<br />
wird dieser Zugang in Form der Klasse Clipboard. Diese Klasse<br />
stellt die Methoden setContents() und getContents() zur Verfügung,<br />
mit denen der Inhalt der Zwischenablage gesetzt bzw. abgefragt werden<br />
kann. Beim Erzeugen einer Clipboard-Instanz muss eine Display-
8.10 Datentransfer<br />
Instanz mit angegeben werden. Da das Clipboard Betriebssystemressourcen<br />
belegt, muss es mit dispose() wieder freigegeben werden,<br />
wenn es nicht mehr benötigt wird.<br />
Üblicherweise enthält eine Zwischenablage die kopierten Daten in<br />
mehreren Formaten. Textprozessoren legen beispielsweise die kopierten<br />
Textsegmente im RTF-Format und als einfachen Text ab. Eclipse<br />
unterscheidet diese Datenformate anhand von Transfertypen. Transfertypen<br />
sind als Unterklassen der abstrakten Klasse Transfer implementiert.<br />
Unter anderem sind vorhanden: FileTransfer, MarkerTransfer,<br />
RTFTransfer, TextTransfer und weitere speziellere Transfertypen.<br />
Hat man selbst speziellere Anforderungen an den Transfertyp, so kann<br />
man eigene Transfertypen (üblicherweise als Unterklasse von ByteArrayTransfer)<br />
implementieren. Der Quellcode von ByteArrayTransfer<br />
enthält eine Anleitung, wie das geht.<br />
Die jeweiligen konkreten Transfertypen dienen dabei der<br />
Umwandlung der typspezifischen Datenformate in ein betriebssystemabhängiges<br />
Datenformat für die Zwischenablage. Will man etwas in<br />
die Zwischenablage ablegen, so übergibt man der Methode setContents()<br />
ein Array mit den Daten in den verschiedenen Formaten sowie<br />
ein Array mit den Instanzen der Transfertypen. Entsprechend erhält<br />
man von der Methode getContents() unter Angabe eines Transfertyps<br />
die Daten aus der Zwischenablage im gewünschten Format. Mit der<br />
Methode getAvailableTypes() erhält man ein Array mit den in der<br />
Zwischenablage vertretenen Transfertypen. So erhält man schnell Aufschluss<br />
darüber, ob das gewünschte Format vorhanden ist, ohne den<br />
Inhalt der Zwischenablage lesen zu müssen.<br />
Im Plugin org.eclipse.swt.examples findet man unter Clipboard.java<br />
ein Beispielprogramm für die Verwendung der Zwischenablage.<br />
8.10.2 Drag&Drop<br />
Für Drag&Drop-Operationen benötigt man eine Datenquelle und eine<br />
Datensenke. Dabei kann die eine oder die andere von einer anderen<br />
Anwendung oder auch vom System bereitgestellt werden. In Eclipse<br />
wird die Datenquelle durch die Klasse DragSource implementiert, die<br />
Datensenke durch die Klasse DropTarget. Beide Klassen sind Unterklassen<br />
der Klasse Widget. Eine Anwendung kann mit mehreren Instanzen<br />
dieser Klassen ausgerüstet werden, allerdings muss jede Instanz<br />
eindeutig einer Control-Instanz zugeordnet werden. <strong>Das</strong> geschieht<br />
durch die Angabe der Control-Instanz im Konstruktor von DragSource<br />
oder DropTarget. Damit sind auch die Position und Größe des Daten-<br />
Transfertypen<br />
213
214<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
transferelements festgelegt. Außerdem werden an dieser Stelle auch die<br />
erlaubten Operationen (DND.NONE, DND.MOVE, DND.COPY, DND.LINK)<br />
deklariert.<br />
Bei einer Drag&Drop-Operation erzeugen die Instanzen dieser<br />
Klassen entsprechende Ereignisse (DragSourceEvent bzw. DropTarget-<br />
Event), die mit entsprechenden Listener-Instanzen (DragSourceListener<br />
bzw. DropTargetListener) empfangen werden können. Die verschiedenen<br />
Methoden dieser Listener erlauben eine lückenlose Überwachung<br />
der Drag&Drop-Operation. So wird die dragStart()-<br />
Methode des DragSourceListeners beim Beginn der Drag&Drop-Operation<br />
aufgerufen. Wird der Bereich des DropTargets betreten, wird die<br />
dragEnter()-Methode des DropTargetListeners aufgerufen. drag-<br />
Leave() wird beim Verlassen dieses Bereichs aufgerufen und drag-<br />
Over(), wenn der Mauszeiger über diesem Bereich bewegt wird. Wird<br />
während der Operation der Operationsmodus geändert (z.B. durch<br />
Drücken von Strg oder Alt), wird die Methode dragOperationChanged()<br />
aufgerufen. Wird die Maustaste über der Datensenke losgelassen,<br />
so wird zunächst die Methode dropAccept() des DropTargetListeners<br />
aufgerufen. Hier besteht die letzte Möglichkeit, die Operation<br />
abzulehnen. Anschließend wird die Methode dragSetData() des<br />
DragSourceListeners aufgerufen, die nun die zu übermittelnden Daten<br />
bereitstellen muss. Dann erfolgt der Aufruf der Methode drop() beim<br />
DropTargetListener, die diese Daten entgegennimmt. Schließlich wird<br />
noch die Methode dragFinished() beim DragSourceListener aufgerufen.<br />
Hier besteht noch die Möglichkeit, notwendige Aufräumarbeiten<br />
durchzuführen.<br />
Bei allen Methoden, die vor der eigentlichen Datenübermittlung<br />
aufgerufen werden, kann noch Einfluss auf die Operation genommen<br />
werden. Durch Zuweisen von DND.DROP_NONE an das Feld detail des<br />
DropTargetEvents kann die Operation abgelehnt werden, durch das<br />
Zuweisen eines anderen Operationscodes kann die Operation modifiziert<br />
werden.<br />
Die eigentliche Datenübergabe erfolgt über das data-Feld des Drag-<br />
SourceEvents bzw. des DropTargetEvents. Wie bei der Zwischenablage<br />
(siehe Abschnitt 8.9.1) können die Daten in verschiedenen Formaten<br />
übergeben werden, die – genau wie dort – mit Hilfe von Transfertypen<br />
beschrieben werden. Die Felder dataType bzw. currentDataType der<br />
Ereignisobjekte enthalten den aktuellen Transfertyp.<br />
In Abschnitt 10.6 zeigen wir die Implementierung einer<br />
Drag&Drop-Datensenke am praktischen Beispiel. Einen ausführlichen<br />
Artikel über <strong>SWT</strong>-basiertes Drag&Drop findet man in der Eclipse Corner<br />
(www.eclipse.org) [Irvine2003].
8.11 Ressourcenverwaltung<br />
8.11 Ressourcenverwaltung<br />
Im Verlaufe dieses Kapitels haben wir verschiedene Ressourcen kennen<br />
gelernt, die, wenn nicht mehr benötigt, mit dispose() wieder freigegeben<br />
werden müssen. Dazu gehören insbesondere Instanzen der Klassen<br />
Color, Font, Image, GC, Cursor, Printer, Display, Shell und Clipboard.<br />
Bei allen diesen Ressourcen gilt der Grundsatz:<br />
Haben Sie etwas selbst erzeugt, müssen Sie es auch selbst entsorgen.<br />
Haben Sie dagegen eine Ressource von anderswo bezogen (z.B. mit get-<br />
SystemColor()), so dürfen Sie diese Ressource nicht selbst entsorgen.<br />
Also kurz gesagt, es gilt das Verursacherprinzip. Am Ende eines Programms<br />
braucht man freilich die Ressourcen nicht selbst zu entsorgen,<br />
das Betriebssystem sorgt dann schon automatisch dafür. Es geht hier<br />
also lediglich um Ressourcen, die nur in einem bestimmten Abschnitt<br />
und zu einer bestimmten Zeit von einer Applikation belegt werden.<br />
<strong>Das</strong> klingt zunächst recht einfach, erweist sich in der Praxis aber<br />
mitunter als kompliziert. Oft ist es so, dass eine Farbe, eine Schriftart<br />
oder ein Bild an mehreren Stellen eines Programms eingesetzt wird.<br />
Wer ist dann für die Entsorgung zuständig? Und ist es wirklich notwendig,<br />
eine Ressource zu entsorgen, die kurz hinterher wieder an<br />
anderer Stelle verwendet wird und dann neu erzeugt werden muss?<br />
Aus diesen Gründen greift man – fast wie im richtigen Leben – auf<br />
die Idee des Leihhauses zurück. Man implementiert einen Resource-<br />
Store, eine Instanz, die über das Leben mehrerer Ressourcen wacht,<br />
und erst dann, wenn der ResourceStore selbst entsorgt wird, werden<br />
auch seine Ressourcen entsorgt. Damit wird es auch möglich, die gleiche<br />
Ressource mehrfach zu verwenden. <strong>Das</strong> ist insbesondere bei Bildern<br />
nützlich, die oft recht speicherhungrig sind.<br />
Wir zeigen hier kurz das Prinzip eines ResourceStore an einem Beispiel<br />
für das Verwalten von Farbressourcen. Erhält die Klasse Color-<br />
Store eine Anfrage nach einer Farbe, die sie noch nicht kennt, so wird<br />
eine Color-Instanz neu erzeugt und dem ColorStore zugefügt. Ist die<br />
Farbe bereits bekannt, so wird die Anfrage aus dem ColorStore befriedigt.<br />
Erst wenn für den ColorStore die dispose()-Methode aufgerufen<br />
wird, werden alle Farbressourcen freigegeben.<br />
import java.util.HashMap;<br />
import java.util.Iterator;<br />
import java.util.Map;<br />
import org.eclipse.swt.graphics.Color;<br />
import org.eclipse.swt.graphics.Device;<br />
ResourceStore<br />
215
216<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
public class ColorStore {<br />
}<br />
private static Map store = new HashMap();<br />
/**<br />
* Method getColor.<br />
* @param name some Color name<br />
* @param device Device instance<br />
* @param r red-value<br />
* @param g green-value<br />
* @param b blue-value<br />
* @return Color requested color<br />
*/<br />
public static Color getColor(String name,Device device,<br />
int r, int g, int b) {<br />
Object obj = store.get(name);<br />
if (obj == null) {<br />
Color newColor = new Color(device,r,g,b);<br />
store.put(name,newColor);<br />
return newColor;<br />
}<br />
return (Color) obj;<br />
}<br />
/**<br />
* Method dispose.<br />
*/<br />
public static void dispose() {<br />
Iterator iter = store.values().iterator();<br />
while (iter.hasNext()) {<br />
Color color = (Color) iter.next();<br />
color.dispose();<br />
}<br />
}<br />
Der Aufruf sieht dann z.B. so aus:<br />
Color green = ColorStore.getColor("green",display,0,255,0);<br />
Da alle Methoden der Klasse ColorStore statisch sind, kann Color-<br />
Store die Farben der gesamten Applikation verwalten. Erst wenn man<br />
keine Farben mehr braucht, entsorgt man alle Farben mit:<br />
ColorStore.dispose();<br />
In Abschnitt 9.1 werden wir einige vorgefertigte Registraturen für<br />
Schriftarten und Bilder diskutieren.
8.12 Windows32-Unterstützung (OLE)<br />
8.12 Windows32-Unterstützung (OLE)<br />
<strong>SWT</strong> stellt eine spezielle Bibliothek für die Unterstützung des OLE-<br />
Mechanismus in Microsoft Windows Betriebssystemen zur Verfügung.<br />
Der Microsoft Win32 Object Linking and Embedding-Mechanismus<br />
(OLE) wird über die Klassen der Package org.eclipse.swt.ole.win32<br />
unterstützt. OLE gestattet es, OLE-Dokumente oder ActiveX-Kontrollelemente<br />
in andere Applikationen (Container) einzubetten. So wird es<br />
z.B. möglich, den Microsoft Internet Explorer als <strong>SWT</strong>-GUI-Element<br />
zu verwenden (das Browser-Widget ist so implementiert), oder aber ein<br />
Microsoft-Office-Dokument in ein <strong>SWT</strong>-GUI einzubetten. Um diese<br />
Klassen nutzen zu können, sollte man über ausreichende OLE-Kenntnisse<br />
verfügen. Ein kleines Beispiel-Plugin findet man in der Eclipse-Beispielsammlung<br />
unter org.eclipse.swt.examples.ole.win32_3.0.0.<br />
8.13 <strong>SWT</strong> auf dem Pocket PC<br />
Die Microsoft Pocket-PC-Plattform ist eine gültige Ablaufplattform<br />
für <strong>SWT</strong>-Anwendungen. Allerdings gelten dabei eine Reihe von Randbedingungen,<br />
die es bei der Erstellung von <strong>SWT</strong>-Applikationen für<br />
unter WindowsCE betriebenen Maschinen zu beachten gibt:<br />
! Noch mehr als Desktop-Anwender legen PDA-Anwender größten<br />
Wert auf Benutzungskomfort.<br />
! Die Prozessoren sind langsamer als bei Desktop-Maschinen.<br />
! Der verfügbare Speicherplatz ist meist kleiner als bei Desktop-<br />
Maschinen.<br />
! Die Größe des Anzeigeschirms ist im Vergleich zum Desktop stark<br />
eingeschränkt.<br />
! Oft steht keine Tastatur, sondern nur ein Stift zur Verfügung.<br />
Ist keine physische Tastatur vorhanden, ist es gängige Praxis, für Texteingaben<br />
eine emulierte Tastatur auf der Anzeigeeinheit abzubilden.<br />
<strong>Das</strong> verringert den für andere Fenster verfügbaren Platz. Pocket-PC-<br />
Anwendungen sollten deshalb beim Erzeugen einer Shell die Stilvariable<br />
<strong>SWT</strong>.RESIZE verwenden, um eine automatische Größenanpassung<br />
zu ermöglichen.<br />
Um die Größe der <strong>SWT</strong>-Bibliothek zu verkleinern, wurden bei der<br />
Pocket-PC-Version, die auf www.eclipse.org erhältlich ist, einige<br />
Packages weggelassen. Dazu gehören:<br />
! org.eclipse.swt.dnd (Drag&Drop, siehe Abschnitt 8.10.2)<br />
! org.eclipse.swt.ole (OLE, siehe Abschnitt 8.12)<br />
! org.eclipse.swt.accessibility (Eingabehilfen, siehe Abschnitt 8.14)<br />
217
218<br />
8 <strong>Das</strong> <strong>SWT</strong><br />
! org.eclipse.swt.custom (Spezielle Widgets, siehe Abschnitt 8.5.13)<br />
! org.eclipse.swt.printing (Druckfunktion, siehe Abschnitt 8.9)<br />
! org.eclipse.swt.program (Dateiassoziationen)<br />
Natürlich kann man auch eine <strong>SWT</strong>-Bibliothek nach eigenen Bedürfnissen<br />
zusammenstellen. Durch Löschen unbenutzter Klassen kann die<br />
Bibliothek weiter verkleinert werden. Der Pocket-PC-Artikel [Cornu2003]<br />
erklärt detailliert, wie man das macht, und wie man auch die Startzeit<br />
einer Pocket-PC-Applikation minimieren kann.<br />
8.14 Behindertengerechte Software<br />
Zum Schluss gehen wir noch kurz darauf ein, wie das <strong>SWT</strong> die Gestaltung<br />
behindertengerechter Software unterstützt. Insbesondere bei<br />
kommerziellen Entwicklungen ist die behindertengerechte Gestaltung<br />
der Benutzeroberfläche ein wichtiger Punkt. So dürfen beispielsweise<br />
viele Behörden nur Softwareprodukte erwerben, die behindertengerecht<br />
gestaltet wurden.<br />
In der Eclipse-Dokumentation gibt es ein besonderes Kapitel, wie<br />
<strong>SWT</strong>-basierte Oberflächen behindertengerecht gestaltet werden können.<br />
Wir finden diesen Abschnitt unter Platform Plugin Developer<br />
Guide unter Reference>Other Reference Information>Tips For<br />
Making User Interfaces Accessible.<br />
Manche Betriebssysteme unterstützen spezielle behindertengerechte<br />
Endgeräte und stellen dafür eine Programmierschnittstelle zur<br />
Verfügung. Eclipse unterstützt das Microsoft Active Accessibility<br />
(MSAA) API. Diese Unterstützung wird durch die Klassen im Package<br />
org.eclipse.swt.accessibility bereitgestellt. <strong>SWT</strong>-Control-Instanzen<br />
können durch den Aufruf getAccessible() eine Instanz der Klasse<br />
Accessible bereitstellen, die als Bindeglied zum Accessibility API<br />
dient.