modules/controls/src/main/java/javafx/scene/control/skin/ChoiceBoxSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.skin;
  27 


  28 import javafx.beans.WeakInvalidationListener;





  29 import javafx.util.StringConverter;
  30 import javafx.beans.InvalidationListener;
  31 import javafx.collections.ListChangeListener;
  32 import javafx.collections.ObservableList;
  33 import javafx.geometry.HPos;
  34 import javafx.geometry.Side;
  35 import javafx.geometry.VPos;
  36 import javafx.scene.control.ChoiceBox;
  37 import javafx.scene.control.ContextMenu;
  38 import javafx.scene.control.Label;
  39 import javafx.scene.control.MenuItem;
  40 import javafx.scene.control.RadioMenuItem;
  41 import javafx.scene.control.SelectionModel;
  42 import javafx.scene.control.Separator;
  43 import javafx.scene.control.SeparatorMenuItem;
  44 import javafx.scene.control.ToggleGroup;
  45 import javafx.scene.layout.StackPane;
  46 import javafx.scene.text.Text;
  47 
  48 import com.sun.javafx.scene.control.behavior.ChoiceBoxBehavior;
  49 import javafx.collections.WeakListChangeListener;
  50 
  51 
  52 /**
  53  * ChoiceBoxSkin - default implementation



  54  */
  55 public class ChoiceBoxSkin<T> extends BehaviorSkinBase<ChoiceBox<T>, ChoiceBoxBehavior<T>> {
  56 
  57     public ChoiceBoxSkin(ChoiceBox<T> control) {
  58         super(control, new ChoiceBoxBehavior<T>(control));
  59         initialize();
  60 
  61         itemsObserver = observable -> updateChoiceBoxItems();
  62         control.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
  63 
  64         control.requestLayout();
  65         registerChangeListener(control.selectionModelProperty(), "SELECTION_MODEL");
  66         registerChangeListener(control.showingProperty(), "SHOWING");
  67         registerChangeListener(control.itemsProperty(), "ITEMS");
  68         registerChangeListener(control.getSelectionModel().selectedItemProperty(), "SELECTION_CHANGED");
  69         registerChangeListener(control.converterProperty(), "CONVERTER");
  70     }
  71 
  72     private ObservableList<T> choiceBoxItems;
  73 
  74     private ContextMenu popup;
  75 
  76     // The region that shows the "arrow" box portion
  77     private StackPane openButton;
  78 
  79     private final ToggleGroup toggleGroup = new ToggleGroup();
  80 
  81     /*
  82      * Watch for if the user changes the selected index, and if so, we toggle
  83      * the selection in the toggle group (so the check shows in the right place)
  84      */
  85     private SelectionModel<T> selectionModel;
  86 
  87     private Label label;
  88 










  89     private final ListChangeListener<T> choiceBoxItemsListener = new ListChangeListener<T>() {
  90         @Override public void onChanged(Change<? extends T> c) {
  91             while (c.next()) {
  92                 if (c.getRemovedSize() > 0 || c.wasPermutated()) {
  93                     toggleGroup.getToggles().clear();
  94                     popup.getItems().clear();
  95                     int i = 0;
  96                     for (T obj : c.getList()) {
  97                         addPopupItem(obj, i);
  98                         i++;
  99                     }
 100                 } else {
 101                     for (int i = c.getFrom(); i < c.getTo(); i++) {
 102                         final T obj = c.getList().get(i);
 103                         addPopupItem(obj, i);
 104                     }
 105                 }
 106             }
 107             updateSelection();
 108             getSkinnable().requestLayout(); // RT-18052 resize of choicebox should happen immediately.
 109         }
 110     };
 111     
 112     private final WeakListChangeListener<T> weakChoiceBoxItemsListener =
 113             new WeakListChangeListener<T>(choiceBoxItemsListener);
 114 
 115     private final InvalidationListener itemsObserver;
 116 
















































































































































































 117     private void initialize() {
 118         updateChoiceBoxItems();
 119 
 120         label = new Label();
 121         label.setMnemonicParsing(false);  // ChoiceBox doesn't do Mnemonics
 122 
 123         openButton = new StackPane();
 124         openButton.getStyleClass().setAll("open-button");
 125 
 126         StackPane region = new StackPane();
 127         region.getStyleClass().setAll("arrow");
 128         openButton.getChildren().clear();
 129         openButton.getChildren().addAll(region);
 130 
 131         popup = new ContextMenu();
 132         // When popup is hidden by autohide - the ChoiceBox Showing property needs
 133         // to be updated. So we listen to when autohide happens. Calling hide()
 134         // there after causes Showing to be set to false
 135         popup.showingProperty().addListener((o, ov, nv) -> {
 136             if (!nv) {


 161         if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
 162             label.setText(""); // clear label text when selectedIndex is -1
 163         }
 164     }
 165 
 166     private void updateChoiceBoxItems() {
 167         if (choiceBoxItems != null) {
 168             choiceBoxItems.removeListener(weakChoiceBoxItemsListener);
 169         }
 170         choiceBoxItems = getSkinnable().getItems();
 171         if (choiceBoxItems != null) {
 172             choiceBoxItems.addListener(weakChoiceBoxItemsListener);
 173         }
 174     }
 175     
 176     // Test only purpose    
 177     String getChoiceBoxSelectedText() {
 178         return label.getText();
 179     }
 180 
 181     @SuppressWarnings("rawtypes")
 182     @Override protected void handleControlPropertyChanged(String p) {
 183         super.handleControlPropertyChanged(p);
 184         if ("ITEMS".equals(p)) {
 185             updateChoiceBoxItems();
 186             updatePopupItems();
 187             updateSelectionModel();
 188             updateSelection();
 189             if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
 190                 label.setText(""); // clear label text when selectedIndex is -1
 191             }
 192         } else if (("SELECTION_MODEL").equals(p)) {
 193             updateSelectionModel();
 194         } else if ("SELECTION_CHANGED".equals(p)) {
 195             if (getSkinnable().getSelectionModel() != null) {
 196                 int index = getSkinnable().getSelectionModel().getSelectedIndex();
 197                 if (index != -1) {
 198                     MenuItem item = popup.getItems().get(index);
 199                     if (item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
 200                 }
 201             }
 202         } else if ("SHOWING".equals(p)) {
 203             if (getSkinnable().isShowing()) {
 204                 MenuItem item = null;
 205 
 206                 SelectionModel sm = getSkinnable().getSelectionModel();
 207                 if (sm == null) return;
 208 
 209                 long currentSelectedIndex = sm.getSelectedIndex();
 210                 int itemInControlCount = choiceBoxItems.size();
 211                 boolean hasSelection = currentSelectedIndex >= 0 && currentSelectedIndex < itemInControlCount;
 212                 if (hasSelection) {
 213                     item = popup.getItems().get((int) currentSelectedIndex);
 214                     if (item != null && item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
 215                 } else {
 216                     if (itemInControlCount > 0) item = popup.getItems().get(0);
 217                 }
 218 
 219                 // This is a fix for RT-9071. Ideally this won't be necessary in
 220                 // the long-run, but for now at least this resolves the
 221                 // positioning
 222                 // problem of ChoiceBox inside a Cell.
 223                 getSkinnable().autosize();
 224                 // -- End of RT-9071 fix
 225 
 226                 double y = 0;
 227 
 228                 if (popup.getSkin() != null) {
 229                     ContextMenuContent cmContent = (ContextMenuContent)popup.getSkin().getNode();
 230                     if (cmContent != null && currentSelectedIndex != -1) {
 231                         y = -(cmContent.getMenuYOffset((int)currentSelectedIndex));
 232                     }
 233                 }
 234 
 235                 popup.show(getSkinnable(), Side.BOTTOM, 2, y);
 236             } else {
 237                 popup.hide();
 238             }
 239         } else if ("CONVERTER".equals(p)) {
 240             updateChoiceBoxItems();
 241             updatePopupItems();
 242         }
 243     }
 244 
 245     private void addPopupItem(final T o, int i) {
 246         MenuItem popupItem = null;
 247         if (o instanceof Separator) {
 248             // We translate the Separator into a SeparatorMenuItem...
 249             popupItem = new SeparatorMenuItem();
 250         } else if (o instanceof SeparatorMenuItem) {
 251             popupItem = (SeparatorMenuItem) o;
 252         } else {
 253             StringConverter<T> c = getSkinnable().getConverter();
 254             String displayString = (c == null) ? ((o == null) ? "" : o.toString()) :  c.toString(o);
 255             final RadioMenuItem item = new RadioMenuItem(displayString);
 256             item.setId("choice-box-menu-item");
 257             item.setToggleGroup(toggleGroup);
 258             item.setOnAction(e -> {
 259                 if (selectionModel == null) return;
 260                 int index = getSkinnable().getItems().indexOf(o);
 261                 selectionModel.select(index);
 262                 item.setSelected(true);
 263             });
 264             popupItem = item;


 295     private void updateSelection() {
 296         if (selectionModel == null || selectionModel.isEmpty()) {
 297             toggleGroup.selectToggle(null);
 298             label.setText("");
 299         } else {
 300             int selectedIndex = selectionModel.getSelectedIndex();
 301             if (selectedIndex == -1 || selectedIndex > popup.getItems().size()) {
 302                 label.setText(""); // clear label text
 303                 return;
 304             }
 305             if (selectedIndex < popup.getItems().size()) {
 306                 MenuItem selectedItem = popup.getItems().get(selectedIndex);
 307                 if (selectedItem instanceof RadioMenuItem) {
 308                     ((RadioMenuItem) selectedItem).setSelected(true);
 309                     toggleGroup.selectToggle(null);
 310                 }
 311                 // update the label
 312                 label.setText(popup.getItems().get(selectedIndex).getText());
 313             }
 314         }
 315     }
 316 
 317     @Override protected void layoutChildren(final double x, final double y,
 318             final double w, final double h) {
 319         // open button width/height
 320         double obw = openButton.prefWidth(-1);
 321 
 322         ChoiceBox<T> control = getSkinnable();
 323         label.resizeRelocate(x, y, w, h);
 324         openButton.resize(obw, openButton.prefHeight(-1));
 325         positionInArea(openButton, (x+w) - obw,
 326                 y, obw, h, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
 327     }
 328 
 329     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 330         final double boxWidth = label.minWidth(-1) + openButton.minWidth(-1);
 331         final double popupWidth = popup.minWidth(-1);
 332         return leftInset + Math.max(boxWidth, popupWidth) + rightInset;
 333     }
 334 
 335     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 336         final double displayHeight = label.minHeight(-1);
 337         final double openButtonHeight = openButton.minHeight(-1);
 338         return topInset + Math.max(displayHeight, openButtonHeight) + bottomInset;
 339     }
 340 
 341     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 342         final double boxWidth = label.prefWidth(-1)
 343                 + openButton.prefWidth(-1);
 344         double popupWidth = popup.prefWidth(-1);
 345         if (popupWidth <= 0) { // first time: when the popup has not shown yet
 346             if (popup.getItems().size() > 0){
 347                 popupWidth = (new Text(((MenuItem)popup.getItems().get(0)).getText())).prefWidth(-1);
 348             }
 349         }
 350         return (popup.getItems().size() == 0) ? 50 : leftInset + Math.max(boxWidth, popupWidth)
 351                 + rightInset;
 352     }
 353 
 354     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 355         final double displayHeight = label.prefHeight(-1);
 356         final double openButtonHeight = openButton.prefHeight(-1);
 357         return topInset
 358                 + Math.max(displayHeight, openButtonHeight)
 359                 + bottomInset;
 360     }
 361     
 362     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 363         return getSkinnable().prefHeight(width);
 364     }
 365     
 366     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 367         return getSkinnable().prefWidth(height);
 368     }
 369 }


   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.scene.control.ContextMenuContent;
  29 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  30 import javafx.beans.WeakInvalidationListener;
  31 import javafx.scene.Node;
  32 import javafx.scene.control.Accordion;
  33 import javafx.scene.control.Button;
  34 import javafx.scene.control.Control;
  35 import javafx.scene.control.SkinBase;
  36 import javafx.util.StringConverter;
  37 import javafx.beans.InvalidationListener;
  38 import javafx.collections.ListChangeListener;
  39 import javafx.collections.ObservableList;
  40 import javafx.geometry.HPos;
  41 import javafx.geometry.Side;
  42 import javafx.geometry.VPos;
  43 import javafx.scene.control.ChoiceBox;
  44 import javafx.scene.control.ContextMenu;
  45 import javafx.scene.control.Label;
  46 import javafx.scene.control.MenuItem;
  47 import javafx.scene.control.RadioMenuItem;
  48 import javafx.scene.control.SelectionModel;
  49 import javafx.scene.control.Separator;
  50 import javafx.scene.control.SeparatorMenuItem;
  51 import javafx.scene.control.ToggleGroup;
  52 import javafx.scene.layout.StackPane;
  53 import javafx.scene.text.Text;
  54 
  55 import com.sun.javafx.scene.control.behavior.ChoiceBoxBehavior;
  56 import javafx.collections.WeakListChangeListener;
  57 
  58 
  59 /**
  60  * Default skin implementation for the {@link ChoiceBox} control.
  61  *
  62  * @see ChoiceBox
  63  * @since 9
  64  */
  65 public class ChoiceBoxSkin<T> extends SkinBase<ChoiceBox<T>> {
  66 
  67     /***************************************************************************
  68      *                                                                         *
  69      * Private fields                                                          *
  70      *                                                                         *
  71      **************************************************************************/









  72 
  73     private ObservableList<T> choiceBoxItems;
  74 
  75     private ContextMenu popup;
  76 
  77     // The region that shows the "arrow" box portion
  78     private StackPane openButton;
  79 
  80     private final ToggleGroup toggleGroup = new ToggleGroup();
  81 
  82     /*
  83      * Watch for if the user changes the selected index, and if so, we toggle
  84      * the selection in the toggle group (so the check shows in the right place)
  85      */
  86     private SelectionModel<T> selectionModel;
  87 
  88     private Label label;
  89 
  90     private final BehaviorBase<ChoiceBox<T>> behavior;
  91 
  92 
  93 
  94     /***************************************************************************
  95      *                                                                         *
  96      * Listeners                                                               *
  97      *                                                                         *
  98      **************************************************************************/
  99 
 100     private final ListChangeListener<T> choiceBoxItemsListener = new ListChangeListener<T>() {
 101         @Override public void onChanged(Change<? extends T> c) {
 102             while (c.next()) {
 103                 if (c.getRemovedSize() > 0 || c.wasPermutated()) {
 104                     toggleGroup.getToggles().clear();
 105                     popup.getItems().clear();
 106                     int i = 0;
 107                     for (T obj : c.getList()) {
 108                         addPopupItem(obj, i);
 109                         i++;
 110                     }
 111                 } else {
 112                     for (int i = c.getFrom(); i < c.getTo(); i++) {
 113                         final T obj = c.getList().get(i);
 114                         addPopupItem(obj, i);
 115                     }
 116                 }
 117             }
 118             updateSelection();
 119             getSkinnable().requestLayout(); // RT-18052 resize of choicebox should happen immediately.
 120         }
 121     };
 122 
 123     private final WeakListChangeListener<T> weakChoiceBoxItemsListener =
 124             new WeakListChangeListener<T>(choiceBoxItemsListener);
 125 
 126     private final InvalidationListener itemsObserver;
 127 
 128 
 129 
 130     /***************************************************************************
 131      *                                                                         *
 132      * Constructors                                                            *
 133      *                                                                         *
 134      **************************************************************************/
 135 
 136     /**
 137      * Creates a new ChoiceBoxSkin instance, installing the necessary child
 138      * nodes into the Control {@link Control#getChildren() children} list, as
 139      * well as the necessary input mappings for handling key, mouse, etc events.
 140      *
 141      * @param control The control that this skin should be installed onto.
 142      */
 143     public ChoiceBoxSkin(ChoiceBox<T> control) {
 144         super(control);
 145 
 146         // install default input map for the ChoiceBox control
 147         behavior = new ChoiceBoxBehavior<>(control);
 148 //        control.setInputMap(behavior.getInputMap());
 149 
 150         initialize();
 151 
 152         itemsObserver = observable -> updateChoiceBoxItems();
 153         control.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
 154 
 155         control.requestLayout();
 156         registerChangeListener(control.selectionModelProperty(), e -> updateSelectionModel());
 157         registerChangeListener(control.showingProperty(), e -> {
 158             if (getSkinnable().isShowing()) {
 159                 MenuItem item = null;
 160 
 161                 SelectionModel sm = getSkinnable().getSelectionModel();
 162                 if (sm == null) return;
 163 
 164                 long currentSelectedIndex = sm.getSelectedIndex();
 165                 int itemInControlCount = choiceBoxItems.size();
 166                 boolean hasSelection = currentSelectedIndex >= 0 && currentSelectedIndex < itemInControlCount;
 167                 if (hasSelection) {
 168                     item = popup.getItems().get((int) currentSelectedIndex);
 169                     if (item != null && item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
 170                 } else {
 171                     if (itemInControlCount > 0) item = popup.getItems().get(0);
 172                 }
 173 
 174                 // This is a fix for RT-9071. Ideally this won't be necessary in
 175                 // the long-run, but for now at least this resolves the
 176                 // positioning
 177                 // problem of ChoiceBox inside a Cell.
 178                 getSkinnable().autosize();
 179                 // -- End of RT-9071 fix
 180 
 181                 double y = 0;
 182 
 183                 if (popup.getSkin() != null) {
 184                     ContextMenuContent cmContent = (ContextMenuContent)popup.getSkin().getNode();
 185                     if (cmContent != null && currentSelectedIndex != -1) {
 186                         y = -(cmContent.getMenuYOffset((int)currentSelectedIndex));
 187                     }
 188                 }
 189 
 190                 popup.show(getSkinnable(), Side.BOTTOM, 2, y);
 191             } else {
 192                 popup.hide();
 193             }
 194         });
 195         registerChangeListener(control.itemsProperty(), e -> {
 196             updateChoiceBoxItems();
 197             updatePopupItems();
 198             updateSelectionModel();
 199             updateSelection();
 200             if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
 201                 label.setText(""); // clear label text when selectedIndex is -1
 202             }
 203         });
 204         registerChangeListener(control.getSelectionModel().selectedItemProperty(), e -> {
 205             if (getSkinnable().getSelectionModel() != null) {
 206                 int index = getSkinnable().getSelectionModel().getSelectedIndex();
 207                 if (index != -1) {
 208                     MenuItem item = popup.getItems().get(index);
 209                     if (item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
 210                 }
 211             }
 212         });
 213         registerChangeListener(control.converterProperty(), e -> {
 214             updateChoiceBoxItems();
 215             updatePopupItems();
 216         });
 217     }
 218 
 219 
 220 
 221     /***************************************************************************
 222      *                                                                         *
 223      * Public API                                                              *
 224      *                                                                         *
 225      **************************************************************************/
 226 
 227     /** {@inheritDoc} */
 228     @Override public void dispose() {
 229         super.dispose();
 230 
 231         if (behavior != null) {
 232             behavior.dispose();
 233         }
 234     }
 235 
 236     /** {@inheritDoc} */
 237     @Override protected void layoutChildren(final double x, final double y,
 238                                             final double w, final double h) {
 239         // open button width/height
 240         double obw = openButton.prefWidth(-1);
 241 
 242         ChoiceBox<T> control = getSkinnable();
 243         label.resizeRelocate(x, y, w, h);
 244         openButton.resize(obw, openButton.prefHeight(-1));
 245         positionInArea(openButton, (x+w) - obw,
 246                 y, obw, h, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
 247     }
 248 
 249     /** {@inheritDoc} */
 250     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 251         final double boxWidth = label.minWidth(-1) + openButton.minWidth(-1);
 252         final double popupWidth = popup.minWidth(-1);
 253         return leftInset + Math.max(boxWidth, popupWidth) + rightInset;
 254     }
 255 
 256     /** {@inheritDoc} */
 257     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 258         final double displayHeight = label.minHeight(-1);
 259         final double openButtonHeight = openButton.minHeight(-1);
 260         return topInset + Math.max(displayHeight, openButtonHeight) + bottomInset;
 261     }
 262 
 263     /** {@inheritDoc} */
 264     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 265         final double boxWidth = label.prefWidth(-1)
 266                 + openButton.prefWidth(-1);
 267         double popupWidth = popup.prefWidth(-1);
 268         if (popupWidth <= 0) { // first time: when the popup has not shown yet
 269             if (popup.getItems().size() > 0){
 270                 popupWidth = (new Text(((MenuItem)popup.getItems().get(0)).getText())).prefWidth(-1);
 271             }
 272         }
 273         return (popup.getItems().size() == 0) ? 50 : leftInset + Math.max(boxWidth, popupWidth)
 274                 + rightInset;
 275     }
 276 
 277     /** {@inheritDoc} */
 278     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 279         final double displayHeight = label.prefHeight(-1);
 280         final double openButtonHeight = openButton.prefHeight(-1);
 281         return topInset
 282                 + Math.max(displayHeight, openButtonHeight)
 283                 + bottomInset;
 284     }
 285 
 286     /** {@inheritDoc} */
 287     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 288         return getSkinnable().prefHeight(width);
 289     }
 290 
 291     /** {@inheritDoc} */
 292     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 293         return getSkinnable().prefWidth(height);
 294     }
 295 
 296 
 297 
 298     /***************************************************************************
 299      *                                                                         *
 300      * Private implementation                                                  *
 301      *                                                                         *
 302      **************************************************************************/
 303 
 304     private void initialize() {
 305         updateChoiceBoxItems();
 306 
 307         label = new Label();
 308         label.setMnemonicParsing(false);  // ChoiceBox doesn't do Mnemonics
 309 
 310         openButton = new StackPane();
 311         openButton.getStyleClass().setAll("open-button");
 312 
 313         StackPane region = new StackPane();
 314         region.getStyleClass().setAll("arrow");
 315         openButton.getChildren().clear();
 316         openButton.getChildren().addAll(region);
 317 
 318         popup = new ContextMenu();
 319         // When popup is hidden by autohide - the ChoiceBox Showing property needs
 320         // to be updated. So we listen to when autohide happens. Calling hide()
 321         // there after causes Showing to be set to false
 322         popup.showingProperty().addListener((o, ov, nv) -> {
 323             if (!nv) {


 348         if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
 349             label.setText(""); // clear label text when selectedIndex is -1
 350         }
 351     }
 352 
 353     private void updateChoiceBoxItems() {
 354         if (choiceBoxItems != null) {
 355             choiceBoxItems.removeListener(weakChoiceBoxItemsListener);
 356         }
 357         choiceBoxItems = getSkinnable().getItems();
 358         if (choiceBoxItems != null) {
 359             choiceBoxItems.addListener(weakChoiceBoxItemsListener);
 360         }
 361     }
 362     
 363     // Test only purpose    
 364     String getChoiceBoxSelectedText() {
 365         return label.getText();
 366     }
 367 
































































 368     private void addPopupItem(final T o, int i) {
 369         MenuItem popupItem = null;
 370         if (o instanceof Separator) {
 371             // We translate the Separator into a SeparatorMenuItem...
 372             popupItem = new SeparatorMenuItem();
 373         } else if (o instanceof SeparatorMenuItem) {
 374             popupItem = (SeparatorMenuItem) o;
 375         } else {
 376             StringConverter<T> c = getSkinnable().getConverter();
 377             String displayString = (c == null) ? ((o == null) ? "" : o.toString()) :  c.toString(o);
 378             final RadioMenuItem item = new RadioMenuItem(displayString);
 379             item.setId("choice-box-menu-item");
 380             item.setToggleGroup(toggleGroup);
 381             item.setOnAction(e -> {
 382                 if (selectionModel == null) return;
 383                 int index = getSkinnable().getItems().indexOf(o);
 384                 selectionModel.select(index);
 385                 item.setSelected(true);
 386             });
 387             popupItem = item;


 418     private void updateSelection() {
 419         if (selectionModel == null || selectionModel.isEmpty()) {
 420             toggleGroup.selectToggle(null);
 421             label.setText("");
 422         } else {
 423             int selectedIndex = selectionModel.getSelectedIndex();
 424             if (selectedIndex == -1 || selectedIndex > popup.getItems().size()) {
 425                 label.setText(""); // clear label text
 426                 return;
 427             }
 428             if (selectedIndex < popup.getItems().size()) {
 429                 MenuItem selectedItem = popup.getItems().get(selectedIndex);
 430                 if (selectedItem instanceof RadioMenuItem) {
 431                     ((RadioMenuItem) selectedItem).setSelected(true);
 432                     toggleGroup.selectToggle(null);
 433                 }
 434                 // update the label
 435                 label.setText(popup.getItems().get(selectedIndex).getText());
 436             }
 437         }





















































 438     }
 439 }