All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.swingbinding.JComboBoxBinding Maven / Gradle / Ivy

The newest version!
/*
 * 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.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 items of a {@code JComboBox}.
 * Each object in the source {@code List} is an item in the {@code JComboBox}.
 * Instances of {@code JComboBoxBinding} are obtained by calling one of the
 * {@code createJComboBoxBinding} methods in the {@code SwingBindings} class.
 * 

* Here is an example of creating a binding from a {@code List} of {@code Country} * objects to a {@code JComboBox}: *

*


 *    // create the country list
 *    List countries = createCountryList();
 *
 *    // create the binding from List to JComboBox
 *    JComboBoxBinding cb = SwingBindings.createJComboBoxBinding(READ, countries, jComboBox);
 *
 *    // realize the binding
 *    cb.bind();
 * 
*

* 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 reflected in the {@code JComboBox}. Important: Changing the contents * of a non-observable {@code List} while it is participating in a * {@code JComboBoxBinding} is unsupported, resulting in undefined behavior and * possible exceptions. *

* {@code JComboBoxBinding} 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 JComboBoxBinding} is not the * target {@code JComboBox} property provided in the constructor, but rather a * private synthetic property representing the {@code List} of objects to show * in the target {@code JComboBox}. This synthetic property is readable/writeable * only when the {@code JComboBoxBinding} is bound and the target {@code JComboBox} * 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 JComboBox}). These methods do not, therefore, have anything to do * with refreshing values in the {@code JComboBox}. 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 JComboBox}. *

* Note: At the current time, the {@code READ_WRITE} update strategy * is not useful for {@code JComboBoxBinding}. To prevent unwanted confusion, * {@code READ_WRITE} is translated to {@code READ} by {@code JComboBoxBinding's} * constructor. *

* {@code JComboBoxBinding} works by installing a custom model on the target * {@code JComboBox}, as appropriate, to represent the source {@code List}. The * model is installed on a target {@code JComboBox} with the first succesful call * to {@code refresh} with that {@code JComboBox} as the target. Subsequent calls * to {@code refresh} update the elements in this already-installed model. * The model is uninstalled from a target {@code JComboBox} when either the * {@code JComboBoxBinding} is unbound or when the target {@code JComboBox} property * changes to no longer represent that {@code JComboBox}. Note: When the model is * uninstalled from a {@code JComboBox}, it is replaced with a {@code DefaultComboBoxModel}, * in order to leave the {@code JComboBox} functional. *

* Some of the above is easier to understand with an example. Let's consider * a {@code JComboBoxBinding} ({@code binding}), with update strategy * {@code READ}, between a property representing a {@code List} ({@code listP}) * and a property representing a {@code JComboBox} ({@code jComboBoxP}). {@code listP} * and {@code jComboBoxP} both start off readable, referring to a {@code non-null} * {@code List} and {@code non-null} {@code JComboBox} respectively. Let's look at * what happens for each of a sequence of events: *

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SequenceEventResult
1explicit call to {@code binding.bind()} * - synthetic target property becomes readable/writeable *
* - {@code refresh()} is called *
* - model is installed on target {@code JComboBox}, 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 jComboBoxP} changes to a new {@code JComboBox} * - model is uninstalled from old {@code JComboBox} *
4explicit call to {@code binding.refresh()} * - model is installed on target {@code JComboBox}, 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 *
6explicit call to {@code binding.unbind()} * - model is uninstalled from target {@code JComboBox} *
*

* Notice that in step 3, when the value * of the {@code JComboBox} property changed, the new {@code JComboBox} 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}). *

* In addition to binding the items of a {@code JComboBox}, it is possible to * bind to the selected item of a {@code JComboBox}. * 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 JComboBox}) * * @author Shannon Hickey */ public final class JComboBoxBinding extends AutoBinding, TS, List> { private Property comboP; private ElementsProperty elementsP; private Handler handler = new Handler(); private JComboBox combo; private BindingComboBoxModel model; /** * Constructs an instance of {@code JComboBoxBinding}. * * @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 targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox} * @param name a name for the {@code JComboBoxBinding} * @throws IllegalArgumentException if the source property or target property is {@code null} */ protected JComboBoxBinding(UpdateStrategy strategy, SS sourceObject, Property> sourceListProperty, TS targetObject, Property targetJComboBoxProperty, String name) { super(strategy == READ_WRITE ? READ : strategy, sourceObject, sourceListProperty, targetObject, new ElementsProperty(), name); if (targetJComboBoxProperty == null) { throw new IllegalArgumentException("target JComboBox property can't be null"); } comboP = targetJComboBoxProperty; elementsP = (ElementsProperty)getTargetProperty(); } protected void bindImpl() { elementsP.setAccessible(isComboAccessible()); comboP.addPropertyStateListener(getTargetObject(), handler); elementsP.addPropertyStateListener(null, handler); super.bindImpl(); } protected void unbindImpl() { elementsP.removePropertyStateListener(null, handler); comboP.removePropertyStateListener(getTargetObject(), handler); elementsP.setAccessible(false); cleanupForLast(); super.unbindImpl(); } private boolean isComboAccessible() { return comboP.isReadable(getTargetObject()) && comboP.getValue(getTargetObject()) != null; } private boolean isComboAccessible(Object value) { return value != null && value != PropertyStateEvent.UNREADABLE; } private void cleanupForLast() { if (combo == null) { return; } combo.setSelectedItem(null); combo.setModel(new DefaultComboBoxModel()); model.updateElements(null, combo.isEditable()); combo = null; model = null; } private class Handler implements PropertyStateListener { public void propertyStateChanged(PropertyStateEvent pse) { if (!pse.getValueChanged()) { return; } if (pse.getSourceProperty() == comboP) { cleanupForLast(); boolean wasAccessible = isComboAccessible(pse.getOldValue()); boolean isAccessible = isComboAccessible(pse.getNewValue()); if (wasAccessible != isAccessible) { elementsP.setAccessible(isAccessible); } else if (elementsP.isAccessible()) { elementsP.setValueAndIgnore(null, null); } } else { if (((ElementsProperty.ElementsPropertyStateEvent)pse).shouldIgnore()) { return; } if (combo == null) { combo = comboP.getValue(getTargetObject()); combo.setSelectedItem(null); model = new BindingComboBoxModel(); combo.setModel(model); } model.updateElements((List)pse.getNewValue(), combo.isEditable()); } } } private final class BindingComboBoxModel extends ListBindingManager implements ComboBoxModel { private final List listeners; private Object selectedItem = null; private int selectedModelIndex = -1; public BindingComboBoxModel() { listeners = new CopyOnWriteArrayList(); } public void updateElements(List elements, boolean isEditable) { setElements(elements, false); if (!isEditable || selectedModelIndex != -1) { selectedItem = null; selectedModelIndex = -1; } if (size() <= 0) { if (selectedModelIndex != -1) { selectedModelIndex = -1; selectedItem = null; } } else { if (selectedItem == null) { selectedModelIndex = 0; selectedItem = getElementAt(selectedModelIndex); } } allChanged(); } protected AbstractColumnBinding[] getColBindings() { return new AbstractColumnBinding[0]; } public Object getSelectedItem() { return selectedItem; } public void setSelectedItem(Object item) { // This is what DefaultComboBoxModel does (yes, yuck!) if ((selectedItem != null && !selectedItem.equals(item)) || selectedItem == null && item != null) { selectedItem = item; contentsChanged(-1, -1); selectedModelIndex = -1; if (item != null) { int size = size(); for (int i = 0; i < size; i++) { if (item.equals(getElementAt(i))) { selectedModelIndex = i; break; } } } } } protected void allChanged() { contentsChanged(0, size()); } protected void valueChanged(int row, int column) { // we're not expecting any value changes since we don't have any // detail bindings for JComboBox } protected void added(int index, int length) { assert length > 0; // enforced by ListBindingManager ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index + length - 1); int size = listeners.size(); for (int i = size - 1; i >= 0; i--) { listeners.get(i).intervalAdded(e); } if (size() == length && selectedItem == null) { setSelectedItem(getElementAt(0)); } } protected void removed(int index, int length) { assert length > 0; // enforced by ListBindingManager ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index + length - 1); int size = listeners.size(); for (int i = size - 1; i >= 0; i--) { listeners.get(i).intervalRemoved(e); } if (selectedModelIndex >= index && selectedModelIndex < index + length) { if (size() == 0) { setSelectedItem(null); } else { setSelectedItem(getElementAt(Math.max(index - 1, 0))); } } } protected void changed(int row) { contentsChanged(row, row); } private void contentsChanged(int row0, int row1) { ListDataEvent e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, row0, row1); int size = listeners.size(); for (int i = size - 1; i >= 0; i--) { listeners.get(i).contentsChanged(e); } } public Object getElementAt(int index) { return getElement(index); } public void addListDataListener(ListDataListener l) { listeners.add(l); } public void removeListDataListener(ListDataListener l) { listeners.remove(l); } public int getSize() { return size(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy