All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jidefx.utils.LazyLoadUtils Maven / Gradle / Ivy

/*
 * @(#)LazyLoadUtils.java 5/19/2013
 *
 * Copyright 2002 - 2013 JIDE Software Inc. All rights reserved.
 */

package jidefx.utils;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.util.Callback;
import javafx.util.Duration;

/**
 * {@code LazyLoadUtils} provides an easy way to implement the lazy loading feature in a {@code Combobox} or a
 * {@code ChoiceBox}. Sometimes it takes a long time to create the ObservableList for the control. By using this
 * LazyLoadUtils, we will create the ObservableList only when the popup is about to show or after a delay, so that it
 * doesn't block the UI thread. The UI will come up without the ObservableList and will be set later.
 * 

* To use it, simply call one of the install methods. The callback will take care of the creation of the * ObservableList. *

* There are two ways to trigger the callback. The first trigger is when the ComboBox or the ChoiceBox is clicked before * the popup content is about to show. The beforeShowing flag will determine if this trigger will be triggered. Default * is true. If this trigger is triggered, we will call the callback on the UI thread which means users will have to wait * for the callback to finish. We will have to do that because the popup doesn't make sense to show without the * ObservableList. The second trigger is triggered after a delay. The delay time is controlled by the delay parameter. * When it reaches the specified delay duration, a worker thread will run to call the callback so that it doesn't block * the UI. Either trigger can come first but it will be triggered only once. Ideally, when the UI is shown, if user * never clicks the ComboBox or the ChoiceBox, the second trigger kicks in and populates the data behind the scene. Not * ideally, user clicks on it right away and then he/she has to wait a while. However it is still much better than * waiting for the same period of time before the UI showing. *

* A typical use case is to create a ComboBox that list all the fonts in the system. However the Font.getFamilies call * is expensive, especially the system has a lot of fonts. The following code will take care of it. *

{@code
 * ComboBox<String> fontComboBox = new ComboBox<>();
 * fontComboBox.setValue("Arial"); // set a default value without setting the Items
 * LazyLoadUtils.install(fontComboBox, new Callback<ComboBox<String>, ObservableList<String>>() {
 *     public ObservableList<String> call(ComboBox<String> comboBox) {
 *         return FXCollections.observableArrayList(Font.getFamilies());
 *     }
 * });
 * }
*/ public class LazyLoadUtils { private final static double DELAY = 200; private final static String PROPERTY_TASK = "LazyLoadUtils.Task"; //NON-NLS /** * Customizes the ComboBox to call the callback and setItems until user clicks on the ComboBox or after a 200 ms * delay. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ public static void install(ComboBox comboBox, Callback, ObservableList> callback) { install(comboBox, callback, Duration.millis(DELAY), true, null); } /** * Customizes the ComboBox to call the callback and setItems until user clicks on the ComboBox or after a delay. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ public static void install(ComboBox comboBox, Callback, ObservableList> callback, Duration delay) { install(comboBox, callback, delay, true, null); } /** * Customizes the ComboBox to call the callback and setItems until user clicks on the ComboBox (if beforeShowing is * true) or after a 200ms delay. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param beforeShowing whether to trigger the callback before the ComboBox popup is about to show, if the callback * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ public static void install(ComboBox comboBox, Callback, ObservableList> callback, boolean beforeShowing) { install(comboBox, callback, Duration.millis(DELAY), beforeShowing, null); } /** * Customizes the ComboBox to call the callback until user clicks on the ComboBox (if beforeShowing is true) or * after a delay. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ComboBox popup is about to show, if the callback * hasn't called yet. * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ public static void install(ComboBox comboBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing) { customizeComboBox(comboBox, callback, delay, beforeShowing, null); } /** * Customizes the ComboBox to call the callback until user clicks on the ComboBox (if beforeShowing is true) or * after a delay. It will also set the initialValue to the ComboBox. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ComboBox popup is about to show, if the callback * hasn't called yet. * @param initialValue the initial value. Null if the value has been set on the ComboBox. * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ public static void install(ComboBox comboBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing, T initialValue) { customizeComboBox(comboBox, callback, delay, beforeShowing, initialValue); } /** * Customizes the ComboBox to call the callback until user clicks on the ComboBox or after a delay. * * @param comboBox the ComboBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ComboBox popup is about to show, if the callback * hasn't called yet. * @param initialValue the initial value. Null if the value has been set on the ComboBox. * @param The type of the value that has been selected or otherwise entered in to this ComboBox. */ protected static void customizeComboBox(ComboBox comboBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing, T initialValue) { Task oldTask = getTask(comboBox); if (oldTask != null) { oldTask.cancel(true); } if (initialValue != null) comboBox.setValue(initialValue); Task> task = new Task>() { @Override protected ObservableList call() throws Exception { return callback.call(comboBox); } }; task.setOnSucceeded(new EventHandler() { @Override public void handle(WorkerStateEvent event) { T item = comboBox.getValue(); Object value = event.getSource().getValue(); if (value instanceof ObservableList) { //noinspection unchecked comboBox.setItems((ObservableList) value); //noinspection unchecked if (item == null || !((ObservableList) value).contains(item)) { comboBox.setValue(null); comboBox.getSelectionModel().select(0); } else { // trick in order to show the item. should be a bug in JavaFX combobox comboBox.setValue(null); comboBox.setValue(item); } } } }); // when user clicks on the combobox, we will retrieve the fonts, not using thread because we don't want the empty // list to show up. User has to wait for a while if the task takes a long time if (beforeShowing) { comboBox.setOnShowing(new EventHandler() { @Override public void handle(Event event) { if (!task.isRunning() && !task.isDone()) { task.run(); } comboBox.setOnShowing(null); } }); } // or use Timeline to get the font in a thread after 1 second, if user didn't click on the font combobox before that startTimer(delay, task); putTask(comboBox, task); } /** * Customizes the ChoiceBox to call the callback and setItems until user clicks on the ChoiceBox or after a 200 ms * delay. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ public static void install(ChoiceBox choiceBox, Callback, ObservableList> callback) { install(choiceBox, callback, Duration.millis(DELAY), true, null); } /** * Customizes the ChoiceBox to call the callback and setItems until user clicks on the ChoiceBox or after a delay. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ public static void install(ChoiceBox choiceBox, Callback, ObservableList> callback, Duration delay) { install(choiceBox, callback, delay, true, null); } /** * Customizes the ChoiceBox to call the callback and setItems until user clicks on the ChoiceBox (if beforeShowing * is true) or after a 200ms delay. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param beforeShowing whether to trigger the callback before the ChoiceBox popup is about to show, if the * callback * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ public static void install(ChoiceBox choiceBox, Callback, ObservableList> callback, boolean beforeShowing) { install(choiceBox, callback, Duration.millis(DELAY), beforeShowing, null); } /** * Customizes the ChoiceBox to call the callback until user clicks on the ChoiceBox (if beforeShowing is true) or * after a delay. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ChoiceBox popup is about to show, if the callback * hasn't called yet. * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ public static void install(ChoiceBox choiceBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing) { customizeChoiceBox(choiceBox, callback, delay, beforeShowing, null); } /** * Customizes the ChoiceBox to call the callback until user clicks on the ChoiceBox (if beforeShowing is true) or * after a delay. It will also set the initialValue to the ChoiceBox. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ChoiceBox popup is about to show, if the callback * hasn't called yet. * @param initialValue the initial value. Null if the value has been set on the ChoiceBox. * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ public static void install(ChoiceBox choiceBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing, T initialValue) { customizeChoiceBox(choiceBox, callback, delay, beforeShowing, initialValue); } /** * Customizes the ChoiceBox to call the callback until user clicks on the ChoiceBox or after a delay. * * @param choiceBox the ChoiceBox. * @param callback the callback to create the ObservableList. * @param delay the delay before the callback is called. * @param beforeShowing whether to trigger the callback before the ChoiceBox popup is about to show, if the callback * hasn't called yet. * @param initialValue the initial value. Null if the value has been set on the ChoiceBox. * @param The type of the value that has been selected or otherwise entered in to this ChoiceBox. */ protected static void customizeChoiceBox(ChoiceBox choiceBox, Callback, ObservableList> callback, Duration delay, boolean beforeShowing, T initialValue) { Task oldTask = getTask(choiceBox); if (oldTask != null) { oldTask.cancel(true); } if (initialValue != null) choiceBox.setValue(initialValue); Task> task = new Task>() { @Override protected ObservableList call() throws Exception { return callback.call(choiceBox); } }; task.setOnSucceeded(new EventHandler() { @Override public void handle(WorkerStateEvent event) { T item = choiceBox.getValue(); Object value = event.getSource().getValue(); if (value instanceof ObservableList) { //noinspection unchecked choiceBox.setItems((ObservableList) value); //noinspection unchecked if (item == null || !((ObservableList) value).contains(item)) { choiceBox.setValue(null); choiceBox.getSelectionModel().select(0); } else { // trick in order to show the item. should be a bug in JavaFX combobox choiceBox.setValue(null); choiceBox.setValue(item); } } } }); // when user clicks on the choicebox, we will retrieve the fonts, not using thread because we don't want the empty // list to show up. User has to wait for a while if the task takes a long time if (beforeShowing) { choiceBox.setOnContextMenuRequested(new EventHandler() { @Override public void handle(Event event) { if (!task.isRunning() && !task.isDone()) { task.run(); } choiceBox.setOnContextMenuRequested(null); } }); } // or use Timeline to get the font in a thread after 1 second, if user didn't click on the font choicebox before that startTimer(delay, task); putTask(choiceBox, task); } // common private static void startTimer(Duration delay, Task> task) { if (delay != null && !delay.isIndefinite()) { Timeline timeline = new Timeline(); timeline.getKeyFrames().add(new KeyFrame(delay, new EventHandler() { public void handle(ActionEvent t) { if (!task.isRunning() && !task.isDone()) { new Thread(task).start(); } } })); timeline.play(); } } private static Task getTask(Node node) { Object task = node.getProperties().get(PROPERTY_TASK); if (task instanceof Task) { return (Task) task; } else { return null; } } private static void putTask(Node node, Task task) { node.getProperties().put(PROPERTY_TASK, task); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy