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.beans.binding.Bindings; 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.geometry.Pos; 34 import javafx.scene.control.CheckBox; 35 import javafx.scene.control.TableCell; 36 import javafx.scene.control.TableColumn; 37 import javafx.util.Callback; 38 import javafx.util.StringConverter; 39 40 /** 41 * A class containing a {@link TableCell} implementation that draws a 42 * {@link CheckBox} node inside the cell, optionally with a label to indicate 43 * what the checkbox represents. 44 * 45 * <p>By default, the CheckBoxTableCell is rendered with a CheckBox centred in 46 * the TableColumn. If a label is required, it is necessary to provide a 47 * non-null StringConverter instance to the 48 * {@link #CheckBoxTableCell(Callback, StringConverter)} constructor. 49 * 50 * <p>To construct an instance of this class, it is necessary to provide a 51 * {@link Callback} that, given an object of type T, will return an 52 * {@code ObservableProperty<Boolean>} that represents whether the given item is 53 * selected or not. This ObservableValue will be bound bidirectionally (meaning 54 * that the CheckBox in the cell will set/unset this property based on user 55 * interactions, and the CheckBox will reflect the state of the ObservableValue, 56 * if it changes externally). 57 * 58 * <p>Note that the CheckBoxTableCell renders the CheckBox 'live', meaning that 59 * the CheckBox is always interactive and can be directly toggled by the user. 60 * This means that it is not necessary that the cell enter its 61 * {@link #editingProperty() editing state} (usually by the user double-clicking 62 * on the cell). A side-effect of this is that the usual editing callbacks 63 * (such as {@link javafx.scene.control.TableColumn#onEditCommitProperty() on edit commit}) 64 * will <strong>not</strong> be called. If you want to be notified of changes, 65 * it is recommended to directly observe the boolean properties that are 66 * manipulated by the CheckBox.</p> 67 * 68 * @param <T> The type of the elements contained within the TableColumn. 69 * @since JavaFX 2.2 70 */ 71 public class CheckBoxTableCell<S,T> extends TableCell<S,T> { 72 73 /*************************************************************************** 74 * * 75 * Static cell factories * 76 * * 77 **************************************************************************/ 78 79 /** 80 * Creates a cell factory for use in a {@link TableColumn} cell factory. 81 * This method requires that the TableColumn be of type {@link Boolean}. 82 * 83 * <p>When used in a TableColumn, the CheckBoxCell is rendered with a 84 * CheckBox centered in the column. 85 * 86 * <p>The {@code ObservableValue<Boolean>} contained within each cell in the 87 * column will be bound bidirectionally. This means that the CheckBox in 88 * the cell will set/unset this property based on user interactions, and the 89 * CheckBox will reflect the state of the {@code ObservableValue<Boolean>}, 90 * if it changes externally). 91 * 92 * @param <S> The type of the TableView generic type 93 * @param column The TableColumn of type Boolean 94 * @return A {@link Callback} that will return a {@link TableCell} that is 95 * able to work on the type of element contained within the TableColumn. 96 */ 97 public static <S> Callback<TableColumn<S,Boolean>, TableCell<S,Boolean>> forTableColumn( 98 final TableColumn<S, Boolean> column) { 99 return forTableColumn(null, null); 100 } 101 102 /** 103 * Creates a cell factory for use in a {@link TableColumn} cell factory. 104 * This method requires that the TableColumn be of type 105 * {@code ObservableValue<Boolean>}. 106 * 107 * <p>When used in a TableColumn, the CheckBoxCell is rendered with a 108 * CheckBox centered in the column. 109 * 110 * @param <S> The type of the TableView generic type 111 * @param <T> The type of the elements contained within the {@link TableColumn} 112 * instance. 113 * @param getSelectedProperty A Callback that, given an object of 114 * type {@code TableColumn<S,T>}, will return an 115 * {@code ObservableValue<Boolean>} 116 * that represents whether the given item is selected or not. This 117 * {@code ObservableValue<Boolean>} will be bound bidirectionally 118 * (meaning that the CheckBox in the cell will set/unset this property 119 * based on user interactions, and the CheckBox will reflect the state of 120 * the {@code ObservableValue<Boolean>}, if it changes externally). 121 * @return A {@link Callback} that will return a {@link TableCell} that is 122 * able to work on the type of element contained within the TableColumn. 123 */ 124 public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn( 125 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) { 126 return forTableColumn(getSelectedProperty, null); 127 } 128 129 /** 130 * Creates a cell factory for use in a {@link TableColumn} cell factory. 131 * This method requires that the TableColumn be of type 132 * {@code ObservableValue<Boolean>}. 133 * 134 * <p>When used in a TableColumn, the CheckBoxCell is rendered with a 135 * CheckBox centered in the column. 136 * 137 * @param <S> The type of the TableView generic type 138 * @param <T> The type of the elements contained within the {@link TableColumn} 139 * instance. 140 * @param getSelectedProperty A Callback that, given an object of 141 * type {@code TableColumn<S,T>}, will return an 142 * {@code ObservableValue<Boolean>} 143 * that represents whether the given item is selected or not. This 144 * {@code ObservableValue<Boolean>} will be bound bidirectionally 145 * (meaning that the CheckBox in the cell will set/unset this property 146 * based on user interactions, and the CheckBox will reflect the state of 147 * the {@code ObservableValue<Boolean>}, if it changes externally). 148 * @param showLabel In some cases, it may be desirable to show a label in 149 * the TableCell beside the {@link CheckBox}. By default a label is not 150 * shown, but by setting this to true the item in the cell will also 151 * have toString() called on it. If this is not the desired behavior, 152 * consider using 153 * {@link #forTableColumn(javafx.util.Callback, javafx.util.StringConverter) }, 154 * which allows for you to provide a callback that specifies the label for a 155 * given row item. 156 * @return A {@link Callback} that will return a {@link TableCell} that is 157 * able to work on the type of element contained within the TableColumn. 158 */ 159 public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn( 160 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 161 final boolean showLabel) { 162 StringConverter<T> converter = ! showLabel ? 163 null : CellUtils.<T>defaultStringConverter(); 164 return forTableColumn(getSelectedProperty, converter); 165 } 166 167 /** 168 * Creates a cell factory for use in a {@link TableColumn} cell factory. 169 * This method requires that the TableColumn be of type 170 * {@code ObservableValue<Boolean>}. 171 * 172 * <p>When used in a TableColumn, the CheckBoxCell is rendered with a 173 * CheckBox centered in the column. 174 * 175 * @param <S> The type of the TableView generic type 176 * @param <T> The type of the elements contained within the {@link TableColumn} 177 * instance. 178 * @param getSelectedProperty A Callback that, given an object of type 179 * {@code TableColumn<S,T>}, will return an 180 * {@code ObservableValue<Boolean>} that represents whether the given 181 * item is selected or not. This {@code ObservableValue<Boolean>} will 182 * be bound bidirectionally (meaning that the CheckBox in the cell will 183 * set/unset this property based on user interactions, and the CheckBox 184 * will reflect the state of the {@code ObservableValue<Boolean>}, if 185 * it changes externally). 186 * @param converter A StringConverter that, give an object of type T, will return a 187 * String that can be used to represent the object visually. The default 188 * implementation in {@link #forTableColumn(Callback, boolean)} (when 189 * showLabel is true) is to simply call .toString() on all non-null 190 * items (and to just return an empty string in cases where the given 191 * item is null). 192 * @return A {@link Callback} that will return a {@link TableCell} that is 193 * able to work on the type of element contained within the TableColumn. 194 */ 195 public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn( 196 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 197 final StringConverter<T> converter) { 198 return list -> new CheckBoxTableCell<S,T>(getSelectedProperty, converter); 199 } 200 201 202 203 /*************************************************************************** 204 * * 205 * Fields * 206 * * 207 **************************************************************************/ 208 private final CheckBox checkBox; 209 210 private boolean showLabel; 211 212 private ObservableValue<Boolean> booleanProperty; 213 214 215 216 /*************************************************************************** 217 * * 218 * Constructors * 219 * * 220 **************************************************************************/ 221 222 /** 223 * Creates a default CheckBoxTableCell. 224 */ 225 public CheckBoxTableCell() { 226 this(null, null); 227 } 228 229 /** 230 * Creates a default CheckBoxTableCell with a custom {@link Callback} to 231 * retrieve an ObservableValue for a given cell index. 232 * 233 * @param getSelectedProperty A {@link Callback} that will return an {@link 234 * ObservableValue} given an index from the TableColumn. 235 */ 236 public CheckBoxTableCell( 237 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) { 238 this(getSelectedProperty, null); 239 } 240 241 /** 242 * Creates a CheckBoxTableCell with a custom string converter. 243 * 244 * @param getSelectedProperty A {@link Callback} that will return a {@link 245 * ObservableValue} given an index from the TableColumn. 246 * @param converter A StringConverter that, given an object of type T, will return a 247 * String that can be used to represent the object visually. 248 */ 249 public CheckBoxTableCell( 250 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 251 final StringConverter<T> converter) { 252 // we let getSelectedProperty be null here, as we can always defer to the 253 // TableColumn 254 this.getStyleClass().add("check-box-table-cell"); 255 256 this.checkBox = new CheckBox(); 257 258 // by default the graphic is null until the cell stops being empty 259 setGraphic(null); 260 261 setSelectedStateCallback(getSelectedProperty); 262 setConverter(converter); 263 264 // // alignment is styleable through css. Calling setAlignment 265 // // makes it look to css like the user set the value and css will not 266 // // override. Initializing alignment by calling set on the 267 // // CssMetaData ensures that css will be able to override the value. 268 // final CssMetaData prop = CssMetaData.getCssMetaData(alignmentProperty()); 269 // prop.set(this, Pos.CENTER); 270 271 272 } 273 274 275 /*************************************************************************** 276 * * 277 * Properties * 278 * * 279 **************************************************************************/ 280 281 // --- converter 282 private ObjectProperty<StringConverter<T>> converter = 283 new SimpleObjectProperty<StringConverter<T>>(this, "converter") { 284 protected void invalidated() { 285 updateShowLabel(); 286 } 287 }; 288 289 /** 290 * The {@link StringConverter} property. 291 * @return the {@link StringConverter} property 292 */ 293 public final ObjectProperty<StringConverter<T>> converterProperty() { 294 return converter; 295 } 296 297 /** 298 * Sets the {@link StringConverter} to be used in this cell. 299 * @param value the {@link StringConverter} to be used in this cell 300 */ 301 public final void setConverter(StringConverter<T> value) { 302 converterProperty().set(value); 303 } 304 305 /** 306 * Returns the {@link StringConverter} used in this cell. 307 * @return the {@link StringConverter} used in this cell 308 */ 309 public final StringConverter<T> getConverter() { 310 return converterProperty().get(); 311 } 312 313 314 315 // --- selected state callback property 316 private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> 317 selectedStateCallback = 318 new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>( 319 this, "selectedStateCallback"); 320 321 /** 322 * Property representing the {@link Callback} that is bound to by the 323 * CheckBox shown on screen. 324 * @return the property representing the {@link Callback} that is bound to 325 * by the CheckBox shown on screen 326 */ 327 public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 328 return selectedStateCallback; 329 } 330 331 /** 332 * Sets the {@link Callback} that is bound to by the CheckBox shown on screen. 333 * @param value the {@link Callback} that is bound to by the CheckBox shown 334 * on screen 335 */ 336 public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) { 337 selectedStateCallbackProperty().set(value); 338 } 339 340 /** 341 * Returns the {@link Callback} that is bound to by the CheckBox shown on screen. 342 * @return the {@link Callback} that is bound to by the CheckBox shown on screen 343 */ 344 public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() { 345 return selectedStateCallbackProperty().get(); 346 } 347 348 349 350 /*************************************************************************** 351 * * 352 * Public API * 353 * * 354 **************************************************************************/ 355 356 /** {@inheritDoc} */ 357 @SuppressWarnings("unchecked") 358 @Override public void updateItem(T item, boolean empty) { 359 super.updateItem(item, empty); 360 361 if (empty) { 362 setText(null); 363 setGraphic(null); 364 } else { 365 StringConverter<T> c = getConverter(); 366 367 if (showLabel) { 368 setText(c.toString(item)); 369 } 370 setGraphic(checkBox); 371 372 if (booleanProperty instanceof BooleanProperty) { 373 checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); 374 } 375 ObservableValue<?> obsValue = getSelectedProperty(); 376 if (obsValue instanceof BooleanProperty) { 377 booleanProperty = (ObservableValue<Boolean>) obsValue; 378 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); 379 } 380 381 checkBox.disableProperty().bind(Bindings.not( 382 getTableView().editableProperty().and( 383 getTableColumn().editableProperty()).and( 384 editableProperty()) 385 )); 386 } 387 } 388 389 390 391 /*************************************************************************** 392 * * 393 * Private implementation * 394 * * 395 **************************************************************************/ 396 397 private void updateShowLabel() { 398 this.showLabel = converter != null; 399 this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER); 400 } 401 402 private ObservableValue<?> getSelectedProperty() { 403 return getSelectedStateCallback() != null ? 404 getSelectedStateCallback().call(getIndex()) : 405 getTableColumn().getCellObservableValue(getIndex()); 406 } 407 }