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}