Ein Container ist eine Komponente, welche eine Liste von
Controls enthält. Die
Schnittstelle IContainer
erweitert
IComponent
und somit auch
IWidget
.
Die Controls eines Containers werden mit Hilfe eines
Layouters angeordnet. Es folgt
eine Beschreibung der wichtigsten Methoden.
Folgende Methoden können verwendet werden, um Controls mit Hilfe eines BluePrints zu einem Container hinzuzufügen:
<WIDGET_TYPE extends IControl> WIDGET_TYPE add( int index, IWidgetDescriptor<? extends WIDGET_TYPE> descriptor, Object layoutConstraints); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( int index, IWidgetDescriptor<? extends WIDGET_TYPE> descriptor); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( IWidgetDescriptor<? extends WIDGET_TYPE> descriptor, Object layoutConstraints); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( IWidgetDescriptor<? extends WIDGET_TYPE> descriptor);
Ein Control wird hinzugefügt, indem man ein BluePrint
hinzufügt. Jedes BluePrint implementiert die Schnittstelle
IWidgetDescriptor
für einen konkreten
WIDGET_TYPE
. Die add()
Methode liefert das erzeugte Control als Rückgabewert.
Der Index bestimmt, an welcher Stelle das Control in die interne Liste eingefügt werden soll. Wird kein Index angegeben, wird das Control am Ende hinzugefügt.
Die layoutConstraints
werden vom
verwendeten Layouter ausgewertet und sind somit
layout spezifisch. Da ein Layouter nicht
immer Constraints benötigt, können diese auch weggelassen
werden. Zudem ist es möglich, die Constraints nachträglich auf
dem Control zu setzen.
Es folgt ein Beispiel für die Verwendung der
add()
Methode:
1 final String growCC = "growx, w 0::";
2
3 final IInputField<Date> dateField = container.add(BPF.inputFieldDate(), growCC);
4
5 final IComboBoxSelectionBluePrint<Gender> genderCmbBp = BPF.comboBoxSelection(Gender.values());
6 final IComboBox<Gender> genderCmb = container.add(genderCmbBp, growCC);
7
8 final IButton okButton = container.add(BPF.buttonOk());
9
10 final ICheckBoxBluePrint licenceCbBp = BPF.checkBox().setText("Accept");
11 final ICheckBox licenceCb = container.add(2, licenceCbBp);
In Zeile 3-8 wird ein Datumsfeld, eine
ComboBox
für die Enum
Gender
sowie ein
OkButton
hinzugefügt. Das Datumsfeld sowie
die Combobox haben ein
MigLayout Constraint, dass
sie den ganzen horizontalen Platz ausfüllen sollen
(growx
).
In Zeile 10-11 wird dann eine Checkbox an Position zwei (zwischen Combobox und Botton) eingefügt.
Will man selbst Controls erstellen, hat man folgende Möglichkeiten:
Man erstellt eine eigene Widget Bibliothek, siehe Erstellung eigener Widget Bibliotheken. Dabei wird auch ein BluePrint für das Control definiert welches man wie weiter oben beschrieben verwenden kann. Dies ist das empfohlene Vorgehen, wenn das Control wiederverwendet werden soll, da ein solch erstelltes Widget alle Vorzüge von jowidgets bietet.
Man leitet von einem Basis Widget ab, um ein Widget zu kapseln.
Man kapselt ein Widgets mit Hilfe eines
ICustomWidgetCreator
.
Folgende Methoden können verwendet werden, um Controls mit
Hilfe eines ICustomWidgetCreator
zu einem
Container hinzuzufügen:
<WIDGET_TYPE extends IControl> WIDGET_TYPE add( int index, ICustomWidgetCreator<WIDGET_TYPE> creator, Object layoutConstraints); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( int index, ICustomWidgetCreator<WIDGET_TYPE> creator); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( ICustomWidgetCreator<WIDGET_TYPE> creator, Object layoutConstraints); <WIDGET_TYPE extends IControl> WIDGET_TYPE add( ICustomWidgetCreator<WIDGET_TYPE> creator);
Die Verwendung des index
und der
layoutConstraints
ist analog wie beider
Verwendung von BluePrints.
Es folgt ein Beispiel für eine Implementierung der
ICustomWidgetCreator
Schnittstelle:
1 public final class ErrorLabel implements ICustomWidgetCreator<ILabel> {
2
3 private final String errorText;
4
5 public ErrorLabel(final String errorText) {
6 this.errorText = errorText;
7 }
8
9 @Override
10 public ILabel create(final ICustomWidgetFactory widgetFactory) {
11 final ILabelBluePrint labelBp = BPF.label();
12 labelBp.setText(errorText).setIcon(IconsSmall.ERROR);
13 final ILabel label = widgetFactory.create(labelBp);
14 return label;
15 }
16 }
In Zeile 13 wird die ICustomWidgetFactory
verwendet, um ein ILabel
Widget zu
erzeugen.
Das ErrorLabel
Control kann nun wie folgt
einem Container hinzugefügt werden:
1 final ILabel label = container.add(new ErrorLabel("Ups!!!"));
Das folgende Beispiel Implementiert ein Control, welches den gesamten Platz mit schwarzer Farbe ausfüllt:
1 public final class BlackBox implements ICustomWidgetCreator<IControl> { 2 3 @Override 4 public IControl create(final ICustomWidgetFactory widgetFactory) { 5 final ICanvas canvas = widgetFactory.create(BPF.canvas()); 6 7 canvas.addPaintListener(new IPaintListener() { 8 @Override 9 public void paint(final IPaintEvent paintEvent) { 10 final IGraphicContext gc = paintEvent.getGraphicContext(); 11 final Rectangle bounds = gc.getBounds(); 12 gc.setBackgroundColor(Colors.BLACK); 13 gc.clearRectangle( 14 bounds.getX(), 15 bounds.getY(), 16 bounds.getWidth(), 17 bounds.getHeight()); 18 } 19 }); 20 21 return canvas; 22 } 23 }
Hier wird in Zeile 5 die
ICustomWidgetFactory
verwendet, um ein
Canvas zu erzeugen welches im
IPaintListener
ab Zeile 7 den gesammten
Bereich mit schwarzer Farbe ausfüllt.
Das BlackBox
Control kann dann die folgt
einem Container hinzugefügt werden:
1 final IControl = frame.add(new BlackBox(), "growx, w 0::");
Controls können entweder auf dem Container mittels der folgenden Methoden entfernt werden:
boolean remove(IControl control); void removeAll();
Oder man ruft auf dem Control selbst die
dispose()
Methode auf:
1 final IInputField<Date> dateField = container.add(BPF.inputFieldDate(), growCC); 2 dateField.dispose();
In Zeile 1 wird ein DateField
zum Container
hinzugefügt, in Zeile 2 wird es wieder entfernt.
Im folgenden Beispiel wird der gleiche Effekt erzielt, nur dass das Entfernen über den Container stattfindet.
1 final IInputField<Date> dateField = container.add(BPF.inputFieldDate(), growCC); 2 container.remove(dateField);
In beiden Fällen wird das DateField
disposed und kann anschließend nicht mehr verwendet werden.
Folgende Methoden können verwendet werden, um das Layout für den Container festzulegen.
void setLayout(ILayoutDescriptor layoutDescriptor); <LAYOUT_TYPE extends ILayouter> LAYOUT_TYPE setLayout(ILayoutFactory<LAYOUT_TYPE> layoutFactory);
Das Layout kann entweder über ein
ILayoutDescriptor
oder über eine
ILayoutFactory
festgelegt werden. Der
resultierende ILayouter
ist dafür
verantwortlich, die Controls eines Containers in der
richtigen Größe an die
richtige Position zu Zeichnen.
Mittels der folgenden Methoden kann der Layoutprozess manuell angestoßen werden:
void layoutBegin(); void layoutEnd(); void layout(); void layoutLater();
Die Methode layout()
berechnet das Layout
für den Container neu.
Die Methode layoutBegin()
kann verwendet
werden, um anzuzeigen, dass mehrere Änderungen des Containers
folgen, welche das Layout beeinflussen. Dadurch wird
verhindert, dass der Container neu gezeichnet wird, bis die
Methode layoutEnd()
aufgerufen wird. Diese
kann verwendet werden, um Flackereffekte
zu vermeiden.
Die Methode layoutLater()
führt ein Layout
in einem späteren UI Event durch.
Mehrere Aufrufe der Methode
layoutLater()
im aktuellen Event bewirken
dabei nur einen späteren
Aufruf der Methode layout()
.
Damit lässt sich ein in der Praxis gelegentlich auftretendes
Problem lösen, welches bewirken kann, dass viele
unabhängige Änderungen eines Containers
in einem Ui Event ein sofortiges layout()
nach sich ziehen. Dann wird in einem Event mehrfach ein
layout() durchgeführt, obwohl das Ergebnis nicht mehr als ein
Mal pro UI Event sichtbar werden kann. De facto wird also nur
der letzte layout()
Aufruf für den Nutzer
sichtbar, die vielen anderen layout()
Aufrufe haben dabei unnötiger Weise den UI Thread blockiert.
Durch den mehrfachen Aufruf der Methode
layoutLater()
anstatt
layout()
tritt das Problem dann nicht mehr
auf, weil nur ein mal zu einem späteren Zeitpunkt ein
layout()
durchgeführt wird.
Um einen eigenen Layouter zu implementieren, sind die folgenden Methoden relevant:
Rectangle getClientArea(); Dimension computeDecoratedSize(Dimension clientAreaSize);
Die Methode getClientArea()
gibt genau den
Bereich zurück, welcher für das Zeichnen der Controls zur
Verfügung steht.
Die Methode computeDecoratedSize()
berechnet für eine gewünschte
clientAreaSize
die notwendige Größe des
gesamten Containers.
Für weitere Details zu diesem Thema sei auf den Abschnitt Layouting verwiesen.
Um die Reihenfolge festzulegen, in welcher durch die Controls durch Verwendung der Tabulator Taste navigiert wird, können die folgenden Methoden verwendet werden:
void setTabOrder(Collection<? extends IControl> tabOrder); void setTabOrder(IControl... controls);
Wird null übergeben, wird die default Reihenfolge verwendet
Mit Hilfe der folgenden Methode erhält man alle derzeit vorhanden Kind Controls eines Containers:
List<IControl> getChildren();
Dabei handelt es sich um eine nicht modifizierbare
(unmodifieable) Kopie der aktuellen Liste der Kind Controls.
Dadurch ist zum Beispiel das folgende möglich, ohne eine
ConcurrentModificationException
zu
bekommen:
1 container.layoutBegin(); 2 for (final IControl childControl : container.getChildren()) { 3 if (!filter.accept(childControl)) { 4 container.remove(childControl); 5 } 6 } 7 container.layoutEnd();
Um sich darüber informieren zu lassen, wenn Kinder hinzugefügt
oder entfernt werden, kann ein
IContainerListener
verwendet werden:
void addContainerListener(IContainerListener listener); void removeContainerListener(IContainerListener listener);
Dieser hat die folgenden Methoden:
void afterAdded(IControl control); void beforeRemove(IControl control);
Die Methode afterAdded()
wird aufgerufen,
nachdem ein Control zum Container hinzugefügt wurde. Die
Methode beforeRemove()
wird aufgerufen,
bevor ein Control vom Container entfernt wird. Dabei spielt es
keine Rolle, ob das Control mittels
dispose()
oder mittels
remove()
entfernt wird. Das Control ist zum
Zeitpunkt des Aufrufs der Methode
beforeRemove()
noch
nicht disposed.
Ein Container kann Controls enthalten, welche selbst wiederum
Container sein können. Dies ist zum Beispiel mit Hilfe eines
Composite möglich, welches
ein IContainer
und ein
IControl
zugleich ist.
In der Praxis kann es vorkommen, dass zu einem eigenen Control Controls hinzugefügt werden, deren interner Aufbau einem über die Zeit (Controls können kommen und gehen) verborgen ist. Dennoch hätte man eventuell gerne Kenntnis über alle Kinder eines Containers (auch rekursiv), zum Beispiel um einen Listener hinzuzufügen, welcher ein PopupMenu öffnet.
Zu diesem Zweck kann eine
IContainerRegistry
verwendet werden, welche
dem Container hinzugefügt wird:
void addContainerRegistry(IContainerRegistry registry); void removeContainerRegistry(IContainerRegistry registry);
Die Schnittstelle IContainerRegistry
hat
die folgenden Methoden:
void register(IControl control); void unregister(IControl control);
Die Methode register()
wird für alle
bereits vorhandenen (zum Zeitpunkt des Aufrufes von
addContainerRegistry()
) Controls sowie für
alle in der Zukunft hinzugefügten Controls aufgerufen. Das
ganze wird rekursiv durchgeführt, wodurch
auch alle Kind Controls sowie deren Kinder usw. erfasst
werden.
Werden Controls (oder Kind Controls, usw.) entfernt, wird die
Methode unregister()
aufgerufen. Kind
Controls welche bereits vor dem Aufruf der Methode
addContainerRegistry()
entfernt wurden,
werden dabei nicht berücksichtigt.
Zum rekursiven hinzufügen von Listenern zu einem Container und dessen Kindern stehen folgende Methoden zur Verfügung:
void addComponentListenerRecursive(IListenerFactory<IComponentListener> listenerFactory); void removeComponentListenerRecursive(IListenerFactory<IComponentListener> listenerFactory); void addFocusListenerRecursive(IListenerFactory<IFocusListener> listenerFactory); void removeFocusListenerRecursive(IListenerFactory<IFocusListener> listenerFactory); void addKeyListenerRecursive(IListenerFactory<IKeyListener> listenerFactory); void removeKeyListenerRecursive(IListenerFactory<IKeyListener> listenerFactory); void addMouseListenerRecursive(IListenerFactory<IMouseListener> listenerFactory); void removeMouseListenerRecursive(IListenerFactory<IMouseListener> listenerFactory); void addPopupDetectionListenerRecursive(IListenerFactory<IPopupDetectionListener> listenerFactory); void removePopupDetectionListenerRecursive(IListenerFactory<IPopupDetectionListener> listenerFactory);
Die Schnittstelle IListenerFactory
sieht
wie folgt aus:
public interface IListenerFactory<LISTENER_TYPE> { LISTENER_TYPE create(IComponent component); }
Durch das Hinzufügen eines rekursiven Listeners wird für den
Container selbst und für jedes aktuelle und in der Zukunft
hinzugefügte Control die create()
Methode
aufgerufen. Wird durch den Implementierer der
IListenerFactory
Schnittstelle ein Listener
erzeugt, wird dieser der Component hinzugefügt, und vor dem
dispose der Component wieder entfernt. Die
create()
Methode kann auch
null
zurückgeben, wodurch für diese
Component kein Listener hinzugefügt wird.
Das folgende Beispiel soll dies verdeutlichen:
1 final IPopupMenu labelMenu = container.createPopupMenu(); 2 labelMenu.setModel(labelMenuModel); 3 4 IListenerFactory<IPopupDetectionListener> listenerFactory; 5 listenerFactory = new IListenerFactory<IPopupDetectionListener>() { 6 @Override 7 public IPopupDetectionListener create(final IComponent component) { 8 if (component instanceof ILabel) { 9 final ILabel label = (ILabel) component; 10 return new IPopupDetectionListener() { 11 @Override 12 public void popupDetected(final Position position) { 13 labelMenuModel.setLabel(label); 14 labelMenu.show(label.toComponent(position, container)); 15 } 16 }; 17 } 18 return null; 19 } 20 }; 21 22 container.addPopupDetectionListenerRecursive(listenerFactory);
Für alle Labels eines Containers wird ein Kontextmenü
(labelMenu
) hinzugefügt. Bevor das Menü
angezeigt wird, wird das Label auf dem zugehörigen Model
(Zeile 13) gesetzt, um etwaige Aktionen bezüglich des
ausgewählten Labels durchführen zu können.
Das Kontextmenü könnte zum Beispiel Aktionen enthalten, welche den Inhalt des Labels in die Zwischenablage kopieren oder dessen Farbe ändern, etc.