How to write your own widget
The best way to start writing your own widget is to study the existing widgets.
These range from very simple (such as SelectAllOnFocusGainWidget - see
example walk-through below) to very complex (such as ComboboxAutoCompletionWidget).
In addition, widgets differ in the dependency on LAF support (explained below).
First, here is an example of a very simple widget in
org.jvnet.lafwidget.text.SelectAllOnFocusGainWidget class.
This widget adds "select all on focus gain" behaviour on text components that
have LafWidget.TEXT_SELECT_ON_FOCUS client property set to
Boolean.TRUE. In order to do this, the widget adds a focus listener
to relevant text component. The focus listener implements the focusGained
function which checks if the text component has the relevant client property set,
and if so, calls the selectAll method of the component. A number of important
things that this simple widget illustrates:
- Since the configuration file registers this widgets on javax.swing.text.JTextComponent,
we can safely assume that we are dealing with text components (casting-wise).
- We extend the LafWidgetAdapter that provides default implementation
of setComponent method (storing the component) and empty implemenations
of the lifecycle methods. This allows us to implement only relevant methods
(installListeners and uninstallListeners).
- The setComponent implementation stores the casted text component
to cut down on the number of casts in the lifecycle methods. This is not required
but recommended if you're going to use the specific component type extensively.
- It's extremely important to unregister all registered listeners (otherwise
you will start retaining memory and leaking resources).
The complete source of this widget is
/**
* Adds "select all on focus gain" behaviour on text components.
*
* @author Kirill Grouchnikov
*/
public class SelectAllOnFocusGainWidget extends LafWidgetAdapter {
protected JTextComponent textComp;
protected FocusListener focusListener;
/*
* (non-Javadoc)
*
* @see org.jvnet.lafwidget.LafWidgetAdapter#setComponent(javax.swing.JComponent)
*/
public void setComponent(JComponent jcomp) {
super.setComponent(jcomp);
this.textComp = (JTextComponent) jcomp;
}
/*
* (non-Javadoc)
*
* @see org.jvnet.lafwidget.LafWidget#requiresCustomLafSupport()
*/
public boolean requiresCustomLafSupport() {
return false;
}
/*
* (non-Javadoc)
*
* @see org.jvnet.lafwidget.LafWidgetAdapter#installListeners()
*/
public void installListeners() {
this.focusListener = new FocusAdapter() {
public void focusGained(FocusEvent e) {
if (LafWidgetUtilities.hasTextFocusSelectAllProperty(textComp))
textComp.selectAll();
}
};
this.textComp.addFocusListener(this.focusListener);
}
/*
* (non-Javadoc)
*
* @see org.jvnet.lafwidget.LafWidgetAdapter#uninstallListeners()
*/
public void uninstallListeners() {
this.focusListener = null;
this.textComp.removeFocusListener(this.focusListener);
}
}
This simple widget shows the importance of both delegate augmentation and
LAF augmentation explained here.
The installListeners and uninstallListeners of this (and all other
widgets) will be called only if the corresponding look-and-feel UI delegate
will call them (delegate augmentation). If some look-and-feel doesn't have delegate
for some component, the widgets will not be instantiated at all (LAF augmentation).
The org.jvnet.lafwidget.text.LockBorderWidget and
org.jvnet.lafwidget.text.PasswordStrengthCheckerWidget emply border manipulation
in order to achieve desired behaviour. You can see the source code for the relevant life-cycle
methods. Note that unlike the previous example, these two widgets are not completely unaware
of the currently set LAF. Although they do not require custom LAF support, they
consult the currently set LAF support for custom icon / stripe rendering. If not custom
LAF support is set, they revert to the basic implementation.
The org.jvnet.lafwidget.combo.ComboboxAutoCompletionWidget and
org.jvnet.lafwidget.tree.dnd.TreeDragAndDropWidget are two examples
of very complex widgets that require elaborate lifecycle implementation in
order to provide the correct and leak-free runtime behaviour. These widgets
were contributed by Thomas
Bierhance and Antonio
Vieiro and refactored to fit the lifecycle interface. These two widgets are
completely decoupled from the currently set look-and-feel. The
org.jvnet.lafwidget.menu.MenuSearchWidget provides additional complex widget.
Although it doesn't require custom LAF support, it requests the current
support to make some buttons "flat" (no border, no background when inactive). The
default implementation of this support is empty (does nothing).
Two additional widgets in
org.jvnet.lafwidget.tabbed.TabHoverPreviewWidget and
org.jvnet.lafwidget.tabbed.TabOverviewDialogWidget require custom LAF support
as they require access (read / change) to the underlying (LAF-specific)
UI delegate. As such, they are available only in look-and-feels that
register custom LAF support on the LafWidgetRepository.
If you are planning to write your own widget, you should remember the following:
- Unregister everything that you register on the components.
- Bear in mind that the widget is expected to work under all look-and-feels.
- In the configuration file you can use a superclass name
(like javax.swing.text.JTextComponent) in order to make
it more terse. This is relevant only if your widget is applicable
to all descendants of this class.
- Provide support for RTL orientation (if relevant).
- Provide i18n and l10n support.
- Make as few assumptions as possible about the current UI delegate
of the component you're working on. Remember that not all look-and-feels
extend Metal.
- Provide configurable behaviour (so that your widget can be easily
disabled on per-component basis). This is preferably done via client
properties that can be set on either component or UIManager.
- If your widget adds substantial functionality, consider making
it disabled by default. Use client properties to enable them (let the
application decide that it wishes to use your widget). This way you will
not alienate users with unexpected widgets.
- Expose as little API as possible to the application. With no API the
application will be able to run with or without your widget, preventing the
"lock-in".