Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import javafx.scene.control.cell.CheckBoxTreeCell; 029import javafx.beans.property.BooleanProperty; 030import javafx.beans.property.SimpleBooleanProperty; 031import javafx.beans.value.ChangeListener; 032import javafx.beans.value.ObservableValue; 033import javafx.event.Event; 034import javafx.event.EventHandler; 035import javafx.event.EventType; 036import javafx.scene.Node; 037 038/** 039 * TreeItem subclass that adds support for being in selected, unselected, and 040 * indeterminate states. This is useful when used in conjunction with a TreeView 041 * which has a {@link CheckBoxTreeCell} installed. 042 * 043 * <p>A CheckBoxTreeItem can be {@link #independentProperty() independent} or 044 * dependent. By default, CheckBoxTreeItem instances are dependent, which means 045 * that any changes to the selection state of a TreeItem will have an impact on 046 * parent and children CheckBoxTreeItem instances. If a CheckBoxTreeItem is 047 * set to be independent, this means that any changes to that CheckBoxTreeItem 048 * will not directly impact the state of parent and children CheckBoxTreeItem 049 * instances. 050 * 051 * <p>The {@link #indeterminateProperty() indeterminate} property is used to 052 * represent the same concept as that in {@link CheckBox#indeterminateProperty()}, 053 * namely, that the CheckBox is neither selected or unselected. This is commonly 054 * used inside a TreeView when some, but not all, of a branches children are 055 * selected. 056 * 057 * <p>A simple example of using the CheckBoxTreeItem class, in conjunction with 058 * {@link CheckBoxTreeCell} is shown below: 059 * 060 * <pre><code> 061 * // create the tree model 062 * CheckBoxTreeItem<String> jonathanGiles = new CheckBoxTreeItem<String>("Jonathan"); 063 * CheckBoxTreeItem<String> juliaGiles = new CheckBoxTreeItem<String>("Julia"); 064 * CheckBoxTreeItem<String> mattGiles = new CheckBoxTreeItem<String>("Matt"); 065 * CheckBoxTreeItem<String> sueGiles = new CheckBoxTreeItem<String>("Sue"); 066 * CheckBoxTreeItem<String> ianGiles = new CheckBoxTreeItem<String>("Ian"); 067 * 068 * CheckBoxTreeItem<String> gilesFamily = new CheckBoxTreeItem<String>("Giles Family"); 069 * gilesFamily.setExpanded(true); 070 * gilesFamily.getChildren().addAll(jonathanGiles, juliaGiles, mattGiles, sueGiles, ianGiles); 071 * 072 * // create the treeView 073 * final TreeView<String> treeView = new TreeView<String>(); 074 * treeView.setRoot(gilesFamily); 075 * 076 * // set the cell factory 077 * treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());</code></pre> 078 * 079 * @see CheckBoxTreeCell 080 * @see TreeItem 081 * @see CheckBox 082 * @since 2.2 083 */ 084// TODO the TreeModificationEvent doesn't actually bubble in the same way as 085// TreeItem - it just looks that way as the 'bubbling' is done via changing the 086// state of all parent items. 087public class CheckBoxTreeItem<T> extends TreeItem<T> { 088 089 /** 090 * An EventType used when the CheckBoxTreeItem selection / indeterminate 091 * state changes. To use this, it is recommended that you use code along the 092 * lines of the following: 093 * 094 *<pre> 095 * {@code 096 * child1.addEventHandler(CheckBoxTreeItem.<String>checkBoxSelectionChangedEvent(), new EventHandler<TreeModificationEvent<String>>() { 097 * public void handle(TreeModificationEvent<String> event) { 098 * ... 099 * } 100 * });} 101 * </pre> 102 * 103 * @param <T> The type of the value contained within the TreeItem. 104 */ 105 @SuppressWarnings("unchecked") 106 public static <T> EventType<TreeModificationEvent<T>> checkBoxSelectionChangedEvent() { 107 return (EventType<TreeModificationEvent<T>>) CHECK_BOX_SELECTION_CHANGED_EVENT; 108 } 109 private static final EventType<? extends Event> CHECK_BOX_SELECTION_CHANGED_EVENT 110 = new EventType<Event>(TreeModificationEvent.ANY, "checkBoxSelectionChangedEvent"); 111 112 /*************************************************************************** 113 * * 114 * Constructors * 115 * * 116 **************************************************************************/ 117 118 /** 119 * Creates an empty CheckBoxTreeItem. 120 */ 121 public CheckBoxTreeItem() { 122 this(null); 123 } 124 125 /** 126 * Creates a CheckBoxTreeItem with the value property set to the provided 127 * object. 128 * 129 * @param value The object to be stored as the value of this TreeItem. 130 */ 131 public CheckBoxTreeItem(T value) { 132 this(value, null, false); 133 } 134 135 /** 136 * Creates a CheckBoxTreeItem with the value property set to the provided 137 * object, and the graphic set to the provided Node. 138 * 139 * @param value The object to be stored as the value of this CheckBoxTreeItem. 140 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 141 */ 142 public CheckBoxTreeItem(T value, Node graphic) { 143 this(value, graphic, false); 144 } 145 146 /** 147 * Creates a CheckBoxTreeItem with the value property set to the provided 148 * object, the graphic set to the provided Node, and the initial state 149 * of the {@link #selectedProperty()} set to the provided boolean value. 150 * 151 * @param value The object to be stored as the value of this CheckBoxTreeItem. 152 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 153 * @param selected The initial value of the 154 * {@link #selectedProperty() selected} property. 155 */ 156 public CheckBoxTreeItem(T value, Node graphic, boolean selected) { 157 this(value, graphic, selected, false); 158 } 159 160 /** 161 * Creates a CheckBoxTreeItem with the value property set to the provided 162 * object, the graphic set to the provided Node, the initial state 163 * of the {@link #selectedProperty()} set to the provided boolean value, and 164 * the initial state of the {@link #independentProperty() independent} 165 * property to the provided boolean value. 166 * 167 * @param value The object to be stored as the value of this CheckBoxTreeItem. 168 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 169 * @param selected The initial value of the 170 * {@link #selectedProperty() selected} property. 171 * @param independent The initial value of the 172 * {@link #independentProperty() independent} property 173 */ 174 public CheckBoxTreeItem(T value, Node graphic, boolean selected, boolean independent) { 175 super(value, graphic); 176 setSelected(selected); 177 setIndependent(independent); 178 179 selectedProperty().addListener(stateChangeListener); 180 indeterminateProperty().addListener(stateChangeListener); 181 } 182 183 184 185 /*************************************************************************** 186 * * 187 * Callbacks * 188 * * 189 **************************************************************************/ 190 private final ChangeListener<Boolean> stateChangeListener = new ChangeListener<Boolean>() { 191 @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean oldVal, Boolean newVal) { 192 updateState(); 193 } 194 }; 195 196 197 /*************************************************************************** 198 * * 199 * Properties * 200 * * 201 **************************************************************************/ 202 203 // --- Selected 204 private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false) { 205 @Override protected void invalidated() { 206 super.invalidated(); 207 fireEvent(CheckBoxTreeItem.this, true); 208 } 209 }; 210 /** Sets the selected state of this CheckBoxTreeItem. */ 211 public final void setSelected(boolean value) { selectedProperty().setValue(value); } 212 /** Returns the selected state of this CheckBoxTreeItem. */ 213 public final boolean isSelected() { return selected.getValue(); } 214 /** A {@link BooleanProperty} used to represent the selected state of this CheckBoxTreeItem. */ 215 public final BooleanProperty selectedProperty() { return selected; } 216 217 218 // --- Indeterminate 219 private final BooleanProperty indeterminate = new SimpleBooleanProperty(this, "indeterminate", false) { 220 @Override protected void invalidated() { 221 super.invalidated(); 222 fireEvent(CheckBoxTreeItem.this, false); 223 } 224 }; 225 /** Sets the indeterminate state of this CheckBoxTreeItem. */ 226 public final void setIndeterminate(boolean value) { indeterminateProperty().setValue(value); } 227 /** Returns the indeterminate state of this CheckBoxTreeItem. */ 228 public final boolean isIndeterminate() { return indeterminate.getValue(); } 229 /** A {@link BooleanProperty} used to represent the indeterminate state of this CheckBoxTreeItem. */ 230 public final BooleanProperty indeterminateProperty() { return indeterminate; } 231 232 233 // --- Independent 234 /** 235 * A {@link BooleanProperty} used to represent the independent state of this CheckBoxTreeItem. 236 * The independent state is used to represent whether changes to a single 237 * CheckBoxTreeItem should influence the state of its parent and children. 238 * 239 * <p>By default, the independent property is false, which means that when 240 * a CheckBoxTreeItem has state changes to the selected or indeterminate 241 * properties, the state of related CheckBoxTreeItems will possibly be changed. 242 * If the independent property is set to true, the state of related CheckBoxTreeItems 243 * will <b>never</b> change. 244 */ 245 public final BooleanProperty independentProperty() { return independent; } 246 private final BooleanProperty independent = new SimpleBooleanProperty(this, "independent", false); 247 public final void setIndependent(boolean value) { independentProperty().setValue(value); } 248 public final boolean isIndependent() { return independent.getValue(); } 249 250 251 252 /*************************************************************************** 253 * * 254 * Private Implementation * 255 * * 256 **************************************************************************/ 257 258 private static boolean updateLock = false; 259 260 private void updateState() { 261 if (isIndependent()) return; 262 263 boolean firstLock = ! updateLock; 264 265 // toggle parent (recursively up to root) 266 updateLock = true; 267 updateUpwards(); 268 269 if (firstLock) updateLock = false; 270 271 // toggle children 272 if (updateLock) return; 273 updateDownwards(); 274 } 275 276 private void updateUpwards() { 277 if (! (getParent() instanceof CheckBoxTreeItem)) return; 278 279 CheckBoxTreeItem<?> parent = (CheckBoxTreeItem<?>) getParent(); 280 int selectCount = 0; 281 int indeterminateCount = 0; 282 for (TreeItem<?> child : parent.getChildren()) { 283 if (! (child instanceof CheckBoxTreeItem)) continue; 284 285 CheckBoxTreeItem<?> cbti = (CheckBoxTreeItem<?>) child; 286 287 selectCount += cbti.isSelected() && ! cbti.isIndeterminate() ? 1 : 0; 288 indeterminateCount += cbti.isIndeterminate() ? 1 : 0; 289 } 290 291 if (selectCount == parent.getChildren().size()) { 292 parent.setSelected(true); 293 parent.setIndeterminate(false); 294 } else if (selectCount == 0 && indeterminateCount == 0) { 295 parent.setSelected(false); 296 parent.setIndeterminate(false); 297 } else { 298 parent.setIndeterminate(true); 299 } 300 } 301 302 private void updateDownwards() { 303 // If this node is not a leaf, we also put all 304 // children into the same state as this branch 305 if (! isLeaf()) { 306 for (TreeItem<T> child : getChildren()) { 307 if (child instanceof CheckBoxTreeItem) { 308 CheckBoxTreeItem<T> cbti = ((CheckBoxTreeItem<T>) child); 309 cbti.setSelected(isSelected()); 310 } 311 } 312 } 313 } 314 315 private void fireEvent(CheckBoxTreeItem<T> item, boolean selectionChanged) { 316 Event evt = new CheckBoxTreeItem.TreeModificationEvent<T>(CHECK_BOX_SELECTION_CHANGED_EVENT, item, selectionChanged); 317 Event.fireEvent(this, evt); 318 } 319 320 321 /** 322 * A TreeModificationEvent class that works in a similar vein to the 323 * {@link javafx.scene.control.TreeItem.TreeModificationEvent} class, in that 324 * this event will bubble up the CheckBoxTreeItem hierarchy, until the parent 325 * node is null. 326 * 327 * @param <T> The type of the value contained within the 328 * {@link CheckBoxTreeItem#valueProperty() value} property. 329 */ 330 public static class TreeModificationEvent<T> extends Event { 331 private static final long serialVersionUID = -8445355590698862999L; 332 333 private transient final CheckBoxTreeItem<T> treeItem; 334 private final boolean selectionChanged; 335 336 /** 337 * Common supertype for all tree modification event types. 338 */ 339 public static final EventType<Event> ANY = 340 new EventType<Event> (Event.ANY, "TREE_MODIFICATION"); 341 342 /** 343 * Creates a default TreeModificationEvent instance to represent the 344 * change in selection/indeterminate states for the given CheckBoxTreeItem 345 * instance. 346 */ 347 public TreeModificationEvent(EventType<? extends Event> eventType, CheckBoxTreeItem<T> treeItem, boolean selectionChanged) { 348 super(eventType); 349 this.treeItem = treeItem; 350 this.selectionChanged = selectionChanged; 351 } 352 353 /** 354 * Returns the CheckBoxTreeItem that this event occurred upon. 355 * @return The CheckBoxTreeItem that this event occurred upon. 356 */ 357 public CheckBoxTreeItem<T> getTreeItem() { 358 return treeItem; 359 } 360 361 /** 362 * Indicates the the reason for this event is that the selection on the 363 * CheckBoxTreeItem changed (as opposed to it becoming indeterminate). 364 */ 365 public boolean wasSelectionChanged() { 366 return selectionChanged; 367 } 368 369 /** 370 * Indicates the the reason for this event is that the indeterminate 371 * state on the CheckBoxTreeItem changed (as opposed to it becoming 372 * selected or unselected). 373 */ 374 public boolean wasIndeterminateChanged() { 375 return ! selectionChanged; 376 } 377 } 378}