1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 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.cell; 27 28 import javafx.scene.control.CheckBoxTreeItem; 29 import javafx.beans.property.BooleanProperty; 30 import javafx.beans.property.ObjectProperty; 31 import javafx.beans.property.SimpleObjectProperty; 32 import javafx.beans.value.ObservableValue; 33 import javafx.scene.control.CheckBox; 34 import javafx.scene.control.TreeCell; 35 import javafx.scene.control.TreeItem; 36 import javafx.scene.control.TreeView; 37 import javafx.util.Callback; 38 import javafx.util.StringConverter; 39 40 /** 41 * A class containing a {@link TreeCell} implementation that draws a 42 * {@link CheckBox} node inside the cell, along with support for common 43 * interactions (discussed in more depth shortly). 44 * 45 * <p>To make creating TreeViews with CheckBoxes easier, a convenience class 46 * called {@link CheckBoxTreeItem} is provided. It is <b>highly</b> recommended 47 * that developers use this class, rather than the regular {@link TreeItem} 48 * class, when constructing their TreeView tree structures. Refer to the 49 * CheckBoxTreeItem API documentation for an example on how these two classes 50 * can be combined. 51 * 52 * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox to 53 * the right of the 'disclosure node' (i.e. the arrow). The item stored in 54 * {@link CheckBoxTreeItem#getValue()} will then have the StringConverter called 55 * on it, and this text will take all remaining horizontal space. Additionally, 56 * by using {@link CheckBoxTreeItem}, the TreeView will automatically handle 57 * situations such as: 58 * 59 * <ul> 60 * <li>Clicking on the {@link CheckBox} beside an item that has children will 61 * result in all children also becoming selected/unselected. 62 * <li>Clicking on the {@link CheckBox} beside an item that has a parent will 63 * possibly toggle the state of the parent. For example, if you select a 64 * single child, the parent will become indeterminate (indicating partial 65 * selection of children). If you proceed to select all children, the 66 * parent will then show that it too is selected. This is recursive, with 67 * all parent nodes updating as expected. 68 * </ul> 69 * 70 * If it is decided that using {@link CheckBoxTreeItem} is not desirable, 71 * then it is necessary to call one of the constructors where a {@link Callback} 72 * is provided that can return an {@code ObservableValue<Boolean>} 73 * given a {@link TreeItem} instance. This {@code ObservableValue<Boolean>} 74 * should represent the boolean state of the given {@link TreeItem}. 75 * 76 * <p>Note that the CheckBoxTreeCell renders the CheckBox 'live', meaning that 77 * the CheckBox is always interactive and can be directly toggled by the user. 78 * This means that it is not necessary that the cell enter its 79 * {@link #editingProperty() editing state} (usually by the user double-clicking 80 * on the cell). A side-effect of this is that the usual editing callbacks 81 * (such as {@link javafx.scene.control.TreeView#onEditCommitProperty() on edit commit}) 82 * will <strong>not</strong> be called. If you want to be notified of changes, 83 * it is recommended to directly observe the boolean properties that are 84 * manipulated by the CheckBox.</p> 85 * 86 * @param <T> The type of the elements contained within the TreeView TreeItem 87 * instances. 88 * @since JavaFX 2.2 89 */ 90 public class CheckBoxTreeCell<T> extends DefaultTreeCell<T> { 91 92 /*************************************************************************** 93 * * 94 * Static cell factories * 95 * * 96 **************************************************************************/ 97 98 /** 99 * Creates a cell factory for use in a TreeView control, although there is a 100 * major assumption when used in a TreeView: this cell factory assumes that 101 * the TreeView root, and <b>all</b> children are instances of 102 * {@link CheckBoxTreeItem}, rather than the default {@link TreeItem} class 103 * that is used normally. 104 * 105 * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox 106 * to the right of the 'disclosure node' (i.e. the arrow). The item stored 107 * in {@link CheckBoxTreeItem#getValue()} will then have the StringConverter 108 * called on it, and this text will take all remaining horizontal space. 109 * Additionally, by using {@link CheckBoxTreeItem}, the TreeView will 110 * automatically handle situations such as: 111 * 112 * <ul> 113 * <li>Clicking on the {@link CheckBox} beside an item that has children 114 * will result in all children also becoming selected/unselected.</li> 115 * <li>Clicking on the {@link CheckBox} beside an item that has a parent 116 * will possibly toggle the state of the parent. For example, if you 117 * select a single child, the parent will become indeterminate (indicating 118 * partial selection of children). If you proceed to select all 119 * children, the parent will then show that it too is selected. This is 120 * recursive, with all parent nodes updating as expected.</li> 121 * </ul> 122 * 123 * <p>Unfortunately, due to limitations in Java, it is necessary to provide 124 * an explicit cast when using this method. For example: 125 * 126 * <pre> 127 * {@code 128 * final TreeView<String> treeView = new TreeView<String>(); 129 * treeView.setCellFactory(CheckBoxCell.<String>forTreeView());}</pre> 130 * 131 * @param <T> The type of the elements contained within the 132 * {@link CheckBoxTreeItem} instances. 133 * @return A {@link Callback} that will return a TreeCell that is able to 134 * work on the type of element contained within the TreeView root, and 135 * all of its children (recursively). 136 */ 137 public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView() { 138 Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty = 139 item -> { 140 if (item instanceof CheckBoxTreeItem<?>) { 141 return ((CheckBoxTreeItem<?>)item).selectedProperty(); 142 } 143 return null; 144 }; 145 return forTreeView(getSelectedProperty, 146 CellUtils.<T>defaultTreeItemStringConverter()); 147 } 148 149 /** 150 * Creates a cell factory for use in a TreeView control. Unlike 151 * {@link #forTreeView()}, this method does not assume that all TreeItem 152 * instances in the TreeView are {@link CheckBoxTreeItem} instances. 153 * 154 * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox 155 * to the right of the 'disclosure node' (i.e. the arrow). The item stored 156 * in {@link CheckBoxTreeItem#getValue()} will then have the StringConverter 157 * called on it, and this text will take all remaining horizontal space. 158 * 159 * <p>Unlike {@link #forTreeView()}, this cell factory does not handle 160 * updating the state of parent or children TreeItems - it simply toggles 161 * the {@code ObservableValue<Boolean>} that is provided, and no more. Of 162 * course, this functionality can then be implemented externally by adding 163 * observers to the {@code ObservableValue<Boolean>}, and toggling the state 164 * of other properties as necessary. 165 * 166 * @param <T> The type of the elements contained within the {@link TreeItem} 167 * instances. 168 * @param getSelectedProperty A {@link Callback} that, given an object of 169 * type TreeItem<T>, will return an {@code ObservableValue<Boolean>} 170 * that represents whether the given item is selected or not. This 171 * {@code ObservableValue<Boolean>} will be bound bidirectionally 172 * (meaning that the CheckBox in the cell will set/unset this property 173 * based on user interactions, and the CheckBox will reflect the state 174 * of the {@code ObservableValue<Boolean>}, if it changes externally). 175 * @return A {@link Callback} that will return a TreeCell that is able to 176 * work on the type of element contained within the TreeView root, and 177 * all of its children (recursively). 178 */ 179 public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView( 180 final Callback<TreeItem<T>, 181 ObservableValue<Boolean>> getSelectedProperty) { 182 return forTreeView(getSelectedProperty, CellUtils.<T>defaultTreeItemStringConverter()); 183 } 184 185 /** 186 * Creates a cell factory for use in a TreeView control. Unlike 187 * {@link #forTreeView()}, this method does not assume that all TreeItem 188 * instances in the TreeView are {@link CheckBoxTreeItem}. 189 * 190 * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox 191 * to the right of the 'disclosure node' (i.e. the arrow). The item stored 192 * in {@link TreeItem#getValue()} will then have the the StringConverter 193 * called on it, and this text will take all remaining horizontal space. 194 * 195 * <p>Unlike {@link #forTreeView()}, this cell factory does not handle 196 * updating the state of parent or children TreeItems - it simply toggles 197 * the {@code ObservableValue<Boolean>} that is provided, and no more. Of 198 * course, this functionality can then be implemented externally by adding 199 * observers to the {@code ObservableValue<Boolean>}, and toggling the state 200 * of other properties as necessary. 201 * 202 * @param <T> The type of the elements contained within the {@link TreeItem} 203 * instances. 204 * @param getSelectedProperty A Callback that, given an object of 205 * type TreeItem<T>, will return an {@code ObservableValue<Boolean>} 206 * that represents whether the given item is selected or not. This 207 * {@code ObservableValue<Boolean>} will be bound bidirectionally 208 * (meaning that the CheckBox in the cell will set/unset this property 209 * based on user interactions, and the CheckBox will reflect the state of 210 * the {@code ObservableValue<Boolean>}, if it changes externally). 211 * @param converter A StringConverter that, give an object of type TreeItem<T>, 212 * will return a String that can be used to represent the object 213 * visually. The default implementation in {@link #forTreeView(Callback)} 214 * is to simply call .toString() on all non-null items (and to just 215 * return an empty string in cases where the given item is null). 216 * @return A {@link Callback} that will return a TreeCell that is able to 217 * work on the type of element contained within the TreeView root, and 218 * all of its children (recursively). 219 */ 220 public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView( 221 final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 222 final StringConverter<TreeItem<T>> converter) { 223 return tree -> new CheckBoxTreeCell<T>(getSelectedProperty, converter); 224 } 225 226 227 228 229 /*************************************************************************** 230 * * 231 * Fields * 232 * * 233 **************************************************************************/ 234 private final CheckBox checkBox; 235 236 private ObservableValue<Boolean> booleanProperty; 237 238 private BooleanProperty indeterminateProperty; 239 240 241 242 /*************************************************************************** 243 * * 244 * Constructors * 245 * * 246 **************************************************************************/ 247 248 /** 249 * Creates a default {@link CheckBoxTreeCell} that assumes the TreeView is 250 * constructed with {@link CheckBoxTreeItem} instances, rather than the 251 * default {@link TreeItem}. 252 * By using {@link CheckBoxTreeItem}, it will internally manage the selected 253 * and indeterminate state of each item in the tree. 254 */ 255 public CheckBoxTreeCell() { 256 // getSelectedProperty as anonymous inner class to deal with situation 257 // where the user is using CheckBoxTreeItem instances in their tree 258 this(item -> { 259 if (item instanceof CheckBoxTreeItem<?>) { 260 return ((CheckBoxTreeItem<?>)item).selectedProperty(); 261 } 262 return null; 263 }); 264 } 265 266 /** 267 * Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a 268 * cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this 269 * method does not assume that all TreeItem instances in the TreeView are 270 * {@link CheckBoxTreeItem}. 271 * 272 * <p>To call this method, it is necessary to provide a 273 * {@link Callback} that, given an object of type TreeItem<T>, will return 274 * an {@code ObservableValue<Boolean>} that represents whether the given 275 * item is selected or not. This {@code ObservableValue<Boolean>} will be 276 * bound bidirectionally (meaning that the CheckBox in the cell will 277 * set/unset this property based on user interactions, and the CheckBox will 278 * reflect the state of the {@code ObservableValue<Boolean>}, if it changes 279 * externally). 280 * 281 * <p>If the items are not {@link CheckBoxTreeItem} instances, it becomes 282 * the developers responsibility to handle updating the state of parent and 283 * children TreeItems. This means that, given a TreeItem, this class will 284 * simply toggles the {@code ObservableValue<Boolean>} that is provided, and 285 * no more. Of course, this functionality can then be implemented externally 286 * by adding observers to the {@code ObservableValue<Boolean>}, and toggling 287 * the state of other properties as necessary. 288 * 289 * @param getSelectedProperty A {@link Callback} that will return an 290 * {@code ObservableValue<Boolean>} that represents whether the given 291 * item is selected or not. 292 */ 293 public CheckBoxTreeCell( 294 final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty) { 295 this(getSelectedProperty, CellUtils.<T>defaultTreeItemStringConverter(), null); 296 } 297 298 /** 299 * Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a 300 * cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this 301 * method does not assume that all TreeItem instances in the TreeView are 302 * {@link CheckBoxTreeItem}. 303 * 304 * <p>To call this method, it is necessary to provide a {@link Callback} 305 * that, given an object of type TreeItem<T>, will return an 306 * {@code ObservableValue<Boolean>} that represents whether the given item 307 * is selected or not. This {@code ObservableValue<Boolean>} will be bound 308 * bidirectionally (meaning that the CheckBox in the cell will set/unset 309 * this property based on user interactions, and the CheckBox will reflect 310 * the state of the {@code ObservableValue<Boolean>}, if it changes 311 * externally). 312 * 313 * <p>If the items are not {@link CheckBoxTreeItem} instances, it becomes 314 * the developers responsibility to handle updating the state of parent and 315 * children TreeItems. This means that, given a TreeItem, this class will 316 * simply toggles the {@code ObservableValue<Boolean>} that is provided, and 317 * no more. Of course, this functionality can then be implemented externally 318 * by adding observers to the {@code ObservableValue<Boolean>}, and toggling 319 * the state of other properties as necessary. 320 * 321 * @param getSelectedProperty A {@link Callback} that will return an 322 * {@code ObservableValue<Boolean>} that represents whether the given 323 * item is selected or not. 324 * @param converter A StringConverter that, give an object of type TreeItem<T>, will 325 * return a String that can be used to represent the object visually. 326 */ 327 public CheckBoxTreeCell( 328 final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 329 final StringConverter<TreeItem<T>> converter) { 330 this(getSelectedProperty, converter, null); 331 } 332 333 private CheckBoxTreeCell( 334 final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 335 final StringConverter<TreeItem<T>> converter, 336 final Callback<TreeItem<T>, ObservableValue<Boolean>> getIndeterminateProperty) { 337 this.getStyleClass().add("check-box-tree-cell"); 338 setSelectedStateCallback(getSelectedProperty); 339 setConverter(converter); 340 341 this.checkBox = new CheckBox(); 342 this.checkBox.setAllowIndeterminate(false); 343 344 // by default the graphic is null until the cell stops being empty 345 setGraphic(null); 346 } 347 348 349 350 /*************************************************************************** 351 * * 352 * Properties * 353 * * 354 **************************************************************************/ 355 356 // --- converter 357 private ObjectProperty<StringConverter<TreeItem<T>>> converter = 358 new SimpleObjectProperty<StringConverter<TreeItem<T>>>(this, "converter"); 359 360 /** 361 * The {@link StringConverter} property. 362 */ 363 public final ObjectProperty<StringConverter<TreeItem<T>>> converterProperty() { 364 return converter; 365 } 366 367 /** 368 * Sets the {@link StringConverter} to be used in this cell. 369 */ 370 public final void setConverter(StringConverter<TreeItem<T>> value) { 371 converterProperty().set(value); 372 } 373 374 /** 375 * Returns the {@link StringConverter} used in this cell. 376 */ 377 public final StringConverter<TreeItem<T>> getConverter() { 378 return converterProperty().get(); 379 } 380 381 382 383 // --- selected state callback property 384 private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> 385 selectedStateCallback = 386 new SimpleObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>( 387 this, "selectedStateCallback"); 388 389 /** 390 * Property representing the {@link Callback} that is bound to by the 391 * CheckBox shown on screen. 392 */ 393 public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 394 return selectedStateCallback; 395 } 396 397 /** 398 * Sets the {@link Callback} that is bound to by the CheckBox shown on screen. 399 */ 400 public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) { 401 selectedStateCallbackProperty().set(value); 402 } 403 404 /** 405 * Returns the {@link Callback} that is bound to by the CheckBox shown on screen. 406 */ 407 public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() { 408 return selectedStateCallbackProperty().get(); 409 } 410 411 412 413 /*************************************************************************** 414 * * 415 * Public API * 416 * * 417 **************************************************************************/ 418 419 /** {@inheritDoc} */ 420 @Override public void updateItem(T item, boolean empty) { 421 super.updateItem(item, empty); 422 423 if (empty) { 424 setText(null); 425 setGraphic(null); 426 } else { 427 StringConverter<TreeItem<T>> c = getConverter(); 428 429 TreeItem<T> treeItem = getTreeItem(); 430 431 // update the node content 432 setText(c != null ? c.toString(treeItem) : (treeItem == null ? "" : treeItem.toString())); 433 checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic()); 434 setGraphic(checkBox); 435 436 // uninstall bindings 437 if (booleanProperty != null) { 438 checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); 439 } 440 if (indeterminateProperty != null) { 441 checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty); 442 } 443 444 // install new bindings. 445 // We special case things when the TreeItem is a CheckBoxTreeItem 446 if (treeItem instanceof CheckBoxTreeItem) { 447 CheckBoxTreeItem<T> cbti = (CheckBoxTreeItem<T>) treeItem; 448 booleanProperty = cbti.selectedProperty(); 449 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); 450 451 indeterminateProperty = cbti.indeterminateProperty(); 452 checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty); 453 } else { 454 Callback<TreeItem<T>, ObservableValue<Boolean>> callback = getSelectedStateCallback(); 455 if (callback == null) { 456 throw new NullPointerException( 457 "The CheckBoxTreeCell selectedStateCallbackProperty can not be null"); 458 } 459 460 booleanProperty = callback.call(treeItem); 461 if (booleanProperty != null) { 462 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); 463 } 464 } 465 } 466 } 467 468 @Override void updateDisplay(T item, boolean empty) { 469 // no-op 470 // This was done to resolve RT-33603, but will impact the ability for 471 // TreeItem.graphic to change dynamically. 472 } 473 }