Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 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 java.util.ArrayList;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.List;
032
033import javafx.beans.property.BooleanProperty;
034import javafx.beans.property.DoubleProperty;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.ObjectPropertyBase;
037import javafx.beans.property.ReadOnlyIntegerProperty;
038import javafx.beans.property.ReadOnlyIntegerWrapper;
039import javafx.beans.property.SimpleBooleanProperty;
040import javafx.beans.property.SimpleDoubleProperty;
041import javafx.beans.property.SimpleObjectProperty;
042import javafx.beans.value.ChangeListener;
043import javafx.beans.value.ObservableValue;
044import javafx.beans.value.WeakChangeListener;
045import javafx.collections.FXCollections;
046import javafx.collections.ListChangeListener;
047import javafx.collections.ListChangeListener.Change;
048import javafx.collections.ObservableList;
049import javafx.css.StyleableDoubleProperty;
050import javafx.event.Event;
051import javafx.event.EventHandler;
052import javafx.event.EventType;
053import javafx.geometry.Orientation;
054import javafx.scene.layout.Region;
055import javafx.util.Callback;
056
057import javafx.css.StyleableObjectProperty;
058import javafx.css.CssMetaData;
059import com.sun.javafx.css.converters.EnumConverter;
060import javafx.collections.WeakListChangeListener;
061import com.sun.javafx.css.converters.SizeConverter;
062import com.sun.javafx.scene.control.accessible.AccessibleList;
063import com.sun.javafx.scene.control.skin.ListViewSkin;
064import com.sun.javafx.scene.control.skin.VirtualContainerBase;
065import java.lang.ref.WeakReference;
066import com.sun.javafx.accessible.providers.AccessibleProvider;
067import javafx.css.PseudoClass;
068import javafx.beans.DefaultProperty;
069import javafx.css.Styleable;
070import javafx.css.StyleableProperty;
071import javafx.scene.Node;
072
073/**
074 * A ListView displays a horizontal or vertical list of items from which the
075 * user may select, or with which the user may interact. A ListView is able to
076 * have its generic type set to represent the type of data in the backing model.
077 * Doing this has the benefit of making various methods in the ListView, as well
078 * as the supporting classes (mentioned below), type-safe. In addition, making 
079 * use of the generic supports substantially simplifies development of applications
080 * making use of ListView, as all modern IDEs are able to auto-complete far
081 * more successfully with the additional type information.
082 * 
083 * <h3>Populating a ListView</h3>
084 * <p>A simple example of how to create and populate a ListView of names (Strings)
085 * is shown here:
086 * 
087 * <pre>
088 * {@code
089 * ObservableList<String> names = FXCollections.observableArrayList(
090 *          "Julia", "Ian", "Sue", "Matthew", "Hannah", "Stephan", "Denise");
091 * ListView<String> listView = new ListView<String>(names);}</pre>
092 *
093 * <p>The elements of the ListView are contained within the 
094 * {@link #itemsProperty() items} {@link ObservableList}. This
095 * ObservableList is automatically observed by the ListView, such that any 
096 * changes that occur inside the ObservableList will be automatically shown in
097 * the ListView itself. If passying the <code>ObservableList</code> in to the
098 * ListView constructor is not feasible, the recommended approach for setting 
099 * the items is to simply call:
100 * 
101 * <pre>
102 * {@code
103 * ObservableList<T> content = ...
104 * listView.setItems(content);}</pre>
105 * 
106 * The end result of this is, as noted above, that the ListView will automatically
107 * refresh the view to represent the items in the list.
108 * 
109 * <p>Another approach, whilst accepted by the ListView, <b>is not the 
110 * recommended approach</b>:
111 * 
112 * <pre>
113 * {@code
114 * List<T> content = ...
115 * getItems().setAll(content);}</pre>
116 * 
117 * The issue with the approach shown above is that the content list is being
118 * copied into the items list - meaning that subsequent changes to the content
119 * list are not observed, and will not be reflected visually within the ListView.
120 * 
121 * <h3>ListView Selection / Focus APIs</h3>
122 * <p>To track selection and focus, it is necessary to become familiar with the
123 * {@link SelectionModel} and {@link FocusModel} classes. A ListView has at most
124 * one instance of each of these classes, available from 
125 * {@link #selectionModelProperty() selectionModel} and 
126 * {@link #focusModelProperty() focusModel} properties respectively.
127 * Whilst it is possible to use this API to set a new selection model, in
128 * most circumstances this is not necessary - the default selection and focus
129 * models should work in most circumstances.
130 * 
131 * <p>The default {@link SelectionModel} used when instantiating a ListView is
132 * an implementation of the {@link MultipleSelectionModel} abstract class. 
133 * However, as noted in the API documentation for
134 * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
135 * property, the default value is {@link SelectionMode#SINGLE}. To enable 
136 * multiple selection in a default ListView instance, it is therefore necessary
137 * to do the following:
138 * 
139 * <pre>
140 * {@code 
141 * listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
142 *
143 * <h3>Customizing ListView Visuals</h3>
144 * <p>The visuals of the ListView can be entirely customized by replacing the 
145 * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to
146 * generate {@link ListCell} instances, which are used to represent an item in the
147 * ListView. See the {@link Cell} class documentation for a more complete
148 * description of how to write custom Cells.
149 * 
150 * @see ListCell
151 * @see MultipleSelectionModel
152 * @see FocusModel
153 * @param <T> This type is used to represent the type of the objects stored in 
154 *          the ListViews {@link #itemsProperty() items} ObservableList. It is
155 *          also used in the {@link #selectionModelProperty() selection model}
156 *          and {@link #focusModelProperty() focus model}.
157 */
158// TODO add code examples
159@DefaultProperty("items")
160public class ListView<T> extends Control {
161    
162    /***************************************************************************
163     *                                                                         *
164     * Static properties and methods                                           *
165     *                                                                         *
166     **************************************************************************/
167
168    /** 
169     * An EventType that indicates some edit event has occurred. It is the parent
170     * type of all other edit events: {@link #EDIT_START_EVENT},
171     *  {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT}.
172     */
173    @SuppressWarnings("unchecked")
174    public static <T> EventType<ListView.EditEvent<T>> editAnyEvent() {
175        return (EventType<ListView.EditEvent<T>>) EDIT_ANY_EVENT;
176    }
177    private static final EventType<?> EDIT_ANY_EVENT =
178            new EventType(Event.ANY, "LIST_VIEW_EDIT");
179    
180    /**
181     * An EventType used to indicate that an edit event has started within the
182     * ListView upon which the event was fired.
183     */
184    @SuppressWarnings("unchecked")
185    public static <T> EventType<ListView.EditEvent<T>> editStartEvent() {
186        return (EventType<ListView.EditEvent<T>>) EDIT_START_EVENT;
187    }
188    private static final EventType<?> EDIT_START_EVENT =
189            new EventType(editAnyEvent(), "EDIT_START");
190
191    /**
192     * An EventType used to indicate that an edit event has just been canceled
193     * within the ListView upon which the event was fired.
194     */
195    @SuppressWarnings("unchecked")
196    public static <T> EventType<ListView.EditEvent<T>> editCancelEvent() {
197        return (EventType<ListView.EditEvent<T>>) EDIT_CANCEL_EVENT;
198    }
199    private static final EventType<?> EDIT_CANCEL_EVENT =
200            new EventType(editAnyEvent(), "EDIT_CANCEL");
201
202    /**
203     * An EventType used to indicate that an edit event has been committed
204     * within the ListView upon which the event was fired.
205     */
206    @SuppressWarnings("unchecked")
207    public static <T> EventType<ListView.EditEvent<T>> editCommitEvent() {
208        return (EventType<ListView.EditEvent<T>>) EDIT_COMMIT_EVENT;
209    }
210    private static final EventType<?> EDIT_COMMIT_EVENT =
211            new EventType(editAnyEvent(), "EDIT_COMMIT");
212    
213    
214
215    /***************************************************************************
216     *                                                                         *
217     * Constructors                                                            *
218     *                                                                         *
219     **************************************************************************/
220
221    /**
222     * Creates a default ListView which will display contents stacked vertically.
223     * As no {@link ObservableList} is provided in this constructor, an empty
224     * ObservableList is created, meaning that it is legal to directly call
225     * {@link #getItems()} if so desired. However, as noted elsewhere, this
226     * is not the recommended approach 
227     * (instead call {@link #setItems(javafx.collections.ObservableList)}).
228     * 
229     * <p>Refer to the {@link ListView} class documentation for details on the
230     * default state of other properties.
231     */
232    public ListView() {
233        this(FXCollections.<T>observableArrayList());
234    }
235
236    /**
237     * Creates a default ListView which will stack the contents retrieved from the
238     * provided {@link ObservableList} vertically.
239     * 
240     * <p>Attempts to add a listener to the {@link ObservableList}, such that all
241     * subsequent changes inside the list will be shown to the user.
242     * 
243     * <p>Refer to the {@link ListView} class documentation for details on the
244     * default state of other properties.
245     */
246    public ListView(ObservableList<T> items) {
247        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
248
249        setItems(items);
250
251        // Install default....
252        // ...selection model
253        setSelectionModel(new ListView.ListViewBitSetSelectionModel<T>(this));
254
255        // ...focus model
256        setFocusModel(new ListView.ListViewFocusModel<T>(this));
257
258        // ...edit commit handler
259        setOnEditCommit(DEFAULT_EDIT_COMMIT_HANDLER);
260    }
261    
262    
263    
264    /***************************************************************************
265     *                                                                         *
266     * Callbacks and Events                                                    *
267     *                                                                         *
268     **************************************************************************/
269    
270    private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = new EventHandler<ListView.EditEvent<T>>() {
271        @Override public void handle(ListView.EditEvent<T> t) {
272            int index = t.getIndex();
273            List<T> list = getItems();
274            if (index < 0 || index >= list.size()) return;
275            list.set(index, t.getNewValue());
276        }
277    };
278    
279    
280    
281    /***************************************************************************
282     *                                                                         *
283     * Properties                                                              *
284     *                                                                         *
285     **************************************************************************/
286    
287    // --- Items
288    private ObjectProperty<ObservableList<T>> items;
289    
290    /**
291     * Sets the underlying data model for the ListView. Note that it has a generic
292     * type that must match the type of the ListView itself.
293     */
294    public final void setItems(ObservableList<T> value) {
295        itemsProperty().set(value);
296    }
297
298    /**
299     * Returns an {@link ObservableList} that contains the items currently being
300     * shown to the user. This may be null if 
301     * {@link #setItems(javafx.collections.ObservableList)} has previously been
302     * called, however, by default it is an empty ObservableList.
303     * 
304     * @return An ObservableList containing the items to be shown to the user, or
305     *      null if the items have previously been set to null.
306     */
307    public final ObservableList<T> getItems() {
308        return items == null ? null : items.get();
309    }
310
311    /**
312     * The underlying data model for the ListView. Note that it has a generic
313     * type that must match the type of the ListView itself.
314     */
315    public final ObjectProperty<ObservableList<T>> itemsProperty() {
316        if (items == null) {
317            items = new SimpleObjectProperty<ObservableList<T>>(this, "items") {
318                WeakReference<ObservableList<T>> oldItemsRef;
319                
320                @Override protected void invalidated() {
321                    ObservableList<T> oldItems = oldItemsRef == null ? null : oldItemsRef.get();
322                    
323                    // FIXME temporary fix for RT-15793. This will need to be
324                    // properly fixed when time permits
325                    if (getSelectionModel() instanceof ListView.ListViewBitSetSelectionModel) {
326                        ((ListView.ListViewBitSetSelectionModel<T>)getSelectionModel()).updateItemsObserver(oldItems, getItems());
327                    }
328                    if (getFocusModel() instanceof ListView.ListViewFocusModel) {
329                        ((ListView.ListViewFocusModel<T>)getFocusModel()).updateItemsObserver(oldItems, getItems());
330                    }
331                    if (getSkin() instanceof ListViewSkin) {
332                        ListViewSkin<?> skin = (ListViewSkin<?>) getSkin();
333                        skin.updateListViewItems();
334                    }
335                    
336                    oldItemsRef = new WeakReference<ObservableList<T>>(getItems());
337                }
338            };
339        }
340        return items;
341    }
342    
343    
344    // --- Placeholder Node
345    private ObjectProperty<Node> placeholder;
346    /**
347     * This Node is shown to the user when the listview has no content to show.
348     * This may be the case because the table model has no data in the first
349     * place or that a filter has been applied to the list model, resulting
350     * in there being nothing to show the user..
351     */
352    public final ObjectProperty<Node> placeholderProperty() {
353        if (placeholder == null) {
354            placeholder = new SimpleObjectProperty<Node>(this, "placeholder");
355        }
356        return placeholder;
357    }
358    public final void setPlaceholder(Node value) {
359        placeholderProperty().set(value);
360    }
361    public final Node getPlaceholder() {
362        return placeholder == null ? null : placeholder.get();
363    }
364    
365    
366    // --- Selection Model
367    private ObjectProperty<MultipleSelectionModel<T>> selectionModel = new SimpleObjectProperty<MultipleSelectionModel<T>>(this, "selectionModel");
368    
369    /**
370     * Sets the {@link MultipleSelectionModel} to be used in the ListView. 
371     * Despite a ListView requiring a <b>Multiple</b>SelectionModel, it is possible
372     * to configure it to only allow single selection (see 
373     * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
374     * for more information).
375     */
376    public final void setSelectionModel(MultipleSelectionModel<T> value) {
377        selectionModelProperty().set(value);
378    }
379
380    /**
381     * Returns the currently installed selection model.
382     */
383    public final MultipleSelectionModel<T> getSelectionModel() {
384        return selectionModel == null ? null : selectionModel.get();
385    }
386
387    /**
388     * The SelectionModel provides the API through which it is possible
389     * to select single or multiple items within a ListView, as  well as inspect
390     * which items have been selected by the user. Note that it has a generic
391     * type that must match the type of the ListView itself.
392     */
393    public final ObjectProperty<MultipleSelectionModel<T>> selectionModelProperty() {
394        return selectionModel;
395    }
396    
397    
398    // --- Focus Model
399    private ObjectProperty<FocusModel<T>> focusModel;
400    
401    /**
402     * Sets the {@link FocusModel} to be used in the ListView. 
403     */
404    public final void setFocusModel(FocusModel<T> value) {
405        focusModelProperty().set(value);
406    }
407
408    /**
409     * Returns the currently installed {@link FocusModel}.
410     */
411    public final FocusModel<T> getFocusModel() {
412        return focusModel == null ? null : focusModel.get();
413    }
414
415    /**
416     * The FocusModel provides the API through which it is possible
417     * to both get and set the focus on a single item within a ListView. Note 
418     * that it has a generic type that must match the type of the ListView itself.
419     */
420    public final ObjectProperty<FocusModel<T>> focusModelProperty() {
421        if (focusModel == null) {
422            focusModel = new SimpleObjectProperty<FocusModel<T>>(this, "focusModel");
423        }
424        return focusModel;
425    }
426    
427    
428    // --- Orientation
429    private ObjectProperty<Orientation> orientation;
430    
431    /**
432     * Sets the orientation of the ListView, which dictates whether
433     * it scrolls vertically or horizontally.
434     */
435    public final void setOrientation(Orientation value) {
436        orientationProperty().set(value);
437    };
438    
439    /**
440     * Returns the current orientation of the ListView, which dictates whether
441     * it scrolls vertically or horizontally.
442     */
443    public final Orientation getOrientation() {
444        return orientation == null ? Orientation.VERTICAL : orientation.get();
445    }
446    
447    /**
448     * The orientation of the {@code ListView} - this can either be horizontal
449     * or vertical.
450     */
451    public final ObjectProperty<Orientation> orientationProperty() {
452        if (orientation == null) {
453            orientation = new StyleableObjectProperty<Orientation>(Orientation.VERTICAL) {
454                @Override public void invalidated() {
455                    final boolean active = (get() == Orientation.VERTICAL);
456                    pseudoClassStateChanged(PSEUDO_CLASS_VERTICAL,    active);
457                    pseudoClassStateChanged(PSEUDO_CLASS_HORIZONTAL, !active);
458                }
459                
460                @Override 
461                public CssMetaData<ListView<?>,Orientation> getCssMetaData() {
462                    return ListView.StyleableProperties.ORIENTATION;
463                }
464                
465                @Override
466                public Object getBean() {
467                    return ListView.this;
468                }
469
470                @Override
471                public String getName() {
472                    return "orientation";
473                }
474            };
475        }
476        return orientation;
477    }
478    
479    
480
481
482    // --- Cell Factory
483    private ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory;
484
485    /**
486     * Sets a new cell factory to use in the ListView. This forces all old 
487     * {@link ListCell}'s to be thrown away, and new ListCell's created with 
488     * the new cell factory.
489     */
490    public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) {
491        cellFactoryProperty().set(value);
492    }
493
494    /**
495     * Returns the current cell factory.
496     */
497    public final Callback<ListView<T>, ListCell<T>> getCellFactory() {
498        return cellFactory == null ? null : cellFactory.get();
499    }
500
501    /**
502     * <p>Setting a custom cell factory has the effect of deferring all cell
503     * creation, allowing for total customization of the cell. Internally, the
504     * ListView is responsible for reusing ListCells - all that is necessary
505     * is for the custom cell factory to return from this function a ListCell
506     * which might be usable for representing any item in the ListView.
507     *
508     * <p>Refer to the {@link Cell} class documentation for more detail.
509     */
510    public final ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactoryProperty() {
511        if (cellFactory == null) {
512            cellFactory = new SimpleObjectProperty<Callback<ListView<T>, ListCell<T>>>(this, "cellFactory");
513        }
514        return cellFactory;
515    }
516
517
518    // --- Fixed cell size
519    private DoubleProperty fixedCellSize;
520
521    /**
522     * Sets the new fixed cell size for this control. Any value greater than
523     * zero will enable fixed cell size mode, whereas a zero or negative value
524     * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
525     * mode.
526     *
527     * @param value The new fixed cell size value, or -1 (or Region.USE_COMPUTED_SIZE)
528     *                  to disable.
529     */
530    public final void setFixedCellSize(double value) {
531        fixedCellSizeProperty().set(value);
532    }
533
534    /**
535     * Returns the fixed cell size value, which may be -1 to represent fixed cell
536     * size mode is disabled, or a value greater than zero to represent the size
537     * of all cells in this control.
538     *
539     * @return A double representing the fixed cell size of this control, or -1
540     *      if fixed cell size mode is disabled.
541     */
542    public final double getFixedCellSize() {
543        return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
544    }
545    /**
546     * Specifies whether this control has cells that are a fixed height (of the
547     * specified value). If this value is -1 (i.e. {@link Region#USE_COMPUTED_SIZE}),
548     * then all cells are individually sized and positioned. This is a slow
549     * operation. Therefore, when performance matters and developers are not
550     * dependent on variable cell sizes it is a good idea to set the fixed cell
551     * size value. Generally cells are around 24px, so setting a fixed cell size
552     * of 24 is likely to result in very little difference in visuals, but a
553     * improvement to performance.
554     *
555     * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
556     * This should not be confused with the -fx-cell-size property. The difference
557     * between these two CSS properties is that -fx-cell-size will size all
558     * cells to the specified size, but it will not enforce that this is the
559     * only size (thus allowing for variable cell sizes, and preventing the
560     * performance gains from being possible). Therefore, when performance matters
561     * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
562     * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
563     */
564    public final DoubleProperty fixedCellSizeProperty() {
565        if (fixedCellSize == null) {
566            fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
567                @Override public CssMetaData<ListView<?>,Number> getCssMetaData() {
568                    return StyleableProperties.FIXED_CELL_SIZE;
569                }
570
571                @Override public Object getBean() {
572                    return ListView.this;
573                }
574
575                @Override public String getName() {
576                    return "fixedCellSize";
577                }
578            };
579        }
580        return fixedCellSize;
581    }
582
583
584    // --- Editable
585    private BooleanProperty editable;
586    public final void setEditable(boolean value) {
587        editableProperty().set(value);
588    }
589    public final boolean isEditable() {
590        return editable == null ? false : editable.get();
591    }
592    /**
593     * Specifies whether this ListView is editable - only if the ListView and
594     * the ListCells within it are both editable will a ListCell be able to go
595     * into their editing state.
596     */
597    public final BooleanProperty editableProperty() {
598        if (editable == null) {
599            editable = new SimpleBooleanProperty(this, "editable", false);
600        }
601        return editable;
602    }
603
604
605    // --- Editing Index
606    private ReadOnlyIntegerWrapper editingIndex;
607
608    private void setEditingIndex(int value) {
609        editingIndexPropertyImpl().set(value);
610    }
611
612    /**
613     * Returns the index of the item currently being edited in the ListView,
614     * or -1 if no item is being edited.
615     */
616    public final int getEditingIndex() {
617        return editingIndex == null ? -1 : editingIndex.get();
618    }
619
620    /**
621     * <p>A property used to represent the index of the item currently being edited
622     * in the ListView, if editing is taking place, or -1 if no item is being edited.
623     * 
624     * <p>It is not possible to set the editing index, instead it is required that
625     * you call {@link #edit(int)}.
626     */
627    public final ReadOnlyIntegerProperty editingIndexProperty() {
628        return editingIndexPropertyImpl().getReadOnlyProperty();
629    }
630
631    private ReadOnlyIntegerWrapper editingIndexPropertyImpl() {
632        if (editingIndex == null) {
633            editingIndex = new ReadOnlyIntegerWrapper(this, "editingIndex", -1);
634        }
635        return editingIndex;
636    }
637
638
639    // --- On Edit Start
640    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStart;
641
642    /**
643     * Sets the {@link EventHandler} that will be called when the user begins
644     * an edit. 
645     * 
646     * <p>This is a convenience method - the same result can be 
647     * achieved by calling 
648     * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
649     */
650    public final void setOnEditStart(EventHandler<ListView.EditEvent<T>> value) {
651        onEditStartProperty().set(value);
652    }
653
654    /**
655     * Returns the {@link EventHandler} that will be called when the user begins
656     * an edit.
657     */
658    public final EventHandler<ListView.EditEvent<T>> getOnEditStart() {
659        return onEditStart == null ? null : onEditStart.get();
660    }
661
662    /**
663     * This event handler will be fired when the user successfully initiates
664     * editing.
665     */
666    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStartProperty() {
667        if (onEditStart == null) {
668            onEditStart = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
669                @Override protected void invalidated() {
670                    setEventHandler(ListView.<T>editStartEvent(), get());
671                }
672
673                @Override
674                public Object getBean() {
675                    return ListView.this;
676                }
677
678                @Override
679                public String getName() {
680                    return "onEditStart";
681                }
682            };
683        }
684        return onEditStart;
685    }
686
687
688    // --- On Edit Commit
689    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommit;
690
691    /**
692     * Sets the {@link EventHandler} that will be called when the user has
693     * completed their editing. This is called as part of the 
694     * {@link ListCell#commitEdit(java.lang.Object)} method.
695     * 
696     * <p>This is a convenience method - the same result can be 
697     * achieved by calling 
698     * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
699     */
700    public final void setOnEditCommit(EventHandler<ListView.EditEvent<T>> value) {
701        onEditCommitProperty().set(value);
702    }
703
704    /**
705     * Returns the {@link EventHandler} that will be called when the user commits
706     * an edit.
707     */
708    public final EventHandler<ListView.EditEvent<T>> getOnEditCommit() {
709        return onEditCommit == null ? null : onEditCommit.get();
710    }
711
712    /**
713     * <p>This property is used when the user performs an action that should
714     * result in their editing input being persisted.</p>
715     *
716     * <p>The EventHandler in this property should not be called directly - 
717     * instead call {@link ListCell#commitEdit(java.lang.Object)} from within
718     * your custom ListCell. This will handle firing this event, updating the 
719     * view, and switching out of the editing state.</p>
720     */
721    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommitProperty() {
722        if (onEditCommit == null) {
723            onEditCommit = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
724                @Override protected void invalidated() {
725                    setEventHandler(ListView.<T>editCommitEvent(), get());
726                }
727
728                @Override
729                public Object getBean() {
730                    return ListView.this;
731                }
732
733                @Override
734                public String getName() {
735                    return "onEditCommit";
736                }
737            };
738        }
739        return onEditCommit;
740    }
741
742
743    // --- On Edit Cancel
744    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancel;
745
746    /**
747     * Sets the {@link EventHandler} that will be called when the user cancels
748     * an edit.
749     */
750    public final void setOnEditCancel(EventHandler<ListView.EditEvent<T>> value) {
751        onEditCancelProperty().set(value);
752    }
753
754    /**
755     * Returns the {@link EventHandler} that will be called when the user cancels
756     * an edit.
757     */
758    public final EventHandler<ListView.EditEvent<T>> getOnEditCancel() {
759        return onEditCancel == null ? null : onEditCancel.get();
760    }
761
762    /**
763     * This event handler will be fired when the user cancels editing a cell.
764     */
765    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancelProperty() {
766        if (onEditCancel == null) {
767            onEditCancel = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
768                @Override protected void invalidated() {
769                    setEventHandler(ListView.<T>editCancelEvent(), get());
770                }
771
772                @Override
773                public Object getBean() {
774                    return ListView.this;
775                }
776
777                @Override
778                public String getName() {
779                    return "onEditCancel";
780                }
781            };
782        }
783        return onEditCancel;
784    }
785
786
787
788
789    /***************************************************************************
790     *                                                                         *
791     * Public API                                                              *
792     *                                                                         *
793     **************************************************************************/
794
795    /**
796     * Instructs the ListView to begin editing the item in the given index, if 
797     * the ListView is {@link #editableProperty() editable}. Once
798     * this method is called, if the current {@link #cellFactoryProperty()} is
799     * set up to support editing, the Cell will switch its visual state to enable
800     * for user input to take place.
801     * 
802     * @param itemIndex The index of the item in the ListView that should be 
803     *     edited.
804     */
805    public void edit(int itemIndex) {
806        if (!isEditable()) return;
807        setEditingIndex(itemIndex);
808    }
809
810    /**
811     * Scrolls the ListView such that the item in the given index is visible to
812     * the end user.
813     * 
814     * @param index The index that should be made visible to the user, assuming
815     *      of course that it is greater than, or equal to 0, and less than the
816     *      size of the items list contained within the given ListView.
817     */
818    public void scrollTo(int index) {
819        ControlUtils.scrollToIndex(this, index);
820    }
821    
822    /**
823     * Scrolls the TableView so that the given object is visible within the viewport.
824     * @param object The object that should be visible to the user.
825     */
826    public void scrollTo(T object) {
827        if( getItems() != null ) {
828            int idx = getItems().indexOf(object);
829            if( idx >= 0 ) {
830                ControlUtils.scrollToIndex(this, idx);        
831            }
832        }
833    }
834    
835    /**
836     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
837     * or {@link #scrollTo(S)}
838     */
839    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
840    
841    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
842        onScrollToProperty().set(value);
843    }
844    
845    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
846        if( onScrollTo != null ) {
847            return onScrollTo.get();
848        }
849        return null;
850    }
851    
852    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
853        if( onScrollTo == null ) {
854            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
855                @Override protected void invalidated() {
856                    setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
857                }
858                
859                @Override public Object getBean() {
860                    return ListView.this;
861                }
862
863                @Override public String getName() {
864                    return "onScrollTo";
865                }
866            };
867        }
868        return onScrollTo;
869    }
870
871    private AccessibleList accListView ;
872    /**
873     * @treatAsPrivate implementation detail
874     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
875     */
876    @Deprecated @Override public AccessibleProvider impl_getAccessible() {
877        if( accListView == null)
878            accListView = new AccessibleList(this);
879        return (AccessibleProvider)accListView ;
880    }
881
882    /** {@inheritDoc} */
883    @Override protected Skin<?> createDefaultSkin() {
884        return new ListViewSkin<T>(this);
885    }
886    
887    /***************************************************************************
888     *                                                                         *
889     * Private Implementation                                                  *
890     *                                                                         *
891     **************************************************************************/  
892
893
894
895    /***************************************************************************
896     *                                                                         *
897     * Stylesheet Handling                                                     *
898     *                                                                         *
899     **************************************************************************/
900
901    private static final String DEFAULT_STYLE_CLASS = "list-view";
902
903    /** @treatAsPrivate */
904    private static class StyleableProperties {
905        private static final CssMetaData<ListView<?>,Orientation> ORIENTATION = 
906            new CssMetaData<ListView<?>,Orientation>("-fx-orientation",
907                new EnumConverter<Orientation>(Orientation.class), 
908                Orientation.VERTICAL) {
909
910            @Override
911            public Orientation getInitialValue(ListView node) {
912                // A vertical ListView should remain vertical 
913                return node.getOrientation();
914            }
915
916            @Override
917            public boolean isSettable(ListView n) {
918                return n.orientation == null || !n.orientation.isBound();
919            }
920
921            @SuppressWarnings("unchecked") // orientationProperty() is a StyleableProperty<Orientation>
922            @Override
923            public StyleableProperty<Orientation> getStyleableProperty(ListView n) {
924                return (StyleableProperty<Orientation>)n.orientationProperty();
925            }
926        };
927
928        private static final CssMetaData<ListView<?>,Number> FIXED_CELL_SIZE =
929            new CssMetaData<ListView<?>,Number>("-fx-fixed-cell-size",
930                                                SizeConverter.getInstance(),
931                                                Region.USE_COMPUTED_SIZE) {
932
933                @Override public Double getInitialValue(ListView node) {
934                    return node.getFixedCellSize();
935                }
936
937                @Override public boolean isSettable(ListView n) {
938                    return n.fixedCellSize == null || !n.fixedCellSize.isBound();
939                }
940
941                @Override public StyleableProperty<Number> getStyleableProperty(ListView n) {
942                    return (StyleableProperty<Number>) n.fixedCellSizeProperty();
943                }
944            };
945            
946        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
947        static {
948            final List<CssMetaData<? extends Styleable, ?>> styleables =
949                new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
950            styleables.add(ORIENTATION);
951            styleables.add(FIXED_CELL_SIZE);
952            STYLEABLES = Collections.unmodifiableList(styleables);
953        }
954    }
955
956    /**
957     * @return The CssMetaData associated with this class, which may include the
958     * CssMetaData of its super classes.
959     */
960    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
961        return StyleableProperties.STYLEABLES;
962    }
963
964    /**
965     * {@inheritDoc}
966     */
967    @Override
968    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
969        return getClassCssMetaData();
970    }
971
972    private static final PseudoClass PSEUDO_CLASS_VERTICAL =
973            PseudoClass.getPseudoClass("vertical");
974    private static final PseudoClass PSEUDO_CLASS_HORIZONTAL =
975            PseudoClass.getPseudoClass("horizontal");
976
977    /***************************************************************************
978     *                                                                         *
979     * Support Interfaces                                                      *
980     *                                                                         *
981     **************************************************************************/
982
983
984
985    /***************************************************************************
986     *                                                                         *
987     * Support Classes                                                         *
988     *                                                                         *
989     **************************************************************************/
990
991    /**
992     * An {@link Event} subclass used specifically in ListView for representing
993     * edit-related events. It provides additional API to easily access the 
994     * index that the edit event took place on, as well as the input provided
995     * by the end user.
996     * 
997     * @param <T> The type of the input, which is the same type as the ListView 
998     *      itself.
999     */
1000    public static class EditEvent<T> extends Event {
1001        private final T newValue;
1002        private final int editIndex;
1003
1004        /**
1005         * Common supertype for all edit event types.
1006         */
1007        public static final EventType<?> ANY = EDIT_ANY_EVENT;
1008
1009        /**
1010         * Creates a new EditEvent instance to represent an edit event. This 
1011         * event is used for {@link #EDIT_START_EVENT}, 
1012         * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
1013         */
1014        public EditEvent(ListView<T> source,
1015                         EventType<? extends ListView.EditEvent<T>> eventType,
1016                         T newValue,
1017                         int editIndex) {
1018            super(source, Event.NULL_SOURCE_TARGET, eventType);
1019            this.editIndex = editIndex;
1020            this.newValue = newValue;
1021        }
1022
1023        /**
1024         * Returns the ListView upon which the edit took place.
1025         */
1026        @Override public ListView<T> getSource() {
1027            return (ListView<T>) super.getSource();
1028        }
1029
1030        /**
1031         * Returns the index in which the edit took place. 
1032         */
1033        public int getIndex() {
1034            return editIndex;
1035        }
1036
1037        /**
1038         * Returns the value of the new input provided by the end user.
1039         */
1040        public T getNewValue() {
1041            return newValue;
1042        }
1043
1044        /**
1045         * Returns a string representation of this {@code EditEvent} object.
1046         * @return a string representation of this {@code EditEvent} object.
1047         */ 
1048        @Override public String toString() {
1049            return "ListViewEditEvent [ newValue: " + getNewValue() + ", ListView: " + getSource() + " ]";
1050        }
1051    }
1052    
1053
1054
1055    // package for testing
1056    static class ListViewBitSetSelectionModel<T> extends MultipleSelectionModelBase<T> {
1057
1058        /***********************************************************************
1059         *                                                                     *
1060         * Constructors                                                        *
1061         *                                                                     *
1062         **********************************************************************/
1063
1064        public ListViewBitSetSelectionModel(final ListView<T> listView) {
1065            if (listView == null) {
1066                throw new IllegalArgumentException("ListView can not be null");
1067            }
1068
1069            this.listView = listView;
1070
1071
1072            /*
1073             * The following two listeners are used in conjunction with
1074             * SelectionModel.select(T obj) to allow for a developer to select
1075             * an item that is not actually in the data model. When this occurs,
1076             * we actively try to find an index that matches this object, going
1077             * so far as to actually watch for all changes to the items list,
1078             * rechecking each time.
1079             */
1080
1081            this.listView.itemsProperty().addListener(weakItemsObserver);
1082            if (listView.getItems() != null) {
1083                this.listView.getItems().addListener(weakItemsContentObserver);
1084//                updateItemsObserver(null, this.listView.getItems());
1085            }
1086            
1087            updateItemCount();
1088        }
1089        
1090        // watching for changes to the items list content
1091        private final ListChangeListener<T> itemsContentObserver = new ListChangeListener<T>() {
1092            @Override public void onChanged(Change<? extends T> c) {
1093                updateItemCount();
1094                
1095                while (c.next()) {
1096                    final T selectedItem = getSelectedItem();
1097                    final int selectedIndex = getSelectedIndex();
1098                    
1099                    if (listView.getItems() == null || listView.getItems().isEmpty()) {
1100                        clearSelection();
1101                    } else if (selectedIndex == -1 && selectedItem != null) {
1102                        int newIndex = listView.getItems().indexOf(selectedItem);
1103                        if (newIndex != -1) {
1104                            setSelectedIndex(newIndex);
1105                        }
1106                    } else if (c.wasRemoved() && 
1107                            c.getRemovedSize() == 1 && 
1108                            ! c.wasAdded() && 
1109                            selectedItem != null && 
1110                            selectedItem.equals(c.getRemoved().get(0))) {
1111                        // Bug fix for RT-28637
1112                        if (getSelectedIndex() < getItemCount()) {
1113                            T newSelectedItem = getModelItem(selectedIndex);
1114                            if (! selectedItem.equals(newSelectedItem)) {
1115                                setSelectedItem(newSelectedItem);
1116                            }
1117                        }
1118                    }
1119                }
1120                
1121                updateSelection(c);
1122            }
1123        };
1124        
1125        // watching for changes to the items list
1126        private final ChangeListener<ObservableList<T>> itemsObserver = new ChangeListener<ObservableList<T>>() {
1127            @Override
1128            public void changed(ObservableValue<? extends ObservableList<T>> valueModel, 
1129                ObservableList<T> oldList, ObservableList<T> newList) {
1130                    updateItemsObserver(oldList, newList);
1131            }
1132        };
1133        
1134        private WeakListChangeListener<T> weakItemsContentObserver =
1135                new WeakListChangeListener<T>(itemsContentObserver);
1136        
1137        private WeakChangeListener<ObservableList<T>> weakItemsObserver = 
1138                new WeakChangeListener<ObservableList<T>>(itemsObserver);
1139        
1140        private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
1141            // update listeners
1142            if (oldList != null) {
1143                oldList.removeListener(weakItemsContentObserver);
1144            }
1145            if (newList != null) {
1146                newList.addListener(weakItemsContentObserver);
1147            }
1148            
1149            updateItemCount();
1150
1151            // when the items list totally changes, we should clear out
1152            // the selection and focus
1153            setSelectedIndex(-1);
1154            focus(-1);
1155        }
1156
1157
1158
1159        /***********************************************************************
1160         *                                                                     *
1161         * Internal properties                                                 *
1162         *                                                                     *
1163         **********************************************************************/
1164
1165        private final ListView<T> listView;
1166        
1167        private int itemCount = 0;
1168        
1169        private int previousModelSize = 0;
1170
1171        // Listen to changes in the listview items list, such that when it 
1172        // changes we can update the selected indices bitset to refer to the 
1173        // new indices.
1174        // At present this is basically a left/right shift operation, which
1175        // seems to work ok.
1176        private void updateSelection(Change<? extends T> c) {
1177//            // debugging output
1178//            System.out.println(listView.getId());
1179//            if (c.wasAdded()) {
1180//                System.out.println("\tAdded size: " + c.getAddedSize() + ", Added sublist: " + c.getAddedSubList());
1181//            }
1182//            if (c.wasRemoved()) {
1183//                System.out.println("\tRemoved size: " + c.getRemovedSize() + ", Removed sublist: " + c.getRemoved());
1184//            }
1185//            if (c.wasReplaced()) {
1186//                System.out.println("\tWas replaced");
1187//            }
1188//            if (c.wasPermutated()) {
1189//                System.out.println("\tWas permutated");
1190//            }
1191            c.reset();
1192            while (c.next()) {
1193                if (c.wasReplaced()) {
1194                    if (c.getList().isEmpty()) {
1195                        // the entire items list was emptied - clear selection
1196                        clearSelection();
1197                    } else {
1198                        int index = getSelectedIndex();
1199                        
1200                        if (previousModelSize == c.getRemovedSize()) {
1201                            // all items were removed from the model
1202                            clearSelection();
1203                        } else if (index < getItemCount() && index >= 0) {
1204                            // Fix for RT-18969: the list had setAll called on it
1205                            // Use of makeAtomic is a fix for RT-20945
1206                            makeAtomic = true;
1207                            clearSelection(index);
1208                            makeAtomic = false;
1209                            select(index);
1210                        } else {
1211                            // Fix for RT-22079
1212                            clearSelection();
1213                        }
1214                    }
1215                } else if (c.wasAdded() || c.wasRemoved()) {
1216                    int shift = c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize();
1217                    shiftSelection(c.getFrom(), shift, null);
1218                } else if (c.wasPermutated()) {
1219
1220                    // General approach:
1221                    //   -- detected a sort has happened
1222                    //   -- Create a permutation lookup map (1)
1223                    //   -- dump all the selected indices into a list (2)
1224                    //   -- clear the selected items / indexes (3)
1225                    //   -- create a list containing the new indices (4)
1226                    //   -- for each previously-selected index (5)
1227                    //     -- if index is in the permutation lookup map
1228                    //       -- add the new index to the new indices list
1229                    //   -- Perform batch selection (6)
1230
1231                    // (1)
1232                    int length = c.getTo() - c.getFrom();
1233                    HashMap<Integer, Integer> pMap = new HashMap<Integer, Integer>(length);
1234                    for (int i = c.getFrom(); i < c.getTo(); i++) {
1235                        pMap.put(i, c.getPermutation(i));
1236                    }
1237
1238                    // (2)
1239                    List<Integer> selectedIndices = new ArrayList<Integer>(getSelectedIndices());
1240
1241
1242                    // (3)
1243                    clearSelection();
1244
1245                    // (4)
1246                    List<Integer> newIndices = new ArrayList<Integer>(getSelectedIndices().size());
1247
1248                    // (5)
1249                    for (int i = 0; i < selectedIndices.size(); i++) {
1250                        int oldIndex = selectedIndices.get(i);
1251
1252                        if (pMap.containsKey(oldIndex)) {
1253                            Integer newIndex = pMap.get(oldIndex);
1254                            newIndices.add(newIndex);
1255                        }
1256                    }
1257
1258                    // (6)
1259                    if (!newIndices.isEmpty()) {
1260                        if (newIndices.size() == 1) {
1261                            select(newIndices.get(0));
1262                        } else {
1263                            int[] ints = new int[newIndices.size() - 1];
1264                            for (int i = 0; i < newIndices.size() - 1; i++) {
1265                                ints[i] = newIndices.get(i + 1);
1266                            }
1267                            selectIndices(newIndices.get(0), ints);
1268                        }
1269                    }
1270                }
1271            }
1272            
1273            previousModelSize = getItemCount();
1274        }
1275
1276
1277
1278        /***********************************************************************
1279         *                                                                     *
1280         * Public selection API                                                *
1281         *                                                                     *
1282         **********************************************************************/
1283
1284        /** {@inheritDoc} */
1285        @Override protected void focus(int row) {
1286            if (listView.getFocusModel() == null) return;
1287            listView.getFocusModel().focus(row);
1288        }
1289
1290        /** {@inheritDoc} */
1291        @Override protected int getFocusedIndex() {
1292            if (listView.getFocusModel() == null) return -1;
1293            return listView.getFocusModel().getFocusedIndex();
1294        }
1295
1296        @Override protected int getItemCount() {
1297            return itemCount;
1298        }
1299
1300        @Override protected T getModelItem(int index) {
1301            List<T> items = listView.getItems();
1302            if (items == null) return null;
1303            if (index < 0 || index >= itemCount) return null;
1304
1305            return items.get(index);
1306        }
1307
1308        private void updateItemCount() {
1309            if (listView == null) {
1310                itemCount = -1;
1311            } else {
1312                List<T> items = listView.getItems();
1313                itemCount = items == null ? -1 : items.size();
1314            }
1315        } 
1316    }
1317
1318
1319
1320    // package for testing
1321    static class ListViewFocusModel<T> extends FocusModel<T> {
1322
1323        private final ListView<T> listView;
1324        private int itemCount = 0;
1325
1326        public ListViewFocusModel(final ListView<T> listView) {
1327            if (listView == null) {
1328                throw new IllegalArgumentException("ListView can not be null");
1329            }
1330
1331            this.listView = listView;
1332            this.listView.itemsProperty().addListener(weakItemsListener);
1333            if (listView.getItems() != null) {
1334                this.listView.getItems().addListener(weakItemsContentListener);
1335            }
1336            
1337            updateItemCount();
1338        }
1339
1340        private ChangeListener<ObservableList<T>> itemsListener = new ChangeListener<ObservableList<T>>() {
1341            @Override
1342            public void changed(ObservableValue<? extends ObservableList<T>> observable, 
1343                ObservableList<T> oldList, ObservableList<T> newList) {
1344                    updateItemsObserver(oldList, newList);
1345            }
1346        };
1347        
1348        private WeakChangeListener<ObservableList<T>> weakItemsListener = 
1349                new WeakChangeListener<ObservableList<T>>(itemsListener);
1350        
1351        private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
1352            // the listview items list has changed, we need to observe
1353            // the new list, and remove any observer we had from the old list
1354            if (oldList != null) oldList.removeListener(weakItemsContentListener);
1355            if (newList != null) newList.addListener(weakItemsContentListener);
1356            
1357            updateItemCount();
1358        }
1359        
1360        // Listen to changes in the listview items list, such that when it
1361        // changes we can update the focused index to refer to the new indices.
1362        private final ListChangeListener<T> itemsContentListener = new ListChangeListener<T>() {
1363            @Override public void onChanged(Change<? extends T> c) {
1364                updateItemCount();
1365                
1366                c.next();
1367                // looking at the first change
1368                int from = c.getFrom();
1369                if (getFocusedIndex() == -1 || from > getFocusedIndex()) {
1370                    return;
1371                }
1372                
1373                c.reset();
1374                boolean added = false;
1375                boolean removed = false;
1376                int addedSize = 0;
1377                int removedSize = 0;
1378                while (c.next()) {
1379                    added |= c.wasAdded();
1380                    removed |= c.wasRemoved();
1381                    addedSize += c.getAddedSize();
1382                    removedSize += c.getRemovedSize();
1383                }
1384                
1385                if (added && !removed) {
1386                    focus(getFocusedIndex() + addedSize);
1387                } else if (!added && removed) {
1388                    focus(getFocusedIndex() - removedSize);
1389                }
1390            }
1391        };
1392        
1393        private WeakListChangeListener<T> weakItemsContentListener 
1394                = new WeakListChangeListener<T>(itemsContentListener);
1395        
1396        @Override protected int getItemCount() {
1397            return itemCount;
1398        }
1399
1400        @Override protected T getModelItem(int index) {
1401            if (isEmpty()) return null;
1402            if (index < 0 || index >= itemCount) return null;
1403
1404            return listView.getItems().get(index);
1405        }
1406
1407        private boolean isEmpty() {
1408            return itemCount == -1;
1409        }
1410        
1411        private void updateItemCount() {
1412            if (listView == null) {
1413                itemCount = -1;
1414            } else {
1415                List<T> items = listView.getItems();
1416                itemCount = items == null ? -1 : items.size();
1417            }
1418        } 
1419    }
1420}