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.cell;
027
028import javafx.scene.control.CheckBoxTreeItem;
029import javafx.beans.property.BooleanProperty;
030import javafx.beans.property.ObjectProperty;
031import javafx.beans.property.SimpleObjectProperty;
032import javafx.beans.value.ObservableValue;
033import javafx.scene.control.CheckBox;
034import javafx.scene.control.TreeCell;
035import javafx.scene.control.TreeItem;
036import javafx.scene.control.TreeView;
037import javafx.util.Callback;
038import javafx.util.StringConverter;
039
040/**
041 * A class containing a {@link TreeCell} implementation that draws a 
042 * {@link CheckBox} node inside the cell, along with support for common 
043 * interactions (discussed in more depth shortly).
044 * 
045 * <p>To make creating TreeViews with CheckBoxes easier, a convenience class 
046 * called {@link CheckBoxTreeItem} is provided. It is <b>highly</b> recommended 
047 * that developers use this class, rather than the regular {@link TreeItem}
048 * class, when constructing their TreeView tree structures. Refer to the 
049 * CheckBoxTreeItem API documentation for an example on how these two classes
050 * can be combined.
051 * 
052 * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox to 
053 * the right of the 'disclosure node' (i.e. the arrow). The item stored in 
054 * {@link CheckBoxTreeItem#getValue()} will then have the StringConverter called 
055 * on it, and this text will take all remaining horizontal space. Additionally, 
056 * by using {@link CheckBoxTreeItem}, the TreeView will automatically handle 
057 * situations such as:
058 * 
059 * <ul>
060 *   <li>Clicking on the {@link CheckBox} beside an item that has children will 
061 *      result in all children also becoming selected/unselected.
062 *   <li>Clicking on the {@link CheckBox} beside an item that has a parent will 
063 *      possibly toggle the state of the parent. For example, if you select a 
064 *      single child, the parent will become indeterminate (indicating partial 
065 *      selection of children). If you proceed to select all children, the 
066 *      parent will then show that it too is selected. This is recursive, with 
067 *      all parent nodes updating as expected.
068 * </ul>
069 * 
070 * If it is decided that using {@link CheckBoxTreeItem} is not desirable, 
071 * then it is necessary to call one of the constructors where a {@link Callback}
072 * is provided that can return an {@code ObservableValue<Boolean>}
073 * given a {@link TreeItem} instance. This {@code ObservableValue<Boolean>} 
074 * should represent the boolean state of the given {@link TreeItem}.
075 * 
076 * @param <T> The type of the elements contained within the TreeView TreeItem 
077 *      instances.
078 * @since 2.2
079 */
080public class CheckBoxTreeCell<T> extends TreeCell<T> {
081    
082    /***************************************************************************
083     *                                                                         *
084     * Static cell factories                                                   *
085     *                                                                         *
086     **************************************************************************/
087    
088    /**
089     * Creates a cell factory for use in a TreeView control, although there is a 
090     * major assumption when used in a TreeView: this cell factory assumes that 
091     * the TreeView root, and <b>all</b> children are instances of 
092     * {@link CheckBoxTreeItem}, rather than the default {@link TreeItem} class 
093     * that is used normally.
094     * 
095     * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox 
096     * to the right of the 'disclosure node' (i.e. the arrow). The item stored 
097     * in {@link CheckBoxTreeItem#getValue()} will then have the StringConverter
098     * called on it, and this text will take all remaining horizontal space. 
099     * Additionally, by using {@link CheckBoxTreeItem}, the TreeView will 
100     * automatically handle situations such as:
101     * 
102     * <ul>
103     *   <li>Clicking on the {@link CheckBox} beside an item that has children 
104     *      will result in all children also becoming selected/unselected.</li>
105     *   <li>Clicking on the {@link CheckBox} beside an item that has a parent 
106     *      will possibly toggle the state of the parent. For example, if you 
107     *      select a single child, the parent will become indeterminate (indicating
108     *      partial selection of children). If you proceed to select all 
109     *      children, the parent will then show that it too is selected. This is
110     *      recursive, with all parent nodes updating as expected.</li>
111     * </ul>
112     * 
113     * <p>Unfortunately, due to limitations in Java, it is necessary to provide 
114     * an explicit cast when using this method. For example:
115     * 
116     * <pre>
117     * {@code
118     * final TreeView<String> treeView = new TreeView<String>();
119     * treeView.setCellFactory(CheckBoxCell.<String>forTreeView());}</pre>
120     * 
121     * @param <T> The type of the elements contained within the 
122     *      {@link CheckBoxTreeItem} instances.
123     * @return A {@link Callback} that will return a TreeCell that is able to 
124     *      work on the type of element contained within the TreeView root, and 
125     *      all of its children (recursively).
126     */
127    public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView() {
128        Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty = 
129                new Callback<TreeItem<T>, ObservableValue<Boolean>>() {
130            @Override public ObservableValue<Boolean> call(TreeItem<T> item) {
131                if (item instanceof CheckBoxTreeItem<?>) {
132                    return ((CheckBoxTreeItem<?>)item).selectedProperty();
133                }
134                return null;
135            }
136        };
137        return forTreeView(getSelectedProperty, 
138                           CellUtils.<T>defaultTreeItemStringConverter());
139    }
140    
141    /**
142     * Creates a cell factory for use in a TreeView control. Unlike 
143     * {@link #forTreeView()}, this method does not assume that all TreeItem 
144     * instances in the TreeView are {@link CheckBoxTreeItem} instances.
145     * 
146     * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox 
147     * to the right of the 'disclosure node' (i.e. the arrow). The item stored 
148     * in {@link CheckBoxTreeItem#getValue()} will then have the StringConverter
149     * called on it, and this text will take all remaining horizontal space. 
150     * 
151     * <p>Unlike {@link #forTreeView()}, this cell factory does not handle 
152     * updating the state of parent or children TreeItems - it simply toggles 
153     * the {@code ObservableValue<Boolean>} that is provided, and no more. Of 
154     * course, this functionality can then be implemented externally by adding 
155     * observers to the {@code ObservableValue<Boolean>}, and toggling the state 
156     * of other properties as necessary.
157     * 
158     * @param <T> The type of the elements contained within the {@link TreeItem}
159     *      instances.
160     * @param getSelectedProperty A {@link Callback} that, given an object of 
161     *      type TreeItem<T>, will return an {@code ObservableValue<Boolean>} 
162     *      that represents whether the given item is selected or not. This 
163     *      {@code ObservableValue<Boolean>} will be bound bidirectionally 
164     *      (meaning that the CheckBox in the cell will set/unset this property 
165     *      based on user interactions, and the CheckBox will reflect the state 
166     *      of the {@code ObservableValue<Boolean>}, if it changes externally).
167     * @return A {@link Callback} that will return a TreeCell that is able to 
168     *      work on the type of element contained within the TreeView root, and 
169     *      all of its children (recursively).
170     */
171    public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView(
172            final Callback<TreeItem<T>, 
173            ObservableValue<Boolean>> getSelectedProperty) {
174        return forTreeView(getSelectedProperty, CellUtils.<T>defaultTreeItemStringConverter());
175    }
176    
177    /**
178     * Creates a cell factory for use in a TreeView control. Unlike 
179     * {@link #forTreeView()}, this method does not assume that all TreeItem 
180     * instances in the TreeView are {@link CheckBoxTreeItem}.
181     * 
182     * <p>When used in a TreeView, the CheckBoxCell is rendered with a CheckBox
183     * to the right of the 'disclosure node' (i.e. the arrow). The item stored 
184     * in {@link TreeItem#getValue()} will then have the the StringConverter
185     * called on it, and this text will take all remaining horizontal space. 
186     * 
187     * <p>Unlike {@link #forTreeView()}, this cell factory does not handle 
188     * updating the state of parent or children TreeItems - it simply toggles 
189     * the {@code ObservableValue<Boolean>} that is provided, and no more. Of 
190     * course, this functionality can then be implemented externally by adding 
191     * observers to the {@code ObservableValue<Boolean>}, and toggling the state 
192     * of other properties as necessary.
193     * 
194     * @param <T> The type of the elements contained within the {@link TreeItem} 
195     *      instances.
196     * @param getSelectedProperty A Callback that, given an object of 
197     *      type TreeItem<T>, will return an {@code ObservableValue<Boolean>} 
198     *      that represents whether the given item is selected or not. This 
199     *      {@code ObservableValue<Boolean>} will be bound bidirectionally 
200     *      (meaning that the CheckBox in the cell will set/unset this property 
201     *      based on user interactions, and the CheckBox will reflect the state of 
202     *      the {@code ObservableValue<Boolean>}, if it changes externally).
203     * @param converter A StringConverter that, give an object of type TreeItem<T>, 
204     *      will return a String that can be used to represent the object
205     *      visually. The default implementation in {@link #forTreeView(Callback)} 
206     *      is to simply call .toString() on all non-null items (and to just 
207     *      return an empty string in cases where the given item is null).      
208     * @return A {@link Callback} that will return a TreeCell that is able to 
209     *      work on the type of element contained within the TreeView root, and 
210     *      all of its children (recursively).
211     */
212    public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView(
213            final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 
214            final StringConverter<TreeItem<T>> converter) {
215        return new Callback<TreeView<T>, TreeCell<T>>() {
216            @Override public TreeCell<T> call(TreeView<T> tree) {
217                return new CheckBoxTreeCell<T>(getSelectedProperty, converter);
218            }
219        };
220    }
221    
222    
223    
224    
225    /***************************************************************************
226     *                                                                         *
227     * Fields                                                                  *
228     *                                                                         *
229     **************************************************************************/
230    private final CheckBox checkBox;
231    
232    private ObservableValue<Boolean> booleanProperty;
233    
234    private BooleanProperty indeterminateProperty;
235    
236    
237    
238    /***************************************************************************
239     *                                                                         *
240     * Constructors                                                            *
241     *                                                                         *
242     **************************************************************************/
243    
244    /**
245     * Creates a default {@link CheckBoxTreeCell} that assumes the TreeView is 
246     * constructed with {@link CheckBoxTreeItem} instances, rather than the 
247     * default {@link TreeItem}.
248     * By using {@link CheckBoxTreeItem}, it will internally manage the selected 
249     * and indeterminate state of each item in the tree.
250     */
251    public CheckBoxTreeCell() {
252        // getSelectedProperty as anonymous inner class to deal with situation
253        // where the user is using CheckBoxTreeItem instances in their tree
254        this(new Callback<TreeItem<T>, ObservableValue<Boolean>>() {
255            @Override public ObservableValue<Boolean> call(TreeItem<T> item) {
256                if (item instanceof CheckBoxTreeItem<?>) {
257                    return ((CheckBoxTreeItem<?>)item).selectedProperty();
258                }
259                return null;
260            }
261        });
262    }
263    
264    /**
265     * Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a 
266     * cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this 
267     * method does not assume that all TreeItem instances in the TreeView are 
268     * {@link CheckBoxTreeItem}.
269     * 
270     * <p>To call this method, it is necessary to provide a 
271     * {@link Callback} that, given an object of type TreeItem<T>, will return 
272     * an {@code ObservableValue<Boolean>} that represents whether the given 
273     * item is selected or not. This {@code ObservableValue<Boolean>} will be 
274     * bound bidirectionally (meaning that the CheckBox in the cell will 
275     * set/unset this property based on user interactions, and the CheckBox will 
276     * reflect the state of the {@code ObservableValue<Boolean>}, if it changes 
277     * externally).
278     * 
279     * <p>If the items are not {@link CheckBoxTreeItem} instances, it becomes 
280     * the developers responsibility to handle updating the state of parent and 
281     * children TreeItems. This means that, given a TreeItem, this class will 
282     * simply toggles the {@code ObservableValue<Boolean>} that is provided, and 
283     * no more. Of course, this functionality can then be implemented externally 
284     * by adding observers to the {@code ObservableValue<Boolean>}, and toggling 
285     * the state of other properties as necessary.
286     * 
287     * @param getSelectedProperty A {@link Callback} that will return an 
288     *      {@code ObservableValue<Boolean>} that represents whether the given 
289     *      item is selected or not.
290     */
291    public CheckBoxTreeCell(
292            final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty) {
293        this(getSelectedProperty, CellUtils.<T>defaultTreeItemStringConverter(), null);
294    }
295    
296    /**
297     * Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a 
298     * cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this 
299     * method does not assume that all TreeItem instances in the TreeView are 
300     * {@link CheckBoxTreeItem}.
301     * 
302     * <p>To call this method, it is necessary to provide a {@link Callback} 
303     * that, given an object of type TreeItem<T>, will return an 
304     * {@code ObservableValue<Boolean>} that represents whether the given item 
305     * is selected or not. This {@code ObservableValue<Boolean>} will be bound 
306     * bidirectionally (meaning that the CheckBox in the cell will set/unset 
307     * this property based on user interactions, and the CheckBox will reflect
308     * the state of the {@code ObservableValue<Boolean>}, if it changes 
309     * externally).
310     * 
311     * <p>If the items are not {@link CheckBoxTreeItem} instances, it becomes 
312     * the developers responsibility to handle updating the state of parent and 
313     * children TreeItems. This means that, given a TreeItem, this class will 
314     * simply toggles the {@code ObservableValue<Boolean>} that is provided, and 
315     * no more. Of course, this functionality can then be implemented externally 
316     * by adding observers to the {@code ObservableValue<Boolean>}, and toggling 
317     * the state of other properties as necessary.
318     * 
319     * @param getSelectedProperty A {@link Callback} that will return an 
320     *      {@code ObservableValue<Boolean>} that represents whether the given 
321     *      item is selected or not.
322     * @param converter A StringConverter that, give an object of type TreeItem<T>, will 
323     *      return a String that can be used to represent the object visually.
324     */
325    public CheckBoxTreeCell(
326            final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 
327            final StringConverter<TreeItem<T>> converter) {
328        this(getSelectedProperty, converter, null);
329    }
330
331    private CheckBoxTreeCell(
332            final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty, 
333            final StringConverter<TreeItem<T>> converter, 
334            final Callback<TreeItem<T>, ObservableValue<Boolean>> getIndeterminateProperty) {
335        this.getStyleClass().add("check-box-tree-cell");
336        setSelectedStateCallback(getSelectedProperty);
337        setConverter(converter);
338        
339        this.checkBox = new CheckBox();
340        this.checkBox.setAllowIndeterminate(false);
341        setGraphic(checkBox);
342    }
343    
344    
345    
346    /***************************************************************************
347     *                                                                         *
348     * Properties                                                              *
349     *                                                                         *
350     **************************************************************************/
351    
352    // --- converter
353    private ObjectProperty<StringConverter<TreeItem<T>>> converter = 
354            new SimpleObjectProperty<StringConverter<TreeItem<T>>>(this, "converter");
355
356    /**
357     * The {@link StringConverter} property.
358     */
359    public final ObjectProperty<StringConverter<TreeItem<T>>> converterProperty() { 
360        return converter; 
361    }
362    
363    /** 
364     * Sets the {@link StringConverter} to be used in this cell.
365     */
366    public final void setConverter(StringConverter<TreeItem<T>> value) { 
367        converterProperty().set(value); 
368    }
369    
370    /**
371     * Returns the {@link StringConverter} used in this cell.
372     */
373    public final StringConverter<TreeItem<T>> getConverter() { 
374        return converterProperty().get(); 
375    }
376    
377    
378    
379    // --- selected state callback property
380    private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> 
381            selectedStateCallback = 
382            new SimpleObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>(
383            this, "selectedStateCallback");
384
385    /**
386     * Property representing the {@link Callback} that is bound to by the 
387     * CheckBox shown on screen.
388     */
389    public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 
390        return selectedStateCallback; 
391    }
392    
393    /** 
394     * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
395     */
396    public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) { 
397        selectedStateCallbackProperty().set(value); 
398    }
399    
400    /**
401     * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
402     */
403    public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() { 
404        return selectedStateCallbackProperty().get(); 
405    }
406    
407    
408    
409    /***************************************************************************
410     *                                                                         *
411     * Public API                                                              *
412     *                                                                         *
413     **************************************************************************/
414    
415    /** {@inheritDoc} */
416    @Override public void updateItem(T item, boolean empty) {
417        super.updateItem(item, empty);
418        
419        if (empty) {
420            setText(null);
421            setGraphic(null);
422        } else {
423            StringConverter c = getConverter();
424            
425            TreeItem<T> treeItem = getTreeItem();
426            
427            // update the node content
428            setText(c != null ? c.toString(treeItem) : (treeItem == null ? "" : treeItem.toString()));
429            checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic());
430            setGraphic(checkBox);
431            
432            // uninstall bindings
433            if (booleanProperty != null) {
434                checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
435            }
436            if (indeterminateProperty != null) {
437                checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty);
438            }
439
440            // install new bindings.
441            // We special case things when the TreeItem is a CheckBoxTreeItem
442            if (treeItem instanceof CheckBoxTreeItem) {
443                CheckBoxTreeItem<T> cbti = (CheckBoxTreeItem<T>) treeItem;
444                booleanProperty = cbti.selectedProperty();
445                checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
446                
447                indeterminateProperty = cbti.indeterminateProperty();
448                checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty);
449            } else {
450                Callback<TreeItem<T>, ObservableValue<Boolean>> callback = getSelectedStateCallback();
451                if (callback == null) {
452                    throw new NullPointerException(
453                            "The CheckBoxTreeCell selectedStateCallbackProperty can not be null");
454                }
455                
456                booleanProperty = callback.call(treeItem);
457                if (booleanProperty != null) {
458                    checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
459                }
460            }
461        }
462    }
463}