1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package hello; 27 28 29 import java.text.NumberFormat; 30 import java.util.AbstractList; 31 import java.util.BitSet; 32 import java.util.Collection; 33 import java.util.Comparator; 34 import java.util.Date; 35 import java.util.HashMap; 36 import java.util.Map; 37 import javafx.animation.Interpolator; 38 import javafx.animation.KeyFrame; 39 import javafx.animation.KeyValue; 40 import javafx.animation.Timeline; 41 42 import javafx.application.Application; 43 import javafx.beans.InvalidationListener; 44 import javafx.beans.Observable; 45 import javafx.beans.value.ChangeListener; 46 import javafx.beans.value.ObservableValue; 47 import javafx.collections.FXCollections; 48 import javafx.collections.ListChangeListener; 49 import javafx.collections.ObservableList; 50 import javafx.collections.transformation.SortedList; 51 import javafx.collections.transformation.FilteredList; 52 import javafx.event.ActionEvent; 53 import javafx.event.EventHandler; 54 import javafx.geometry.Insets; 55 import javafx.geometry.Orientation; 56 import javafx.scene.Group; 57 import javafx.scene.Node; 58 import javafx.scene.Scene; 59 import javafx.scene.control.Button; 60 import javafx.scene.control.ChoiceBox; 61 import javafx.scene.control.Label; 62 import javafx.scene.control.ListCell; 63 import javafx.scene.control.ListView; 64 import javafx.scene.control.ListView.EditEvent; 65 import javafx.scene.control.RadioButton; 66 import javafx.scene.control.SelectionMode; 67 import javafx.scene.control.Tab; 68 import javafx.scene.control.TabPane; 69 import javafx.scene.control.TextField; 70 import javafx.scene.control.ToggleGroup; 71 import javafx.scene.control.Tooltip; 72 import javafx.scene.control.cell.ChoiceBoxListCell; 73 import javafx.scene.control.cell.TextFieldListCell; 74 import javafx.scene.input.*; 75 import javafx.scene.layout.GridPane; 76 import javafx.scene.layout.Priority; 77 import javafx.scene.layout.VBox; 78 import javafx.scene.paint.Color; 79 import javafx.scene.text.Font; 80 import javafx.stage.Stage; 81 import javafx.util.Callback; 82 import javafx.util.Duration; 83 84 public class HelloListView extends Application implements InvalidationListener { 85 86 private static ObservableList<String> data = FXCollections.<String>observableArrayList(); 87 private static ObservableList<String> names = FXCollections.<String>observableArrayList(); 88 private static ObservableList<Number> money = FXCollections.<Number>observableArrayList(); 89 private static ObservableList<Map<String, String>> mapData = FXCollections.<Map<String, String>>observableArrayList(); 90 91 private static final String FIRST_NAME = "firstName"; 92 private static final String LAST_NAME = "lastName"; 93 94 static { 95 data.addAll("Row 1", "Row 2", 96 // "Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Row 3", 97 "Row 4", "Row 5", "Row 6", 98 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 99 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 100 101 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 102 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 103 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 104 105 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 106 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 107 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 108 109 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 110 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 111 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 112 113 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 114 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 115 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 116 117 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 118 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 119 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 120 121 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 122 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 123 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "===== Row 20 ====", 124 125 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 126 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 127 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 128 129 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 130 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 131 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20", 132 133 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6", 134 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13", 135 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20" 136 ); 137 138 names.addAll( 139 // Top 100 boys names for 2010, taken from 140 // http://www.babycenter.com/top-baby-names-2010 141 "Aiden", "Jacob", "Jackson", "Ethan", "Jayden", "Noah", "Logan", "Caden", 142 // "Lucas", "Liam", "Mason", "Caleb", "Jack", "Brayden", "Connor", "Ryan", 143 // "Matthew", "Michael", "Alexander", "Landon,Nicholas", "Nathan", "Dylan", 144 // "Evan", "Benjamin", "Andrew", "Joshua", "Luke", "Gabriel", "William", 145 // "James", "Elijah", "Owen", "Tyler", "Gavin", "Carter", "Cameron", "Daniel", 146 // "Zachary", "Christian", "Joseph", "Wyatt", "Anthony", "Samuel", "Chase", 147 // "Max", "Isaac", "Christopher", "John", "Eli", "Austin", "Colton", 148 // "Hunter", "Tristan", "Jonathan", "David", "Alex", "Colin", "Dominic", 149 // "Cooper", "Henry", "Carson", "Isaiah", "Charlie", "Julian", "Grayson", 150 // "Cole", "Oliver", "Jordan", "Thomas", "Sean", "Brody", "Adam", "Levi", 151 // "Aaron", "Parker", "Sebastian", "Xavier", "Ian", "Miles", "Blake", "Jake", 152 // "Riley", "Jason", "Nathaniel", "Adrian", "Brandon", "Justin", "Nolan", 153 // "Jeremiah", "Hayden", "Devin", "Brady", "Robert", "Josiah", "Hudson", 154 // "Ryder", "Bryce", "Micah", "Sam", 155 // 156 // // Top 100 girls names for 2010, taken from 157 // // http://www.babycenter.com/top-baby-names-2010 158 "Sophia", "Isabella", "Olivia", "Emma", "Chloe", "Ava", "Lily", "Madison" 159 // "Addison", "Abigail", "Madelyn", "Emily", "Zoe", "Hailey", "Riley", 160 // "Ella", "Mia", "Kaitlyn", "Kaylee", "Peyton", "Layla,Avery", "Hannah", 161 // "Mackenzie", "Elizabeth", "Kylie", "Sarah", "Anna", "Grace", "Brooklyn", 162 // "Natalie", "Alyssa", "Alexis", "Aubrey", "Samantha", "Isabelle", 163 // "Arianna", "Charlotte,Makayla", "Claire", "Lillian", "Gabriella", 164 // "Lyla", "Amelia", "Sophie", "Aaliyah", "Taylor", "Audrey", "Bella", 165 // "Leah", "Allison", "Sydney", "Alana", "Maya", "Keira", "Lucy", "Kayla", 166 // "Lauren", "Savannah", "Brianna", "Ellie", "Reagan", "Evelyn", "Carly", 167 // "Julia", "Bailey", "Jordyn", "Victoria", "Annabelle", "Cadence", 168 // "Katherine", "Stella", "Molly", "Kennedy", "Jasmine,Gianna", "Abby", 169 // "Makenna", "Morgan", "Caroline", "Maria", "Brooke", "Nora", "Alexa", 170 // "Camryn", "Paige", "Eva", "Scarlett", "Adriana", "Juliana", "Ashlyn", 171 // "Megan", "Kendall", "Harper", "Jada", "Violet", "Alexandra", "Gracie", 172 // "Nevaeh", "Sadie" 173 ); 174 175 money.addAll(43.68, 102.35, -23.67, 110.23, -43.93, 87.21); 176 177 Map<String, String> map1 = new HashMap<String, String>(); 178 map1.put(FIRST_NAME, "Jonathan"); 179 map1.put(LAST_NAME, "Giles"); 180 Map<String, String> map2 = new HashMap<String, String>(); 181 map2.put(FIRST_NAME, "Brian"); 182 map2.put(LAST_NAME, "Beck"); 183 Map<String, String> map3 = new HashMap<String, String>(); 184 map3.put(FIRST_NAME, "Richard"); 185 map3.put(LAST_NAME, "Bair"); 186 Map<String, String> map4 = new HashMap<String, String>(); 187 map4.put(FIRST_NAME, "Jasper"); 188 map4.put(LAST_NAME, "Potts"); 189 Map<String, String> map5 = new HashMap<String, String>(); 190 map5.put(FIRST_NAME, "Will"); 191 map5.put(LAST_NAME, "Walker"); 192 mapData.addAll(map1, map2, map3, map4, map5); 193 } 194 195 /** 196 * @param args the command line arguments 197 */ 198 public static void main(String[] args) { 199 Application.launch(args); 200 } 201 202 @Override public void start(Stage stage) { 203 stage.setTitle("Hello ListView"); 204 final Scene scene = new Scene(new Group(), 875, 700); 205 scene.setFill(Color.LIGHTGRAY); 206 Group root = (Group)scene.getRoot(); 207 208 209 // TabPane 210 final TabPane tabPane = new TabPane(); 211 tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); 212 tabPane.setPrefWidth(scene.getWidth()); 213 tabPane.setPrefHeight(scene.getHeight()); 214 215 InvalidationListener sceneListener = new InvalidationListener() { 216 @Override public void invalidated(Observable ov) { 217 tabPane.setPrefWidth(scene.getWidth()); 218 tabPane.setPrefHeight(scene.getHeight()); 219 } 220 }; 221 scene.widthProperty().addListener(sceneListener); 222 scene.heightProperty().addListener(sceneListener); 223 224 // simple list view example 225 Tab simpleTab = new Tab("Simple"); 226 buildSimpleTab(simpleTab); 227 tabPane.getTabs().add(simpleTab); 228 229 // horizontal list view example 230 Tab horizontalTab = new Tab("Horizontal"); 231 buildHorizontalTab(horizontalTab); 232 tabPane.getTabs().add(horizontalTab); 233 234 // Cell Factory Tab 235 Tab cellFactoriesTab = new Tab("Cell Factories"); 236 buildCellFactoriesTab(cellFactoriesTab); 237 tabPane.getTabs().add(cellFactoriesTab); 238 239 // Cell Editing Tab 240 Tab cellEditingTab = new Tab("Cell Editing"); 241 buildCellEditingTab(cellEditingTab); 242 tabPane.getTabs().add(cellEditingTab); 243 244 // Cell Editing Tab 245 Tab disappearingNodesTab = new Tab("RT-12822"); 246 buildDisappearingNodesTab(disappearingNodesTab); 247 tabPane.getTabs().add(disappearingNodesTab); 248 249 // sorted and filtered list view example 250 Tab sortAndFilterTab = new Tab("Sort & Filter"); 251 buildSortAndFilterTab(sortAndFilterTab); 252 tabPane.getTabs().add(sortAndFilterTab); 253 254 // big list view example 255 Tab bigListTab = new Tab("Big List"); 256 buildBigListTab(bigListTab); 257 tabPane.getTabs().add(bigListTab); 258 259 // big DnD example 260 Tab dndTab = new Tab("DnD"); 261 buildDndTab(dndTab); 262 tabPane.getTabs().add(dndTab); 263 264 root.getChildren().add(tabPane); 265 266 stage.setScene(scene); 267 stage.show(); 268 } 269 270 public void invalidated(Observable observable) { 271 System.out.println("Event: " + observable); 272 } 273 274 private void buildSimpleTab(Tab tab) { 275 GridPane grid = new GridPane(); 276 // grid.setGridLinesVisible(true); 277 grid.setPadding(new Insets(5, 5, 5, 5)); 278 grid.setHgap(5); 279 grid.setVgap(5); 280 281 // simple list view 282 final ListView<String> listView = new ListView<String>(); 283 listView.setItems(data); 284 listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); 285 286 listView.setOnEditStart(new EventHandler<EditEvent<String>>() { 287 @Override public void handle(EditEvent<String> t) { 288 System.out.println("Edit Start: " + t.getIndex()); 289 } 290 }); 291 listView.setOnEditCancel(new EventHandler<EditEvent<String>>() { 292 @Override public void handle(EditEvent<String> t) { 293 System.out.println("Edit Cancel: " + t.getIndex()); 294 } 295 }); 296 listView.setOnEditCommit(new EventHandler<EditEvent<String>>() { 297 @Override public void handle(EditEvent<String> t) { 298 System.out.println("Edit Commit: " + t.getIndex()); 299 } 300 }); 301 302 grid.add(listView, 0, 0, 1, 10); 303 GridPane.setVgrow(listView, Priority.ALWAYS); 304 GridPane.setHgrow(listView, Priority.ALWAYS); 305 // --- simple listview 306 307 308 // control buttons 309 Button row5btn = new Button("Select 'Row 5'"); 310 row5btn.setOnAction(new EventHandler<ActionEvent>() { 311 @Override public void handle(ActionEvent e) { 312 listView.getSelectionModel().clearSelection(); 313 listView.getSelectionModel().select("Row 5"); 314 } 315 }); 316 grid.add(row5btn, 1, 0); 317 318 Button deselectRow5btn = new Button("Deselect item in 5th row"); 319 deselectRow5btn.setOnAction(new EventHandler<ActionEvent>() { 320 @Override public void handle(ActionEvent e) { 321 listView.getSelectionModel().clearSelection(4); 322 } 323 }); 324 grid.getChildren().add(deselectRow5btn); 325 GridPane.setConstraints(deselectRow5btn, 1, 1); 326 327 328 Button row20focusBtn = new Button("Focus on item in 20th row"); 329 row20focusBtn.setOnAction(new EventHandler<ActionEvent>() { 330 @Override public void handle(ActionEvent e) { 331 listView.getFocusModel().focus(19); 332 } 333 }); 334 grid.getChildren().add(row20focusBtn); 335 GridPane.setConstraints(row20focusBtn, 1, 2); 336 337 Button insertBeforeRow1btn = new Button("Add row before 0th row"); 338 insertBeforeRow1btn.setOnAction(new EventHandler<ActionEvent>() { 339 @Override public void handle(ActionEvent e) { 340 data.add(0, new Date().toString()); 341 } 342 }); 343 grid.getChildren().add(insertBeforeRow1btn); 344 GridPane.setConstraints(insertBeforeRow1btn, 1, 3); 345 346 Button insertBeforeRow5btn = new Button("Add row before 5th row"); 347 insertBeforeRow5btn.setOnAction(new EventHandler<ActionEvent>() { 348 @Override public void handle(ActionEvent e) { 349 data.add(5, new Date().toString()); 350 } 351 }); 352 grid.getChildren().add(insertBeforeRow5btn); 353 GridPane.setConstraints(insertBeforeRow5btn, 1, 4); 354 355 Button delete0thRow = new Button("Delete 0th row"); 356 delete0thRow.setOnAction(new EventHandler<ActionEvent>() { 357 @Override public void handle(ActionEvent e) { 358 data.remove(0); 359 } 360 }); 361 grid.getChildren().add(delete0thRow); 362 GridPane.setConstraints(delete0thRow, 1, 5); 363 364 Button delete5thRow = new Button("Delete 5th row"); 365 delete5thRow.setOnAction(new EventHandler<ActionEvent>() { 366 @Override public void handle(ActionEvent e) { 367 data.remove(4); 368 } 369 }); 370 grid.getChildren().add(delete5thRow); 371 GridPane.setConstraints(delete5thRow, 1, 6); 372 373 Button moveToRow40btn = new Button("Move to row 40"); 374 moveToRow40btn.setOnAction(new EventHandler<ActionEvent>() { 375 @Override public void handle(ActionEvent e) { 376 listView.scrollTo(39); 377 } 378 }); 379 grid.getChildren().add(moveToRow40btn); 380 GridPane.setConstraints(moveToRow40btn, 1, 8); 381 382 tab.setContent(grid); 383 384 // 385 // observation code for debugging the simple list view multiple selection 386 listView.getSelectionModel().selectedIndexProperty().addListener(new InvalidationListener() { 387 public void invalidated(Observable ov) { 388 System.out.println("SelectedIndex: " + listView.getSelectionModel().getSelectedIndex()); 389 } 390 }); 391 //// listView.getSelectionModel().selectedItemProperty().addListener(new InvalidationListener() { 392 //// public void invalidated(ObservableValue ov) { 393 //// System.out.println("\tSelectedItem: " + listView.getSelectionModel().getSelectedItem()); 394 //// } 395 //// }); 396 //// listView.getFocusModel().focusedIndexProperty().addListener(new InvalidationListener() { 397 //// public void invalidated(ObservableValue ov) { 398 //// System.out.println("\tFocusedIndex: " + listView.getFocusModel().getFocusedIndex()); 399 //// } 400 //// }); 401 //// listView.getFocusModel().focusedItemProperty().addListener(new InvalidationListener() { 402 //// public void invalidated(ObservableValue ov) { 403 //// System.out.println("\tFocusedItem: " + listView.getFocusModel().getFocusedItem()); 404 //// } 405 //// }); 406 ////// listView.getFocusModel().addInvalidationListener(FocusModel.FOCUSED_ITEM, this); 407 //// 408 listView.getSelectionModel().getSelectedIndices().addListener(new ListChangeListener<Integer>() { 409 public void onChanged(Change<? extends Integer> change) { 410 while (change.next()) { 411 System.out.println("SelectedIndices: " + change.getList() + 412 ", removed: " + change.getRemoved() + 413 ", addedFrom: " + change.getFrom() + 414 ", addedTo: " + change.getTo()); 415 } 416 } 417 }); 418 //// ((MultipleSelectionModel)listView.getSelectionModel()).getSelectedItems().addListener(new ListChangeListener<String>() { 419 //// public void onChanged(Change<? extends String> c) { 420 //// System.out.println("SelectedIndices: " + c.getList() + 421 //// ", removed: " + c.getRemoved() + 422 //// ", addedFrom: " + c.getFrom() + 423 //// ", addedTo: " + c.getTo()); 424 //// } 425 //// }); 426 } 427 428 private void buildHorizontalTab(Tab tab) { 429 GridPane grid = new GridPane(); 430 grid.setPadding(new Insets(5, 5, 5, 5)); 431 grid.setHgap(5); 432 grid.setVgap(5); 433 434 // simple list view with content 435 ListView horizontalListView = new ListView(); 436 horizontalListView.setOrientation(Orientation.HORIZONTAL); 437 horizontalListView.setItems(data); 438 439 grid.add(horizontalListView, 0, 0); 440 GridPane.setVgrow(horizontalListView, Priority.ALWAYS); 441 GridPane.setHgrow(horizontalListView, Priority.ALWAYS); 442 443 // simple list view with content 444 ListView emptyListView = new ListView(); 445 emptyListView.setOrientation(Orientation.HORIZONTAL); 446 447 grid.add(emptyListView, 1, 0); 448 GridPane.setVgrow(emptyListView, Priority.ALWAYS); 449 GridPane.setHgrow(emptyListView, Priority.ALWAYS); 450 451 tab.setContent(grid); 452 } 453 454 private void buildCellFactoriesTab(Tab tab) { 455 GridPane grid = new GridPane(); 456 grid.setPadding(new Insets(5, 5, 5, 5)); 457 grid.setHgap(5); 458 grid.setVgap(5); 459 460 // number format listview 461 final ListView<Number> listView = new ListView<Number>(money); 462 listView.setCellFactory(new Callback<ListView<Number>, ListCell<Number>>() { 463 @Override public ListCell<Number> call(ListView<Number> list) { 464 return new MoneyFormatCell(); 465 } 466 }); 467 grid.add(listView, 0, 0); 468 GridPane.setVgrow(listView, Priority.ALWAYS); 469 GridPane.setHgrow(listView, Priority.ALWAYS); 470 // --- number format listview 471 472 473 // expanding cells listview 474 final ListView<String> listView2 = new ListView<String>(data); 475 listView2.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 476 @Override public ListCell<String> call(ListView<String> list) { 477 return new ExpandOnSelectionCell<String>(); 478 } 479 }); 480 grid.add(listView2, 1, 0); 481 GridPane.setVgrow(listView2, Priority.ALWAYS); 482 GridPane.setHgrow(listView2, Priority.ALWAYS); 483 // --- expanding cells listview 484 485 486 tab.setContent(grid); 487 } 488 489 private void buildDisappearingNodesTab(Tab tab) { 490 GridPane grid = new GridPane(); 491 grid.setPadding(new Insets(5, 5, 5, 5)); 492 grid.setHgap(5); 493 grid.setVgap(5); 494 495 // Create a ListView<Button> 496 ListView<Button> listView = new ListView<Button>(); 497 listView.setPrefWidth(100.0F); 498 listView.setPrefHeight(100.0F); 499 Button button = new Button("Apples"); 500 Tooltip tooltip = new Tooltip(); 501 tooltip.setText("Tooltip Apples"); 502 button.setTooltip(tooltip); 503 Button button2 = new Button("Oranges"); 504 Tooltip tooltip2 = new Tooltip(); 505 tooltip2.setText("Tooltip Oranges"); 506 button2.setTooltip(tooltip2); 507 Button button3 = new Button("Peaches"); 508 Tooltip tooltip3 = new Tooltip(); 509 tooltip3.setText("Tooltip Peaches"); 510 button3.setTooltip(tooltip3); 511 Button button4 = new Button("Plums"); 512 Tooltip tooltip4 = new Tooltip(); 513 tooltip4.setText("Tooltip Plums"); 514 button4.setTooltip(tooltip4); 515 Button button5 = new Button("Apricots"); 516 Tooltip tooltip5 = new Tooltip(); 517 tooltip5.setText("Tooltip Apricots"); 518 button5.setTooltip(tooltip5); 519 Button button6 = new Button("Lemons"); 520 Tooltip tooltip6 = new Tooltip(); 521 tooltip6.setText("Tooltip Lemons"); 522 button6.setTooltip(tooltip6); 523 Button button7 = new Button("Grapefruit"); 524 Tooltip tooltip7 = new Tooltip(); 525 tooltip7.setText("Tooltip Grapefruit"); 526 button7.setTooltip(tooltip7); 527 Button button8 = new Button("Cherries"); 528 Tooltip tooltip8 = new Tooltip(); 529 tooltip8.setText("Tooltip Cherries"); 530 button8.setTooltip(tooltip8); 531 listView.setItems(javafx.collections.FXCollections.observableArrayList( 532 button, button2, button3, button4, button5, button6, button7, button8)); 533 534 grid.add(listView, 0, 0, 1, 10); 535 tab.setContent(grid); 536 } 537 538 private void buildCellEditingTab(Tab tab) { 539 GridPane grid = new GridPane(); 540 grid.setPadding(new Insets(5, 5, 5, 5)); 541 grid.setHgap(5); 542 grid.setVgap(5); 543 544 // simple textfield list view 545 final ListView<String> textFieldListView = new ListView<String>(); 546 textFieldListView.setEditable(true); 547 textFieldListView.setItems(data); 548 textFieldListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); 549 textFieldListView.setCellFactory(TextFieldListCell.forListView()); 550 textFieldListView.setOnEditStart(new EventHandler<EditEvent<String>>() { 551 @Override public void handle(EditEvent<String> t) { 552 System.out.println("On Edit Start: " + t); 553 } 554 }); 555 textFieldListView.setOnEditCancel(new EventHandler<EditEvent<String>>() { 556 @Override public void handle(EditEvent<String> t) { 557 System.out.println("On Edit Cancel: " + t); 558 } 559 }); 560 grid.add(textFieldListView, 0, 0, 1, 10); 561 GridPane.setVgrow(textFieldListView, Priority.ALWAYS); 562 GridPane.setHgrow(textFieldListView, Priority.ALWAYS); 563 // --- simple listview 564 565 // simple choicebox list view 566 final ObservableList<String> options = FXCollections.observableArrayList("Jenny", "Billy", "Timmy"); 567 final ListView<String> choiceBoxListView = new ListView<String>(); 568 choiceBoxListView.setEditable(true); 569 choiceBoxListView.setItems(data); 570 choiceBoxListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); 571 choiceBoxListView.setCellFactory(ChoiceBoxListCell.forListView(options)); 572 grid.add(choiceBoxListView, 1, 0, 1, 10); 573 GridPane.setVgrow(choiceBoxListView, Priority.ALWAYS); 574 GridPane.setHgrow(choiceBoxListView, Priority.ALWAYS); 575 // --- simple listview 576 577 578 // control buttons 579 final Button editRow3btn = new Button("Edit row 3"); 580 editRow3btn.setOnAction(new EventHandler<ActionEvent>() { 581 public void handle(ActionEvent t) { 582 textFieldListView.edit(2); 583 choiceBoxListView.edit(2); 584 } 585 }); 586 grid.getChildren().add(editRow3btn); 587 GridPane.setConstraints(editRow3btn, 2, 0); 588 589 final Button cancelEditBtn = new Button("Cancel edit"); 590 cancelEditBtn.setOnAction(new EventHandler<ActionEvent>() { 591 public void handle(ActionEvent t) { 592 textFieldListView.edit(-1); 593 choiceBoxListView.edit(-1); 594 } 595 }); 596 grid.getChildren().add(cancelEditBtn); 597 GridPane.setConstraints(cancelEditBtn, 2, 1); 598 599 tab.setContent(grid); 600 } 601 602 // private void buildCellsTab(Tab tab) { 603 // GridPane grid = new GridPane(); 604 // grid.setPadding(new Insets(5, 5, 5, 5)); 605 // grid.setHgap(5); 606 // grid.setVgap(5); 607 // 608 // // add a complex listview (using pre-built cell factory) 609 // final ListView<Number> listView2 = new ListView<Number>(); 610 // listView2.setItems(money); 611 // listView2.setCellFactory(MoneyFormatCellFactory.listView()); 612 // grid.getChildren().add(listView2); 613 // GridPane.setVgrow(listView2, Priority.ALWAYS); 614 // GridPane.setConstraints(listView2, 0, 0); 615 // // --- complex listview 616 // 617 // // add another complex listview (using pre-built cell factory) 618 // final ListView<Map<String, String>> listView3 = new ListView<Map<String, String>>(); 619 // listView3.setItems(mapData); 620 //// listView3.setCellFactory(Cells.ListView.mapProperty(FIRST_NAME)); 621 // listView3.setCellFactory(MapValueCellFactory.listView("First Name: %1$s\r\nLast Name: %2$s", FIRST_NAME, LAST_NAME)); 622 // grid.getChildren().add(listView3); 623 // GridPane.setVgrow(listView3, Priority.ALWAYS); 624 // GridPane.setConstraints(listView3, 1, 0); 625 // // --- complex listview 626 // 627 // tab.setContent(grid); 628 // } 629 630 631 private Comparator<String> alphabeticalComparator = new Comparator<String>() { 632 @Override public int compare(String o1, String o2) { 633 return o1.compareTo(o2); 634 } 635 }; 636 637 private Comparator<String> reverseAlphabeticalComparator = new Comparator<String>() { 638 @Override public int compare(String o1, String o2) { 639 return o2.compareTo(o1); 640 } 641 }; 642 643 private void buildSortAndFilterTab(Tab tab) { 644 // initially we match everything in the filter list 645 646 final SortedList<String> sortedList = new SortedList<String>(names); 647 final FilteredList<String> filteredList = new FilteredList<String>(sortedList, e -> true); 648 649 GridPane grid = new GridPane(); 650 grid.setPadding(new Insets(5, 5, 5, 5)); 651 grid.setHgap(5); 652 grid.setVgap(5); 653 654 // --- unmodified listview 655 final ListView<String> unmodifiedListView = new ListView<String>(); 656 unmodifiedListView.setId("Unmodified list"); 657 unmodifiedListView.setItems(names); 658 // unmodifiedListView.setCellFactory(TextFieldCellFactory.listView()); 659 Node unmodifiedLabel = createLabel("Original ListView:"); 660 grid.getChildren().addAll(unmodifiedLabel, unmodifiedListView); 661 GridPane.setConstraints(unmodifiedLabel, 0, 0); 662 GridPane.setConstraints(unmodifiedListView, 0, 1); 663 GridPane.setVgrow(unmodifiedListView, Priority.ALWAYS); 664 // --- unmodified listview 665 666 667 // --- sorted listview 668 final ListView<String> sortedListView = new ListView<String>(); 669 sortedListView.setId("sorted list"); 670 sortedListView.setItems(sortedList); 671 // sortedListView.setCellFactory(TextFieldCellFactory.listView()); 672 Node sortedLabel = createLabel("Sorted ListView:"); 673 grid.getChildren().addAll(sortedLabel, sortedListView); 674 GridPane.setConstraints(sortedLabel, 1, 0); 675 GridPane.setConstraints(sortedListView, 1, 1); 676 GridPane.setVgrow(sortedListView, Priority.ALWAYS); 677 // --- sorted listview 678 679 680 // --- filtered listview 681 final ListView<String> filteredListView = new ListView<String>(); 682 filteredListView.setId("filtered list"); 683 filteredListView.setItems(filteredList); 684 // filteredListView.setCellFactory(TextFieldCellFactory.listView()); 685 Node filteredLabel = createLabel("Filtered (and sorted) ListView:"); 686 grid.getChildren().addAll(filteredLabel, filteredListView); 687 GridPane.setConstraints(filteredLabel, 2, 0); 688 GridPane.setConstraints(filteredListView, 2, 1); 689 GridPane.setVgrow(filteredListView, Priority.ALWAYS); 690 // --- filtered listview 691 692 693 // control buttons 694 VBox vbox = new VBox(10); 695 696 vbox.getChildren().add(new Label("Note: Double-click list cells to edit.")); 697 698 final TextField filterInput = new TextField(); 699 filterInput.setPromptText("Enter filter text"); 700 // filterInput.setColumns(35); 701 filterInput.setOnKeyReleased(new EventHandler<KeyEvent>() { 702 public void handle(KeyEvent t) { 703 filteredList.setPredicate((String e) -> 704 e.toUpperCase().contains(filterInput.getText().toUpperCase())); 705 } 706 }); 707 vbox.getChildren().add(filterInput); 708 709 final TextField newItemInput = new TextField(); 710 newItemInput.setPromptText("Enter text, then press enter to add item to list"); 711 // newItemInput.setColumns(35); 712 newItemInput.setOnKeyReleased(new EventHandler<KeyEvent>() { 713 public void handle(KeyEvent t) { 714 if (t.getCode() == KeyCode.ENTER) { 715 names.add(newItemInput.getText()); 716 newItemInput.setText(""); 717 } 718 } 719 }); 720 vbox.getChildren().add(newItemInput); 721 722 // sort ascending 723 final ToggleGroup toggleGroup = new ToggleGroup(); 724 toggleGroup.selectedToggleProperty().addListener(new InvalidationListener() { 725 public void invalidated(Observable ov) { 726 if (toggleGroup.getSelectedToggle() == null) return; 727 sortedList.setComparator((Comparator<String>)toggleGroup.getSelectedToggle().getUserData()); 728 } 729 }); 730 final RadioButton sortAscBtn = new RadioButton("Sort Ascending"); 731 sortAscBtn.setUserData(alphabeticalComparator); 732 sortAscBtn.setToggleGroup(toggleGroup); 733 sortAscBtn.setSelected(true); 734 735 final RadioButton sortDescBtn = new RadioButton("Sort Descending"); 736 sortDescBtn.setUserData(reverseAlphabeticalComparator); 737 sortDescBtn.setToggleGroup(toggleGroup); 738 739 vbox.getChildren().addAll(sortAscBtn, sortDescBtn); 740 741 grid.setConstraints(vbox, 3, 1); 742 grid.getChildren().add(vbox); 743 744 tab.setContent(grid); 745 } 746 747 private void buildBigListTab(Tab tab) { 748 GridPane grid = new GridPane(); 749 grid.setPadding(new Insets(5, 5, 5, 5)); 750 grid.setHgap(5); 751 grid.setVgap(5); 752 753 BigList intList = new BigList(); 754 755 // simple list view 756 final ListView<Integer> listView = new ListView<Integer>(); 757 listView.setItems(intList); 758 // listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION); 759 grid.add(listView, 0, 0); 760 GridPane.setVgrow(listView, Priority.ALWAYS); 761 GridPane.setHgrow(listView, Priority.ALWAYS); 762 763 tab.setContent(grid); 764 } 765 766 private void buildDndTab(Tab tab) { 767 GridPane grid = new GridPane(); 768 grid.setPadding(new Insets(5, 5, 5, 5)); 769 grid.setHgap(5); 770 grid.setVgap(5); 771 772 ObservableList<String> listOneItems = FXCollections.observableArrayList(names.subList(0, 8)); 773 ObservableList<String> listTwoItems = FXCollections.observableArrayList(names.subList(8, 16)); 774 775 Label introLabel = new Label("On Windows: DnD is a MOVE, hold ctrl/cmd whilst dragging for COPY.\n" 776 +"On Mac: DnD is a COPY. With any modifier held, no DnD operation is performed."); 777 introLabel.setWrapText(true); 778 introLabel.setFont(Font.font(14)); 779 grid.add(introLabel, 0, 0, 2, 1); 780 781 // --- list one 782 final ListView<String> listOne = new ListView<String>(); 783 listOne.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); 784 listOne.setItems(listOneItems); 785 Node listOneLabel = createLabel("List One:"); 786 grid.getChildren().addAll(listOneLabel, listOne); 787 GridPane.setConstraints(listOneLabel, 0, 1); 788 GridPane.setConstraints(listOne, 0, 2); 789 GridPane.setVgrow(listOne, Priority.ALWAYS); 790 // --- list one 791 792 // --- list two 793 final ListView<String> listTwo = new ListView<String>(); 794 listTwo.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); 795 listTwo.setItems(listTwoItems); 796 Node listTwoLabel = createLabel("List Two:"); 797 grid.getChildren().addAll(listTwoLabel, listTwo); 798 GridPane.setConstraints(listTwoLabel, 1, 1); 799 GridPane.setConstraints(listTwo, 1, 2); 800 GridPane.setVgrow(listTwo, Priority.ALWAYS); 801 // --- list two 802 803 // set up Dnd in both directions 804 EventHandler<MouseEvent> dragDetected = new EventHandler<MouseEvent>() { 805 public void handle(MouseEvent event) { 806 ListView<String> list = (ListView) event.getSource(); 807 Dragboard db = list.startDragAndDrop(TransferMode.ANY); 808 809 ClipboardContent content = new ClipboardContent(); 810 content.putString(list.getSelectionModel().getSelectedItem()); 811 db.setContent(content); 812 813 event.consume(); 814 } 815 }; 816 EventHandler<DragEvent> dragOver = new EventHandler<DragEvent>() { 817 public void handle(DragEvent event) { 818 if (event.getGestureSource() != event.getTarget() && event.getDragboard().hasString()) { 819 event.acceptTransferModes(TransferMode.COPY_OR_MOVE); 820 } 821 822 event.consume(); 823 } 824 }; 825 EventHandler<DragEvent> dragDropped = new EventHandler<DragEvent>() { 826 public void handle(DragEvent event) { 827 ListView<String> list = (ListView) event.getGestureTarget(); 828 829 Dragboard db = event.getDragboard(); 830 boolean success = false; 831 if (db.hasString()) { 832 list.getItems().add(db.getString()); 833 success = true; 834 } 835 836 event.setDropCompleted(success); 837 event.consume(); 838 } 839 }; 840 EventHandler<DragEvent> dragDone = new EventHandler<DragEvent>() { 841 public void handle(DragEvent event) { 842 if (event.getTransferMode() == TransferMode.MOVE) { 843 ListView<String> list = (ListView) event.getGestureSource(); 844 list.getItems().remove(event.getDragboard().getString()); 845 } 846 event.consume(); 847 } 848 }; 849 850 listOne.setOnDragDetected(dragDetected); 851 listOne.setOnDragOver(dragOver); 852 listOne.setOnDragDropped(dragDropped); 853 listOne.setOnDragDone(dragDone); 854 855 listTwo.setOnDragDetected(dragDetected); 856 listTwo.setOnDragOver(dragOver); 857 listTwo.setOnDragDropped(dragDropped); 858 listTwo.setOnDragDone(dragDone); 859 860 tab.setContent(grid); 861 } 862 863 private Node createLabel(String text) { 864 Label label = new Label(text); 865 return label; 866 } 867 868 // public void handle(Bean bean, PropertyReference<?> pr) { 869 // SelectionModel sm = null; 870 // FocusModel fm = null; 871 // if (bean instanceof SelectionModel) { 872 // System.out.print("Selection Event: "); 873 // sm = (SelectionModel) bean; 874 // } else if (bean instanceof FocusModel) { 875 // System.out.print("Focus Event: "); 876 // fm = (FocusModel) bean; 877 // } 878 // 879 // 880 // if (pr == SelectionModel.SELECTED_INDEX) { 881 // System.out.println("\tSelectedIndex: " + sm.getSelectedIndex()); 882 // } else if (pr == SelectionModel.SELECTED_ITEM) { 883 // System.out.println("\tSelectedItem: " + sm.getSelectedItem()); 884 // } else if (pr == FocusModel.FOCUSED_INDEX) { 885 // System.out.println("\tFocusedIndex: " + fm.getFocusedIndex()); 886 // } else if (pr == FocusModel.FOCUSED_ITEM) { 887 // System.out.println("\tFocusedItem: " + fm.getFocusedItem()); 888 // } 889 // } 890 891 private static class BigList extends AbstractList<Integer> implements ObservableList<Integer> { 892 893 @Override 894 public Integer get(int index) { 895 return index; 896 } 897 898 @Override 899 public int size() { 900 // We have to be somewhat conscious of memory use, in relation to 901 // the ListView selection model, which is currently implemented as a 902 // BitSet. This means that if we have too many items in a list 903 // (e.g Integer.MAX_VALUE) and we select an item towards the end of 904 // the list, we will get an OOME as we require a bit for every item 905 // up to the selected item (although they would all be 0/false, it 906 // still costs memory). Therefore, we limit the size of the list 907 // to prevent this from being a concern. 908 // A future improvement would be to use a more memory-efficient 909 // selection model implementation when the number of items is large 910 // (e.g. a simple List<Integer>, rather than a BitSet). 911 return Integer.MAX_VALUE / 4; 912 } 913 914 @Override 915 public void addListener(InvalidationListener listener) { 916 // no-op 917 } 918 919 @Override 920 public void removeListener(InvalidationListener listener) { 921 // no-op 922 } 923 924 public void addListener(ListChangeListener<? super Integer> ll) { 925 // no-op 926 } 927 928 public void removeListener(ListChangeListener<? super Integer> ll) { 929 // no-op 930 } 931 932 public boolean addAll(Integer... es) { 933 throw new UnsupportedOperationException("Not supported yet."); 934 } 935 936 public boolean setAll(Integer... es) { 937 throw new UnsupportedOperationException("Not supported yet."); 938 } 939 940 public boolean setAll(Collection<? extends Integer> clctn) { 941 throw new UnsupportedOperationException("Not supported yet."); 942 } 943 944 public boolean removeAll(Integer... es) { 945 throw new UnsupportedOperationException("Not supported yet."); 946 } 947 948 public boolean retainAll(Integer... es) { 949 throw new UnsupportedOperationException("Not supported yet."); 950 } 951 952 public void remove(int from, int to) { 953 throw new UnsupportedOperationException("Not supported yet."); 954 } 955 956 } 957 958 private static class MoneyFormatCell extends ListCell<Number> { 959 @Override 960 public void updateItem(Number item, boolean empty) { 961 super.updateItem(item, empty); 962 963 // format the number as if it were a monetary value using the 964 // formatting relevant to the current locale. This would format 965 // 43.68 as "$43.68", and -23.67 as "-$23.67" 966 setText(item == null ? "" : NumberFormat.getCurrencyInstance().format(item)); 967 968 // change the label colour based on whether it is positive (green) 969 // or negative (red). If the cell is selected, the text will 970 // always be white (so that it can be read against the blue 971 // background), and if the value is zero, we'll make it black. 972 if (item != null) { 973 double value = item.doubleValue(); 974 setTextFill(isSelected() ? Color.WHITE : 975 value == 0 ? Color.BLACK : 976 value < 0 ? Color.RED : Color.GREEN); 977 } 978 } 979 } 980 981 982 983 private static class ExpandOnSelectionCell<T> extends ListCell<T> { 984 private static final double PREF_HEIGHT = 24; 985 private static final double EXPAND_HEIGHT = PREF_HEIGHT * 3; 986 987 private static final int duration = 350; 988 989 // records all expanded cells 990 private static final BitSet expandedCells = new BitSet(); 991 992 993 public ExpandOnSelectionCell() { 994 995 prefHeightProperty().addListener(new InvalidationListener() { 996 public void invalidated(Observable o) { 997 requestLayout(); 998 } 999 }); 1000 1001 addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 1002 @Override public void handle(MouseEvent t) { 1003 1004 double startHeight = getHeight(); 1005 1006 // the end height is the opposite of the current state - 1007 // we are animating out of this state after all 1008 double endHeight = isExpanded() ? PREF_HEIGHT : EXPAND_HEIGHT; 1009 1010 // flip whether this cell index is expanded or not 1011 expandedCells.flip(getIndex()); 1012 1013 // create a timeline to expand/collapse the cell. All this 1014 // really does is modify the height of the content 1015 Timeline timeline = new Timeline(); 1016 timeline.setCycleCount(1); 1017 timeline.setAutoReverse(false); 1018 1019 timeline.getKeyFrames().addAll( 1020 new KeyFrame(Duration.ZERO, new KeyValue(prefHeightProperty(), startHeight, Interpolator.EASE_BOTH)), 1021 new KeyFrame(Duration.millis(duration), new KeyValue(prefHeightProperty(), endHeight, Interpolator.EASE_BOTH)) 1022 ); 1023 1024 timeline.playFromStart(); 1025 } 1026 }); 1027 } 1028 1029 @Override 1030 public void updateItem(T item, boolean empty) { 1031 super.updateItem(item, empty); 1032 setText(item == null ? "null" : item.toString()); 1033 } 1034 1035 @Override 1036 public void updateIndex(int i) { 1037 super.updateIndex(i); 1038 1039 if (isExpanded()) { 1040 // immediately expand this cell 1041 setPrefHeight(EXPAND_HEIGHT); 1042 } else { 1043 // immediately collapse this cell 1044 setPrefHeight(PREF_HEIGHT); 1045 } 1046 } 1047 1048 private boolean isExpanded() { 1049 if (getIndex() < 0) return false; 1050 return expandedCells.get(getIndex()); 1051 } 1052 1053 @Override 1054 protected double computePrefHeight(double width) { 1055 double ph = 0; 1056 if (isExpanded()) { 1057 ph = getPrefHeight(); 1058 } else { 1059 ph = super.computePrefHeight(width); 1060 } 1061 1062 return ph; 1063 } 1064 1065 @Override protected double computeMinHeight(double width) { 1066 return computePrefHeight(width); 1067 } 1068 } 1069 } 1070