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 com.sun.javafx.scene.control.skin.ListCellSkin; 029import javafx.beans.InvalidationListener; 030import javafx.beans.Observable; 031import javafx.beans.WeakInvalidationListener; 032import javafx.beans.property.ReadOnlyObjectProperty; 033import javafx.beans.property.ReadOnlyObjectWrapper; 034import javafx.beans.value.ChangeListener; 035import javafx.beans.value.ObservableValue; 036import javafx.beans.value.WeakChangeListener; 037import javafx.collections.ListChangeListener; 038import javafx.collections.ObservableList; 039 040import javafx.collections.WeakListChangeListener; 041import java.lang.ref.WeakReference; 042import java.util.List; 043 044 045/** 046 * <p>The {@link Cell} type used within {@link ListView} instances. In addition 047 * to the API defined on Cell and {@link IndexedCell}, the ListCell is more 048 * tightly bound to a ListView, allowing for better support of editing events, 049 * etc. 050 * 051 * <p>A ListView maintains selection, indicating which cell(s) have been selected, 052 * and focus, indicating the current focus owner for any given ListView. For each 053 * property, each ListCell has a boolean reflecting whether this specific cell is 054 * selected or focused. To achieve this, each ListCell has a reference back to 055 * the ListView that it is being used within. Each ListCell belongs to one and 056 * only one ListView. 057 * 058 * <p>Note that in the case of virtualized controls like ListView, when a cell 059 * has focus this is not in the same sense as application focus. When a ListCell 060 * has focus it simply represents the fact that the cell will receive keyboard 061 * events in the situation that the owning ListView actually contains focus. Of 062 * course, in the case where a cell has a Node set in the 063 * {@link #graphicProperty() graphic} property, it is completely legal for this 064 * Node to request, and acquire focus as would normally be expected. 065 * 066 * @param <T> The type of the item contained within the ListCell. 067 */ 068// TODO add code examples 069public class ListCell<T> extends IndexedCell<T> { 070 071 /*************************************************************************** 072 * * 073 * Constructors * 074 * * 075 **************************************************************************/ 076 077 /** 078 * Creates a default ListCell with the default style class of 'list-cell'. 079 */ 080 public ListCell() { 081 getStyleClass().addAll(DEFAULT_STYLE_CLASS); 082 } 083 084 085 /*************************************************************************** 086 * * 087 * Listeners * 088 * We have to listen to a number of properties on the ListView itself * 089 * as well as attach listeners to a couple different ObservableLists. * 090 * We have to be sure to unhook these listeners whenever the reference * 091 * to the ListView changes, or whenever one of the ObservableList * 092 * references changes (such as setting the selectionModel, focusModel, * 093 * or items). * 094 * * 095 **************************************************************************/ 096 097 /** 098 * Listens to the editing index on the ListView. It is possible for the developer 099 * to call the ListView#edit(int) method and cause a specific cell to start 100 * editing. In such a case, we need to be notified so we can call startEdit 101 * on our side. 102 */ 103 private final InvalidationListener editingListener = new InvalidationListener() { 104 @Override public void invalidated(Observable value) { 105 updateEditing(); 106 } 107 }; 108 private boolean updateEditingIndex = true; 109 110 /** 111 * Listens to the selection model on the ListView. Whenever the selection model 112 * is changed (updated), the selected property on the ListCell is updated accordingly. 113 */ 114 private final ListChangeListener<Integer> selectedListener = new ListChangeListener<Integer>() { 115 @Override public void onChanged(ListChangeListener.Change<? extends Integer> c) { 116 updateSelection(); 117 } 118 }; 119 120 /** 121 * Listens to the selectionModel property on the ListView. Whenever the entire model is changed, 122 * we have to unhook the weakSelectedListener and update the selection. 123 */ 124 private final ChangeListener<MultipleSelectionModel<T>> selectionModelPropertyListener = new ChangeListener<MultipleSelectionModel<T>>() { 125 @Override 126 public void changed( 127 ObservableValue<? extends MultipleSelectionModel<T>> observable, 128 MultipleSelectionModel<T> oldValue, 129 MultipleSelectionModel<T> newValue) { 130 131 if (oldValue != null) { 132 oldValue.getSelectedIndices().removeListener(weakSelectedListener); 133 } 134 135 if (newValue != null) { 136 newValue.getSelectedIndices().addListener(weakSelectedListener); 137 } 138 139 updateSelection(); 140 } 141 142 }; 143 144 /** 145 * Listens to the items on the ListView. Whenever the items are changed in such a way that 146 * it impacts the index of this ListCell, then we must update the item. 147 */ 148 private final ListChangeListener<T> itemsListener = new ListChangeListener<T>() { 149 @Override public void onChanged(ListChangeListener.Change<? extends T> c) { 150 updateItem(); 151 } 152 }; 153 154 /** 155 * Listens to the items property on the ListView. Whenever the entire list is changed, 156 * we have to unhook the weakItemsListener and update the item. 157 */ 158 private final ChangeListener<ObservableList<T>> itemsPropertyListener = new ChangeListener<ObservableList<T>>() { 159 @Override public void changed(ObservableValue<? extends ObservableList<T>> observable, 160 ObservableList<T> oldValue, 161 ObservableList<T> newValue) { 162 if (oldValue != null) { 163 oldValue.removeListener(weakItemsListener); 164 } 165 if (newValue != null) { 166 newValue.addListener(weakItemsListener); 167 } 168 updateItem(); 169 } 170 }; 171 172 /** 173 * Listens to the focus model on the ListView. Whenever the focus model changes, 174 * the focused property on the ListCell is updated 175 */ 176 private final InvalidationListener focusedListener = new InvalidationListener() { 177 @Override public void invalidated(Observable value) { 178 updateFocus(); 179 } 180 }; 181 182 /** 183 * Listens to the focusModel property on the ListView. Whenever the entire model is changed, 184 * we have to unhook the weakFocusedListener and update the focus. 185 */ 186 private final ChangeListener<FocusModel<T>> focusModelPropertyListener = new ChangeListener<FocusModel<T>>() { 187 @Override public void changed(ObservableValue<? extends FocusModel<T>> observable, 188 FocusModel<T> oldValue, 189 FocusModel<T> newValue) { 190 if (oldValue != null) { 191 oldValue.focusedIndexProperty().removeListener(weakFocusedListener); 192 } 193 if (newValue != null) { 194 newValue.focusedIndexProperty().addListener(weakFocusedListener); 195 } 196 updateFocus(); 197 } 198 }; 199 200 201 private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener); 202 private final WeakListChangeListener<Integer> weakSelectedListener = new WeakListChangeListener<Integer>(selectedListener); 203 private final WeakChangeListener<MultipleSelectionModel<T>> weakSelectionModelPropertyListener = new WeakChangeListener<MultipleSelectionModel<T>>(selectionModelPropertyListener); 204 private final WeakListChangeListener<T> weakItemsListener = new WeakListChangeListener<T>(itemsListener); 205 private final WeakChangeListener<ObservableList<T>> weakItemsPropertyListener = new WeakChangeListener<ObservableList<T>>(itemsPropertyListener); 206 private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener); 207 private final WeakChangeListener<FocusModel<T>> weakFocusModelPropertyListener = new WeakChangeListener<FocusModel<T>>(focusModelPropertyListener); 208 209 /*************************************************************************** 210 * * 211 * Properties * 212 * * 213 **************************************************************************/ 214 215 /** 216 * The ListView associated with this Cell. 217 */ 218 private ReadOnlyObjectWrapper<ListView<T>> listView = new ReadOnlyObjectWrapper<ListView<T>>(this, "listView") { 219 /** 220 * A weak reference to the ListView itself, such that whenever the ... 221 */ 222 private WeakReference<ListView<T>> weakListViewRef = new WeakReference<ListView<T>>(null); 223 224 @Override protected void invalidated() { 225 // Get the current and old list view references 226 final ListView<T> currentListView = get(); 227 final ListView<T> oldListView = weakListViewRef.get(); 228 229 // If the currentListView is the same as the oldListView, then 230 // there is nothing to be done. 231 if (currentListView == oldListView) return; 232 233 // If the old list view is not null, then we must unhook all its listeners 234 if (oldListView != null) { 235 // If the old selection model isn't null, unhook it 236 final MultipleSelectionModel<T> sm = oldListView.getSelectionModel(); 237 if (sm != null) { 238 sm.getSelectedIndices().removeListener(weakSelectedListener); 239 } 240 241 // If the old focus model isn't null, unhook it 242 final FocusModel<T> fm = oldListView.getFocusModel(); 243 if (fm != null) { 244 fm.focusedIndexProperty().removeListener(weakFocusedListener); 245 } 246 247 // If the old items isn't null, unhook the listener 248 final ObservableList<T> items = oldListView.getItems(); 249 if (items != null) { 250 items.removeListener(weakItemsListener); 251 } 252 253 // Remove the listeners of the properties on ListView 254 oldListView.editingIndexProperty().removeListener(weakEditingListener); 255 oldListView.itemsProperty().removeListener(weakItemsPropertyListener); 256 oldListView.focusModelProperty().removeListener(weakFocusModelPropertyListener); 257 oldListView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener); 258 } 259 260 if (currentListView != null) { 261 final MultipleSelectionModel<T> sm = currentListView.getSelectionModel(); 262 if (sm != null) { 263 sm.getSelectedIndices().addListener(weakSelectedListener); 264 } 265 266 final FocusModel<T> fm = currentListView.getFocusModel(); 267 if (fm != null) { 268 fm.focusedIndexProperty().addListener(weakFocusedListener); 269 } 270 271 final ObservableList<T> items = currentListView.getItems(); 272 if (items != null) { 273 items.addListener(weakItemsListener); 274 } 275 276 currentListView.editingIndexProperty().addListener(weakEditingListener); 277 currentListView.itemsProperty().addListener(weakItemsPropertyListener); 278 currentListView.focusModelProperty().addListener(weakFocusModelPropertyListener); 279 currentListView.selectionModelProperty().addListener(weakSelectionModelPropertyListener); 280 281 weakListViewRef = new WeakReference<ListView<T>>(currentListView); 282 } 283 284 updateItem(); 285 updateSelection(); 286 updateFocus(); 287 requestLayout(); 288 } 289 }; 290 private void setListView(ListView<T> value) { listView.set(value); } 291 public final ListView<T> getListView() { return listView.get(); } 292 public final ReadOnlyObjectProperty<ListView<T>> listViewProperty() { return listView.getReadOnlyProperty(); } 293 294 295 296 /*************************************************************************** 297 * * 298 * Public API * 299 * * 300 **************************************************************************/ 301 302 /** {@inheritDoc} */ 303 @Override void indexChanged() { 304 super.indexChanged(); 305 updateItem(); 306 updateSelection(); 307 updateFocus(); 308 } 309 310 /** {@inheritDoc} */ 311 @Override protected Skin<?> createDefaultSkin() { 312 return new ListCellSkin(this); 313 } 314 315 316 /*************************************************************************** 317 * * 318 * Editing API * 319 * * 320 **************************************************************************/ 321 322 /** {@inheritDoc} */ 323 @Override public void startEdit() { 324 final ListView<T> list = getListView(); 325 if (!isEditable() || (list != null && ! list.isEditable())) { 326 return; 327 } 328 329 // it makes sense to get the cell into its editing state before firing 330 // the event to the ListView below, so that's what we're doing here 331 // by calling super.startEdit(). 332 super.startEdit(); 333 334 // Inform the ListView of the edit starting. 335 if (list != null) { 336 list.fireEvent(new ListView.EditEvent<T>(list, 337 ListView.<T>editStartEvent(), 338 null, 339 list.getEditingIndex())); 340 list.edit(getIndex()); 341 list.requestFocus(); 342 } 343 } 344 345 /** {@inheritDoc} */ 346 @Override public void commitEdit(T newValue) { 347 if (! isEditing()) return; 348 ListView<T> list = getListView(); 349 350 if (list != null) { 351 // Inform the ListView of the edit being ready to be committed. 352 list.fireEvent(new ListView.EditEvent<T>(list, 353 ListView.<T>editCommitEvent(), 354 newValue, 355 list.getEditingIndex())); 356 } 357 358 // update the item within this cell, so that it represents the new value 359 updateItem(newValue, false); 360 361 // inform parent classes of the commit, so that they can switch us 362 // out of the editing state 363 super.commitEdit(newValue); 364 365 if (list != null) { 366 // reset the editing index on the ListView. This must come after the 367 // event is fired so that the developer on the other side can consult 368 // the ListView editingIndex property (if they choose to do that 369 // rather than just grab the int from the event). 370 list.edit(-1); 371 list.requestFocus(); 372 } 373 } 374 375 /** {@inheritDoc} */ 376 @Override public void cancelEdit() { 377 if (! isEditing()) return; 378 379 // Inform the ListView of the edit being cancelled. 380 ListView<T> list = getListView(); 381 382 super.cancelEdit(); 383 384 if (list != null) { 385 int editingIndex = list.getEditingIndex(); 386 387 // reset the editing index on the ListView 388 if (updateEditingIndex) list.edit(-1); 389 list.requestFocus(); 390 391 list.fireEvent(new ListView.EditEvent<T>(list, 392 ListView.<T>editCancelEvent(), 393 null, 394 editingIndex)); 395 } 396 } 397 398 399 /* ************************************************************************* 400 * * 401 * Private implementation * 402 * * 403 **************************************************************************/ 404 405 private void updateItem() { 406 ListView<T> lv = getListView(); 407 List<T> items = lv == null ? null : lv.getItems(); 408 int index = getIndex(); 409 410 // Compute whether the index for this cell is for a real item 411 boolean valid = items != null && index >=0 && index < items.size(); 412 413 // Cause the cell to update itself 414 if (valid) { 415 T oldValue = getItem(); 416 T newValue = items.get(index); 417 418 if ((newValue != null && ! newValue.equals(oldValue)) || 419 oldValue != null && ! oldValue.equals(newValue)) { 420 updateItem(newValue, false); 421 } 422 } else { 423 if (! isEmpty()) { 424 updateItem(null, true); 425 } 426 } 427 } 428 429 /** 430 * Updates the ListView associated with this Cell. 431 * 432 * @expert This function is intended to be used by experts, primarily 433 * by those implementing new Skins. It is not common 434 * for developers or designers to access this function directly. 435 */ 436 public final void updateListView(ListView<T> listView) { 437 setListView(listView); 438 } 439 440 private void updateSelection() { 441 if (isEmpty()) return; 442 int index = getIndex(); 443 ListView<T> listView = getListView(); 444 if (index == -1 || listView == null) return; 445 446 SelectionModel<T> sm = listView.getSelectionModel(); 447 if (sm == null) return; 448 449 boolean isSelected = sm.isSelected(index); 450 if (isSelected() == isSelected) return; 451 452 updateSelected(isSelected); 453 } 454 455 private void updateFocus() { 456 int index = getIndex(); 457 ListView<T> listView = getListView(); 458 if (index == -1 || listView == null) return; 459 460 FocusModel<T> fm = listView.getFocusModel(); 461 if (fm == null) return; 462 463 setFocused(fm.isFocused(index)); 464 } 465 466 private void updateEditing() { 467 final int index = getIndex(); 468 final ListView<T> list = getListView(); 469 final int editIndex = list == null ? -1 : list.getEditingIndex(); 470 final boolean editing = isEditing(); 471 472 // Check that the list is specified, and my index is not -1 473 if (index != -1 && list != null) { 474 // If my index is the index being edited and I'm not currently in 475 // the edit mode, then I need to enter the edit mode 476 if (index == editIndex && !editing) { 477 startEdit(); 478 } else if (index != editIndex && editing) { 479 // If my index is not the one being edited then I need to cancel 480 // the edit. The tricky thing here is that as part of this call 481 // I cannot end up calling list.edit(-1) the way that the standard 482 // cancelEdit method would do. Yet, I need to call cancelEdit 483 // so that subclasses which override cancelEdit can execute. So, 484 // I have to use a kind of hacky flag workaround. 485 updateEditingIndex = false; 486 cancelEdit(); 487 updateEditingIndex = true; 488 } 489 } 490 } 491 492 493 494 /*************************************************************************** 495 * * 496 * Stylesheet Handling * 497 * * 498 **************************************************************************/ 499 500 private static final String DEFAULT_STYLE_CLASS = "list-cell"; 501} 502