
org.jdesktop.swingbinding.JListBinding Maven / Gradle / Ivy
Show all versions of swixml Show documentation
/*
* Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.swingbinding;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jdesktop.beansbinding.AutoBinding;
import org.jdesktop.beansbinding.ObjectProperty;
import org.jdesktop.beansbinding.Property;
import org.jdesktop.beansbinding.PropertyStateEvent;
import org.jdesktop.beansbinding.PropertyStateListener;
import org.jdesktop.swingbinding.impl.AbstractColumnBinding;
import org.jdesktop.swingbinding.impl.ListBindingManager;
import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;
/**
* Binds a {@code List} of objects to act as the elements of a {@code JList}.
* Each object in the source {@code List} provides one element in the {@code JList}.
* By setting a {@link org.jdesktop.swingbinding.JListBinding.DetailBinding DetailBinding}
* you can specify the property to use to derive each list element from its
* corresponding object in the source {@code List}. The default {@code DetailBinding} uses
* the objects directly. Instances of {@code JListBinding} are obtained by
* calling one of the {@code createJListBinding} methods in the {@code SwingBindings}
* class.
*
* Here is an example of creating a binding from a {@code List} of {@code Person}
* objects to a {@code JList}:
*
*
* // create the person list
* List people = createPersonList();
*
* // create the binding from List to JList
* JListBinding lb = SwingBindings.createJListBinding(READ, people, jList);
*
* // define the property to be used to derive list elements
* ELProperty fullNameP = ELProperty.create("${firstName} ${lastName}");
*
* // add the detail binding
* lb.setDetailBinding(fullNameP);
*
* // realize the binding
* lb.bind();
*
*
* The {@code JList} target of a {@code JListBinding} acts as a live view of
* the objects in the source {@code List}, regardless of the update strategy (the
* meaning of the update strategy is clarified later
* in this document). {@code JListBinding} listens to the property specified for
* any {@code DetailBinding}, for all objects in the {@code List}, and updates
* the values displayed in the {@code JList} in response to change. If the
* {@code List} is an instance of {@code ObservableList}, then changes to the
* {@code List} contents (such as adding, removing or replacing an object) are
* also reflected in the {@code JList}. Important: Changing the contents
* of a non-observable {@code List} while it is participating in a
* {@code JListBinding} is unsupported, resulting in undefined behavior and
* possible exceptions.
*
* {@code JListBinding} requires
* extra clarification on the operation of the
* {@code refresh} and {@code save} methods and the meaning of the update
* strategy. The target property of a {@code JListBinding} is not the
* target {@code JList} property provided in the constructor, but rather a
* private synthetic property representing the {@code List} of objects to show
* in the target {@code JList}. This synthetic property is readable/writeable
* only when the {@code JListBinding} is bound and the target {@code JList}
* property is readable with a {@code non-null} value.
*
* It is this private synthetic property on which the {@code refresh} and
* {@code save} methods operate; meaning that these methods simply cause syncing
* between the value of the source {@code List} property and the value of the
* synthetic target property (representing the {@code List} to be shown in the
* target {@code JList}). These methods do not, therefore, have anything to do
* with refreshing values in the {@code JList}. Likewise, the update
* strategy, which simply controls when {@code refresh} and {@code save} are
* automatically called, also has nothing to do with refreshing values
* in the {@code JList}.
*
* Note: At the current time, the {@code READ_WRITE} update strategy
* is not useful for {@code JListBinding}. To prevent unwanted confusion,
* {@code READ_WRITE} is translated to {@code READ} by {@code JListBinding's}
* constructor.
*
* {@code JListBinding} works by installing a custom model on the target
* {@code JList}, as appropriate, to represent the source {@code List}. The
* model is installed on a target {@code JList} with the first succesful call
* to {@code refresh} with that {@code JList} as the target. Subsequent calls
* to {@code refresh} update the elements in this already-installed model.
* The model is uninstalled from a target {@code JList} when either the
* {@code JListBinding} is unbound or when the target {@code JList} property
* changes to no longer represent that {@code JList}. Note: When the model is
* uninstalled from a {@code JList}, it is replaced with a {@code DefaultListModel},
* in order to leave the {@code JList} functional.
*
* Some of the above is easier to understand with an example. Let's consider
* a {@code JListBinding} ({@code binding}), with update strategy
* {@code READ}, between a property representing a {@code List} ({@code listP})
* and a property representing a {@code JList} ({@code jListP}). {@code listP}
* and {@code jListP} both start off readable, referring to a {@code non-null}
* {@code List} and {@code non-null} {@code JList} respectively. Let's look at
* what happens for each of a sequence of events:
*
*
* Sequence Event Result
*
* 1
* explicit call to {@code binding.bind()}
*
* - synthetic target property becomes readable/writeable
*
* - {@code refresh()} is called
*
* - model is installed on target {@code JList}, representing list of objects
*
*
*
* 2
* {@code listP} changes to a new {@code List}
*
* - {@code refresh()} is called
*
* - model is updated with new list of objects
*
*
*
* 3
* {@code jListP} changes to a new {@code JList}
*
* - model is uninstalled from old {@code JList}
*
*
*
* 4
* explicit call to {@code binding.refresh()}
*
* - model is installed on target {@code JList}, representing list of objects
*
*
*
* 5
* {@code listP} changes to a new {@code List}
*
* - {@code refresh()} is called
*
* - model is updated with new list of objects
*
*
*
* 6
* explicit call to {@code binding.unbind()}
*
* - model is uninstalled from target {@code JList}
*
*
*
*
* Notice that in step 3, when the value
* of the {@code JList} property changed, the new {@code JList} did not
* automatically get the model with the elements applied to it. A change to the
* target value should not cause an {@code AutoBinding} to sync the target from
* the source. Step 4 forces a sync by explicitly calling {@code refresh}.
* Alternatively, it could be caused by any other action that results
* in a {@code refresh} (for example, the source property changing value, or an
* explicit call to {@code unbind} followed by {@code bind}).
*
* {@code DetailBindings} are managed by the {@code JList}. They are not
* to be explicitly bound, unbound, added to a {@code BindingGroup}, or accessed
* in a way that is not allowed for a managed binding.
*
* In addition to binding the elements of a {@code JList}, it is possible to
* bind to the selection of a {@code JList}. When binding to the selection of a {@code JList}
* backed by a {@code JListBinding}, the selection is always in terms of elements
* from the source {@code List}, regardless of any {@code DetailBinding} specified.
* See the list of
* interesting swing properties in the package summary for more details.
*
* @param the type of elements in the source {@code List}
* @param the type of source object (on which the source property resolves to {@code List})
* @param the type of target object (on which the target property resolves to {@code JList})
*
* @author Shannon Hickey
*/
public final class JListBinding extends AutoBinding, TS, List> {
private Property listP;
private ElementsProperty elementsP;
private Handler handler = new Handler();
private JList list;
private BindingListModel model;
private DetailBinding detailBinding;
/**
* Constructs an instance of {@code JListBinding}.
*
* @param strategy the update strategy
* @param sourceObject the source object
* @param sourceListProperty a property on the source object that resolves to the {@code List} of elements
* @param targetObject the target object
* @param targetJListProperty a property on the target object that resolves to a {@code JList}
* @param name a name for the {@code JListBinding}
* @throws IllegalArgumentException if the source property or target property is {@code null}
*/
protected JListBinding(UpdateStrategy strategy, SS sourceObject, Property> sourceListProperty, TS targetObject, Property targetJListProperty, String name) {
super(strategy == READ_WRITE ? READ : strategy,
sourceObject, sourceListProperty, targetObject, new ElementsProperty(), name);
if (targetJListProperty == null) {
throw new IllegalArgumentException("target JList property can't be null");
}
listP = targetJListProperty;
elementsP = (ElementsProperty)getTargetProperty();
setDetailBinding(null);
}
protected void bindImpl() {
elementsP.setAccessible(isListAccessible());
listP.addPropertyStateListener(getTargetObject(), handler);
elementsP.addPropertyStateListener(null, handler);
super.bindImpl();
}
protected void unbindImpl() {
elementsP.removePropertyStateListener(null, handler);
listP.removePropertyStateListener(getTargetObject(), handler);
elementsP.setAccessible(false);
cleanupForLast();
super.unbindImpl();
}
private boolean isListAccessible() {
return listP.isReadable(getTargetObject()) && listP.getValue(getTargetObject()) != null;
}
private boolean isListAccessible(Object value) {
return value != null && value != PropertyStateEvent.UNREADABLE;
}
private void cleanupForLast() {
if (list == null) {
return;
}
resetListSelection();
list.setModel(new DefaultListModel());
list = null;
model.setElements(null, true);
model = null;
}
/**
* Creates a {@code DetailBinding} and sets it as the {@code DetailBinding}
* for this {@code JListBinding}. A {@code DetailBinding} specifies the property
* of the objects in the source {@code List} to be used as the elements of the
* {@code JList}. If the {@code detailProperty} parameter is {@code null}, the
* {@code DetailBinding} specifies that the objects themselves be used.
*
* @param detailProperty the property with which to derive each list value
* from its corresponding object in the source {@code List}
* @return the {@code DetailBinding}
*/
public DetailBinding setDetailBinding(Property detailProperty) {
return setDetailBinding(detailProperty, null);
}
/**
* Creates a named {@code DetailBinding} and sets it as the {@code DetailBinding}
* for this {@code JListBinding}. A {@code DetailBinding} specifies the property
* of the objects in the source {@code List} to be used as the elements of the
* {@code JList}. If the {@code detailProperty} parameter is {@code null}, the
* {@code DetailBinding} specifies that the objects themselves be used.
*
* @param detailProperty the property with which to derive each list value
* from its corresponding object in the source {@code List}
* @return the {@code DetailBinding}
*/
public DetailBinding setDetailBinding(Property detailProperty, String name) {
throwIfBound();
if (name == null && JListBinding.this.getName() != null) {
name = JListBinding.this.getName() + ".DETAIL_BINDING";
}
detailBinding = detailProperty == null ?
new DetailBinding(ObjectProperty.create(), name) :
new DetailBinding(detailProperty, name);
return detailBinding;
}
/**
* Returns the {@code DetailBinding} for this {@code JListBinding}.
* A {@code DetailBinding} specifies the property of the source {@code List} elements
* to be used as the elements of the {@code JList}.
*
* @return the {@code DetailBinding}
* @see #setDetailBinding(Property, String)
*/
public DetailBinding getDetailBinding() {
return detailBinding;
}
private final Property DETAIL_PROPERTY = new Property() {
public Class