:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Tasks ::

Adapting Domain Properties

If the domain object properties are implemented as Java Bean properties, they can be converted to ValueModels using a PresentationModel, BeanAdapter, or PropertyAdapter. Typically you'll use a PresentationModel, in some cases the simpler BeanAdapter, and only if you want to convert a single property, you may consider using a PropertyAdapter.

Observing and Firing Property Changes

In most cases we want to automatically update the view of a bound domain property changes. More generally, we want to update a view if a bound value changes. Therefore we must be able to register with the bound value to get notified about changes. That's the second role of the ValueModels - besides reading and writing values.

The Java Bean standard describes a standard mechanism to report changes in property values and to listen to these changes, or in other words, to observe values. To make Bean properties bound, the bean class must provide a pair of methods to add and remove a PropertyChangeListener. We use the same mechanism and exactly the same events for all ValueModels in the JGoodies Binding.

To minimize the effort required to make your bean properties bound, you can extend the class Model that provides all the methods necessary to fire events and register listeners.

Converting Values to Views

To present a value with a Swing component, you can either set the value in the component, or you can provide a component model that will be used by the component to read and write values from/to. If possible we favor the latter approach and therefore offer a bunch of adapter classes that convert ValueModels into the model interface required by the different Swing components. For example, the RadioButtonAdapter is a model that converts a ValueModel so it can be used with a JRadioButton. And the DocumentAdapter converts a ValueModel into a Document implementation, for use in JTextField, JTextArea and all other JTextComponents.

The BasicComponentFactory can create Swing components that are bound to a ValueModel; the factory chooses the appropriate adapter. If you already have a component factory, or if you are using customized versions of the standard Swing components, you can use the more basic Bindings class. It establishes the connection between a ValueModel and a given Swing component by setting the appropriate ValueModel adapter as the component's model.

Buffering

A BufferedValueModel is used to hold a temporary copy of the value in another ValueModel (known as the subject). The application modifies the temporary copy, but the BufferedValueModel only gives this temporary value to its subject when the application confirms the changes. The application also has the option of canceling the changes, resetting the temporary copy to the subject's value.

For example, suppose the application provides a series of text fields for entering customer name, address, phone, etc., but we only want the Customer object to be updated after the user has finished entering data and has indicated completion by clicking on an OK button. This technique is often used in database applications, to postpone updating the customer record in the database until all changes to that record are completed. In this application, the customer's old address would likely be held by an PropertyAdapter on the Customer bean. The property adapter would become the subject of a BufferedValueModel. The BufferedValueModel would make a temporary copy of the customer's address and make that value available to the input field for editing. The user could change the address, but so far only the temporary copy has been altered. Only when the users clicks on 'OK' does the application notify each field's BufferedValueModel to replace the corresponding value in the Customer object.

A BufferedValueModel is constructed for given ValueModels for the subject and trigger channel. The subject is a ValueModel containing the data value. The trigger channel is a ValueHolder containing the boolean object false. Later, when the user clicks on 'OK', the application can cause the temporary copy to become the subject's value by setting the triggerChannel's value to true. The application can also cancel any edits, by setting the triggerChannel's value to false. Note that the prior value in the triggerChannel is not significant - as long as a value change is reported.

By using the same trigger channel for all of the BufferedValueModels, the application can cause them all to be updated at the same time. This is the usual arrangement for a set of related widgets.

Change Management

You may want to detect whether the user has changed an object in an editor to enable or disable a 'Save' action or an 'OK' button. Therefore you can compare the edited object's old and new values, or listen to object property changes, or observe the buffering state of BufferedValueModels - if you use any.

Comparing old and new values is the most precise approach to detect whether things changed or not. But it requires to duplicate the edited values and compare them later. You can copy (or clone) your domain objects and compare them after each change using a custom #equals implementation.

The classes PresentationModel and BeanAdapter provide a means to detect potential changes. They listen to all bean property changes and keep this state in a bound property changed. You can read, write and observe this state, for example to enable or disable Actions.

If you are buffering the edited values, you can listen to the BufferedValueModels buffering property to detect pending changes.

Indirection

Indirection is used to change the target object of a bound object property from one target to another. For example, if you display a list of albums, and present the selection in a details panel, you want to change the target object whenever the selection changes. In other words, the details components are bound to the property of an object, the indirection tells the binding which object it is.

If you are using PresentationModel, BeanAdapter or PropertyAdapter to convert Bean properties to ValueModels, you can set the current target bean in these adapters using #setBean(Object).

If multiple ValueModels shall be redirected from one target to another, you'll typically use a bean channel. That is another ValueModel that holds the target bean. And every interested party can observe changes in the bean channel to change its target. The PresentationModel, BeanAdapter and PropertyAdapter already provide constructors to use a bean channel.

Type Conversion

The ValueModel interface operates on general Object instances. If you want to convert types, you can wrap a ValueModel and perform the conversion while you get and set a value. In addition you must convert the old and new value in all PropertyChangeEvents fired by the wrapping ValueModel. Class AbstractConverter minimizes the effort required for such a conversion.

Conversions among ValueModels are rare, just because most conversions are required only for the presentation in the user interface. The latter can often be handled using the JFormattedTextField that provides a powerful means to format, parse, verify, and modify objects. See also the JavaDoc class comment in AbstractConverter.

Renaming Domain Properties

If you use Java Bean properties in the domain layer, there are no direct references from the model or presentation layer to the domain. To access properties the property's name and the accessor names must by synchronized. This makes renaming properties a little bit harder.

I recommend to use String constants, not Strings, for the property names. Then refer to these constants when getting ValueModels from a PresentationModel or when adding and removing listeners. Renaming a property is then a 2-step process: you rename 1) the getter and setter and 2) the property name constant.

Note that this renaming problem applies to code obfuscation too. Most code obfuscators rename the property accessor names, but leave the property name unchanged. Attempts to observe the property or to create an adapting ValueModel by the property name will then fail. Either you exclude the Bean accessors from the obfuscation, or you use ValueHolders in the domain. I personally favor Beans and assume that most accessor names do not expose the domain logic to be hidden by the obfuscator.

(c) 2008 JGoodies