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

javafx.scene.control.ChoiceBox Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.control;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.event.ActionEvent;
import javafx.util.StringConverter;

import javafx.css.PseudoClass;
import com.sun.javafx.scene.control.skin.ChoiceBoxSkin;
import javafx.beans.DefaultProperty;

/**
 * The ChoiceBox is used for presenting the user with a relatively small set of
 * predefined choices from which they may choose. The ChoiceBox, when "showing",
 * will display to the user these choices and allow them to pick exactly one
 * choice. When not showing, the current choice is displayed.
 * 

* The ChoiceBox can be configured either to support null as a * valid choice, or to prohibit it. In the case that it is prohibited, there * will always be some item that is selected, as long as there is at least * one item defined. By default, no item is selected unless * otherwise specified. In the case that null is acceptable, * a default entry may be inserted into the list of choices at the top, * with a name similar to "None" and localized for different Locales. *

* Although the ChoiceBox will only allow a user to select from the predefined * list, it is possible for the developer to specify the selected item to be * something other than what is available in the predefined list. This is * required for several important use cases. *

* It means configuration of the ChoiceBox is order independent. You * may either specify the items and then the selected item, or you may * specify the selected item and then the items. Either way will function * correctly. *

* ChoiceBox item selection is handled by * {@link javafx.scene.control.SelectionModel SelectionModel} * As with ListView and ComboBox, it is possible to modify the * {@link javafx.scene.control.SelectionModel SelectionModel} that is used, * although this is likely to be rarely changed. ChoiceBox supports only a * single selection model, hence the default used is a {@link SingleSelectionModel}. * *

 * import javafx.scene.control.ChoiceBox;
 *
 * ChoiceBox cb = new ChoiceBox();
 * cb.getItems().addAll("item1", "item2", "item3");
 * 
* @since JavaFX 2.0 */ @DefaultProperty("items") public class ChoiceBox extends Control { /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Create a new ChoiceBox which has an empty list of items. */ public ChoiceBox() { this(FXCollections.observableArrayList()); } /** * Create a new ChoiceBox with the given set of items. Since it is observable, * the content of this list may change over time and the ChoiceBox will * be updated accordingly. * @param items */ public ChoiceBox(ObservableList items) { getStyleClass().setAll("choice-box"); setItems(items); setSelectionModel(new ChoiceBoxSelectionModel(this)); // listen to the value property, if the value is // set to something that exists in the items list, update the // selection model to indicate that this is the selected item valueProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, T t, T t1) { if (getItems() == null) return; int index = getItems().indexOf(t1); if (index > -1) { getSelectionModel().select(index); } } }); } /*************************************************************************** * * * Properties * * * **************************************************************************/ /** * The selection model for the ChoiceBox. Only a single choice can be made, * hence, the ChoiceBox supports only a SingleSelectionModel. Generally, the * main interaction with the selection model is to explicitly set which item * in the items list should be selected, or to listen to changes in the * selection to know which item has been chosen. */ private ObjectProperty> selectionModel = new SimpleObjectProperty>(this, "selectionModel") { private SelectionModel oldSM = null; @Override protected void invalidated() { if (oldSM != null) { oldSM.selectedItemProperty().removeListener(selectedItemListener); } SelectionModel sm = get(); oldSM = sm; if (sm != null) { sm.selectedItemProperty().addListener(selectedItemListener); } } }; private ChangeListener selectedItemListener = new ChangeListener() { @Override public void changed(ObservableValue ov, T t, T t1) { if (! valueProperty().isBound()) { setValue(t1); } } }; public final void setSelectionModel(SingleSelectionModel value) { selectionModel.set(value); } public final SingleSelectionModel getSelectionModel() { return selectionModel.get(); } public final ObjectProperty> selectionModelProperty() { return selectionModel; } /** * Indicates whether the drop down is displaying the list of choices to the * user. This is a readonly property which should be manipulated by means of * the #show and #hide methods. */ private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() { @Override protected void invalidated() { pseudoClassStateChanged(SHOWING_PSEUDOCLASS_STATE, get()); } @Override public Object getBean() { return ChoiceBox.this; } @Override public String getName() { return "showing"; } }; public final boolean isShowing() { return showing.get(); } public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); } /** * The items to display in the choice box. The selected item (as indicated in the * selection model) must always be one of these items. */ private ObjectProperty> items = new ObjectPropertyBase>() { ObservableList old; @Override protected void invalidated() { final ObservableList newItems = get(); if (old != newItems) { // Add and remove listeners if (old != null) old.removeListener(itemsListener); if (newItems != null) newItems.addListener(itemsListener); // Clear the selection model final SingleSelectionModel sm = getSelectionModel(); if (sm != null) { if (newItems != null && newItems.isEmpty()) { sm.setSelectedIndex(-1); } else if (sm.getSelectedIndex() == -1 && sm.getSelectedItem() != null) { int newIndex = getItems().indexOf(sm.getSelectedItem()); if (newIndex != -1) { sm.setSelectedIndex(newIndex); } } else sm.clearSelection(); } // if (sm != null) sm.setSelectedIndex(-1); // Save off the old items old = newItems; } } @Override public Object getBean() { return ChoiceBox.this; } @Override public String getName() { return "items"; } }; public final void setItems(ObservableList value) { items.set(value); } public final ObservableList getItems() { return items.get(); } public final ObjectProperty> itemsProperty() { return items; } private final ListChangeListener itemsListener = new ListChangeListener() { @Override public void onChanged(Change c) { final SingleSelectionModel sm = getSelectionModel(); if (sm!= null) { if (getItems() == null || getItems().isEmpty()) { sm.clearSelection(); } else { int newIndex = getItems().indexOf(sm.getSelectedItem()); sm.setSelectedIndex(newIndex); } } if (sm != null) { // Look for the selected item as having been removed. If it has been, // then we need to clear the selection in the selection model. final T selectedItem = sm.getSelectedItem(); while (c.next()) { if (selectedItem != null && c.getRemoved().contains(selectedItem)) { sm.clearSelection(); break; } } } } }; /** * Allows a way to specify how to represent objects in the items list. When * a StringConverter is set, the object toString method is not called and * instead its toString(object T) is called, passing the objects in the items list. * This is useful when using domain objects in a ChoiceBox as this property * allows for customization of the representation. Also, any of the pre-built * Converters available in the {@link javafx.util.converter} package can be set. * @since JavaFX 2.1 */ public ObjectProperty> converterProperty() { return converter; } private ObjectProperty> converter = new SimpleObjectProperty>(this, "converter", null); public final void setConverter(StringConverter value) { converterProperty().set(value); } public final StringConverter getConverter() {return converterProperty().get(); } /** * The value of this ChoiceBox is defined as the selected item in the ChoiceBox * selection model. The valueProperty is synchronized with the selectedItem. * This property allows for bi-directional binding of external properties to the * ChoiceBox and updates the selection model accordingly. * @since JavaFX 2.1 */ public ObjectProperty valueProperty() { return value; } private ObjectProperty value = new SimpleObjectProperty(this, "value") { @Override protected void invalidated() { super.invalidated(); fireEvent(new ActionEvent()); // Update selection final SingleSelectionModel sm = getSelectionModel(); if (sm != null) { sm.select(super.getValue()); } } }; public final void setValue(T value) { valueProperty().set(value); } public final T getValue() { return valueProperty().get(); } /*************************************************************************** * * * Methods * * * **************************************************************************/ /** * Opens the list of choices. */ public void show() { if (!isDisabled()) showing.set(true); } /** * Closes the list of choices. */ public void hide() { showing.set(false); } /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new ChoiceBoxSkin(this); } /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ private static final PseudoClass SHOWING_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("showing"); // package for testing static class ChoiceBoxSelectionModel extends SingleSelectionModel { private final ChoiceBox choiceBox; public ChoiceBoxSelectionModel(final ChoiceBox cb) { if (cb == null) { throw new NullPointerException("ChoiceBox can not be null"); } this.choiceBox = cb; /* * The following two listeners are used in conjunction with * SelectionModel.select(T obj) to allow for a developer to select * an item that is not actually in the data model. When this occurs, * we actively try to find an index that matches this object, going * so far as to actually watch for all changes to the items list, * rechecking each time. */ // watching for changes to the items list content final ListChangeListener itemsContentObserver = new ListChangeListener() { @Override public void onChanged(Change c) { if (choiceBox.getItems() == null || choiceBox.getItems().isEmpty()) { setSelectedIndex(-1); } else if (getSelectedIndex() == -1 && getSelectedItem() != null) { int newIndex = choiceBox.getItems().indexOf(getSelectedItem()); if (newIndex != -1) { setSelectedIndex(newIndex); } } } }; if (this.choiceBox.getItems() != null) { this.choiceBox.getItems().addListener(itemsContentObserver); } // watching for changes to the items list ChangeListener> itemsObserver = new ChangeListener>() { @Override public void changed(ObservableValue> valueModel, ObservableList oldList, ObservableList newList) { if (oldList != null) { oldList.removeListener(itemsContentObserver); } if (newList != null) { newList.addListener(itemsContentObserver); } setSelectedIndex(-1); if (getSelectedItem() != null) { int newIndex = choiceBox.getItems().indexOf(getSelectedItem()); if (newIndex != -1) { setSelectedIndex(newIndex); } } } }; this.choiceBox.itemsProperty().addListener(itemsObserver); } // API Implementation @Override protected T getModelItem(int index) { final ObservableList items = choiceBox.getItems(); if (items == null) return null; if (index < 0 || index >= items.size()) return null; return items.get(index); } @Override protected int getItemCount() { final ObservableList items = choiceBox.getItems(); return items == null ? 0 : items.size(); } /** * Selects the given row. Since the SingleSelectionModel can only support having * a single row selected at a time, this also causes any previously selected * row to be unselected. * This method is overridden here so that we can move past a Separator * in a ChoiceBox and select the next valid menuitem. */ @Override public void select(int index) { // this does not sound right, we should let the superclass handle it. final T value = getModelItem(index); if (value instanceof Separator) { select(++index); } else { super.select(index); } if (choiceBox.isShowing()) { choiceBox.hide(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy