20.08.2012 Aufrufe

8 Das SWT

8 Das SWT

8 Das SWT

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

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

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.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!