Entwicklung von eigenen Widgets
Obwohl die Widget-Klassen aus technischen Gründen nicht final sind, sollten in SWT nur die Widgets abgeleitet werden, bei denen dies explizit erlaubt ist. Dies ist nur bei Container-Klassen wie Composite der Fall:
This class may be subclassed by custom control implementors who are building controls that are constructed from aggregates of other controls.
Wie können für SWT eigene Widgets oder Erweiterungen zu existierenden Widgets realisiert werden?
3rd Party Widgets
Benötigt man über den Funktionsumfang von SWT hinausgehende Widgets, empfiehlt sich zunächst der Blick in vorhandene Komponentensammlungen für SWT: Zusätzliche Widgets für SWT
Formatter und Factory-Klassen
Lässt sich die Erweiterung als Ergänzung zu existierenden Widgets umsetzen, z.B. durch Setzen von Eigenschaften oder Hinzufügen von Listenern, bietet sich die Realisierung einer Formatter oder Factory-Klasse an. Ein Formatter verändert ein existierendes Widget, eine Factory baut das komplette Widget inklusive der gewünschten Veränderungen:
public class SomeReusableSwtStuff {
public static void formatLabel(Label label) {
label.setForeground(label.getDisplay().getSystemColor(SWT.COLOR_BLUE));
}
public static Label buildLabel(Composite parent, int style) {
Label label = new Label(parent, style);
label.setForeground(label.getDisplay().getSystemColor(SWT.COLOR_BLUE));
return label;
}
}
Diese Methoden können Sie bei der Erstellung Ihrer Masken nun verwenden, um das gewünschte Verhalten abzurufen.
Selbst zeichnende Controls
Um ein Control realisieren, welches seine Inhalte selbst zeichnet, leiten Sie eine Klasse von Canvas ab und fügen einen PaintListener hinzu. Der PaintListener ist dafür zuständig, auf Anfrage die Inhalte des Canvas zu zeichnen. Er erhält dazu den GraphicalContext gc mit allen Zeichenoperationen. Im folgenden Beispiel sehen Sie eine einfache Canvas-Klasse, die einen Kreis zeichnet:
public class CircleCanvas extends Canvas {
public CircleCanvas(Composite parent, int style) {
super(parent, style);
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.setBackground(e.display.getSystemColor(SWT.COLOR_RED));
e.gc.fillOval(0, 0, e.width, e.height);
}
});
}
}
Ein wichtiges Detail fehlt noch: Von SWT-Komponenten wird erwartet, dass sie Auskunft über ihre bevorzugte Größe geben können. SWT wird dazu auf dem Canvas die Methode computeSize aufrufen:
public Point computeSize(int widthHint, int heightHint, boolean changed);
widthHint und heightHint sind dabei Größenwünsche vom Aufrufer. Hat dieser keine Wünsche, wird SWT.DEFAULT übergeben. Der Parameter changed gibt an, ob sich etwas an den Inhalten selbst geändert haben könnte. Ist changed = false, wurde lediglich die Größe des Composites verändert und evt. noch vorhandene Zwischenergebnisse vom letzten Aufruf dürfen wiederverwendet werden.
Die Standard-Implementierung von computeSize überlässt die Ermittlung der Wunschgröße dem gesetzten Layout bzw. liefert eine Standardgröße von 64x64 Pixeln zurück. Diese Methode ist passend zur Zeichenlogik zu implementieren, damit der Layout-Manager die Größenwünsche des Controls berücksichtigen kann.
Vorhandene Controls zusammensetzen
Möchten Sie ein Control erstellen, welches sich aus anderen Controls zusammensetzt, leiten Sie eine Klasse von Composite ab. Im Folgenden sehen Sie eine Composite-Klasse LabeledText, die ein Label und ein Text-Eingabefeld kombiniert:
public class LabeledText extends Composite {
private final Label label;
private final Text text;
public LabeledText(Composite parent, int style) {
super(parent, style);
label = new Label(this, SWT.NONE);
text = new Text(this, SWT.BORDER);
RowLayout layout = new RowLayout();
layout.center = true;
setLayout(layout);
}
}
Wichtig ist dabei die Frage, wie die Methoden der eingebetteten Controls nach Außen gegeben werden. Wenn Sie nur einzelne Methoden der Controls benötigen, können Sie Getter und Setter-Methoden bereitstellen, die an die jeweiligen Controls weiterdelegieren:
public String getText() {
return this.text.getText();
}
public void setText(String s) {
this.text.setText(s);
}
Das hat vor allem den Vorteil, dass die beinhalteten Controls komplett gekapselt sind und jederzeit durch andere Implementierungen ausgetauscht werden können. Kommt es Ihnen auf diesen Aspekt nicht an oder möchten Sie alle Methoden der inneren Controls veröffentlichen, könnten Sie auch einen Getter für die Komponenten selbst bereitstellen:
public Label getLabel() {
return label;
}
Abgesehen von der Methodensignatur des Controls stellt sich die Frage nach der Positionierung und Wunschgröße der eingebetteten Controls. Im obigen Beispiel wurde dazu das Standard-RowLayout verwendet. Dieses richtet die inneren Komponenten aus und wird eine Größe berechnen, die die Platzwünsche aller beinhalteten Controls beachtet.
Möchte man über die Bordmittel der Standard-Layouts hinaus mehr Kontrolle über die Anordnung der beinhalteten Komponenten haben, kann man die Positionierung der Controls selbst übernehmen. Empfehlenswert ist dazu die Implementierung einer eigenen Layout-Klasse. Um das Prinzip zu illustrieren hier ein Beispiel mit einfacher, absoluter Positionierung:
public class LabeledText extends Composite {
private static final Layout LABEL_TEXT_LAYOUT = new Layout() {
@Override
protected Point computeSize(Composite composite, int widthHint, int heightHint,
boolean flushCache) {
return new Point(200, 100);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
composite.getChildren()[0].setBounds(0, 0, 50, 50);
composite.getChildren()[1].setBounds(0, 50, 200, 50);
}
};
public LabeledText(Composite parent, int style) {
super(parent, style);
Label label = new Label(this, SWT.NONE);
Text text = new Text(this, SWT.BORDER);
setLayout(LABEL_TEXT_LAYOUT);
}
}
Über Ereignisse benachrichtigen
Sowohl bei der Implementierung von Canvas- als auch Composite-basierten Steuerelementen werden Sie gelegentlich die Möglichkeit benötigen, die Außenwelt aus dem Control heraus über Ereignisse zu benachrichtigen.
Legen Sie dafür zunächst ein Event-Objekt an, welches das aufgetretene Ereignis repräsentiert. Dieses sollte von java.util.EventObject erben:
public class SomeEvent extends EventObject {
private final String someInfo;
public SomeEvent(Object source, String someInfo) {
super(source);
this.someInfo = someInfo;
}
public String getSomeInfo() {
return someInfo;
}
}
Stellen Sie außerdem ein Listener-Interface bereit, welches zu implementieren ist, um über dieses Ereignis benachrichtigt zu werden:
public interface SomeEventListener {
void someEventHappened(SomeEvent e);
}
Fügen Sie Ihrem Control nun Methoden hinzu, um den Listener zu registrieren und wieder zu entfernen:
private LinkedHashSet<SomeEventListener> someEventListeners
= new LinkedHashSet<SomeEventListener>(1);
public void addSomeEventListener(SomeEventListener listener) {
someEventListeners.add(listener);
}
public void removeSomeEventListener(SomeEventListener listener) {
someEventListeners.remove(listener);
}
Das Control kann den Event auslösen, indem es alle registrierten Listener aufruft:
SomeEvent event = new SomeEvent(this, "some info");
for (SomeEventListener listener : someEventListeners) {
listener.someEventHappened(event);
}


