Manage wait cursor for task

2019-09-09 19:44发布

  1. I'm outside the UI and wish to display a wait cursor while stuff is happening and using this basic pattern:

    on UI - primaryStage.scene.cursor = Cursor.WAIT try { do stuff off UI... } finally { on UI - primaryStage.scene.cursor = Cursor.DEFAULT }

While running I can start another process which completes quickly and the Cursor is restored before the first task completes.

I don't mind "waiting" while the first task completes, but I don't think this means doing the work on the UI thread? Is there any built in solution for this pattern provided in javafx?

  1. My tab contains 2 Combo Box. When I hit the 2nd Combo Box drop down, a WAIT cursor sometimes appears over the list even though the Cursor is currently DEFAULT state. If I move the mouse pointer outside/back on the list the cursor is correctly displayed as Default. Would this be a separate issue or somehow related?

VIEW

label 'From'
comboBox(items: bind(model.wcomboFromItemsProperty()), value: bind(model.wcomboFromProperty()), selectFromAction)
label 'To'
comboBox(items: bind(model.wcomboFromItemsProperty()), value: bind(model.wcomboToProperty()), selectToAction)

MODEL

@FXObservable ListElement wcomboFrom = new ListElement()
@FXObservable ListElement wcomboTo = new ListElement()
@FXObservable List wcomboFromItems = FXCollections.observableArrayList()
@FXObservable List wcomboToItems = FXCollections.observableArrayList()

final ObjectProperty<Cursor> CURSOR_DEFAULT = new SimpleObjectProperty<>(Cursor.DEFAULT)
final ObjectProperty<Cursor> CURSOR_WAIT = new SimpleObjectProperty<>(Cursor.WAIT)

CONTROLLER

//lifecycle
void onReadyStart(GriffonApplication application) {
    loadWindowData()
}

// both combo boxes contain the same items
protected void loadWindowData() {
    def list = [new ListElement(textValue: '')]
    list.addAll dataService.getData().collect {
        new ListElement(textValue: it.name, objectValue: it)
    }

    runInsideUIAsync {
        model.wcomboFromItems.addAll(list)
        model.wcomboToItems.addAll(list)
    }
}

void selectFrom() {
    performAction {
        gcListFrom = getControlList(model.wcomboFrom.objectValue)
        setTreeItems(model.wtreeGcFrom, gcListFrom, model.wcomboFrom)
        setTreeItems(model.wtreeGcTo, gcListTo, model.wcomboTo)
    }
}

void selectTo() {
    performAction {
        gcListTo = getControlList(model.wcomboTo.objectValue)
        setTreeItems(model.wtreeGcTo, gcListTo, model.wcomboTo)
    }
}

def performAction = {c ->
    Task<Void> t = new Task() {
        @Override protected Void call() {
            println "Running closure " + isUIThread()
            c.call()
        }
    }

    runInsideUISync {
        application.primaryStage.scene.cursorProperty().bind(Bindings.when(t.runningProperty())
            .then(model.CURSOR_WAIT).otherwise(model.CURSOR_DEFAULT))
    }

    runOutsideUI(t)
}

OTHER

@EqualsAndHashCode(includes = 'textValue')
class ListElement implements Serializable {
    String textValue = ""
    Serializable objectValue // Serializable object from business model

    @Override
    String toString() {
        textValue
    }
}

The Griffon framework automatically invokes the onAction controller events outside the UI thread. GroovyFX contains some magic which adds an "onSelect" action bound to selectionModel.selectedItemProperty i.e.

class GroovyFXEnhancer {
    static void enhanceClasses() {
        ...
        ComboBox.metaClass {
            cellFactory << { Closure closure -> delegate.setCellFactory(closure as Callback)}
            onSelect << { Closure closure ->
                delegate.selectionModel.selectedItemProperty().addListener(closure as ChangeListener);
        }
        ...
    }
}

2条回答
干净又极端
2楼-- · 2019-09-09 20:14

You can also use the griffon-tasks-plugin http://griffon-plugins.github.io/griffon-tasks-plugin/

This plugin delivers and UI toolkit agnostik SwingWorker-like API for executing tasks in a background thread.

查看更多
SAY GOODBYE
3楼-- · 2019-09-09 20:17

Is there any built in solution for this pattern provided in javafx?

I would advice you to use the built in Task ;)

It has predefined methods to handle everything you need.

private Task<Void> backgroundTask = new Task() {
    @Override
    protected Void call() throws Exception {
        // Something to do on background thread ;
        return null;
    }
};

It has a runningProperty(), which can bind to the cursorProperty() of the scene.

You can create two ObjectProperty<Cursor> containing Cursor.DEFAULT and CURSOR.WAIT.

final ObjectProperty<Cursor> CURSOR_DEFAULT = new SimpleObjectProperty<>(Cursor.DEFAULT);
final ObjectProperty<Cursor> CURSOR_WAIT = new SimpleObjectProperty<>(Cursor.WAIT);

Then you can bind them to the task :

scene.cursorProperty().bind(Bindings.when(backgroundTask.runningProperty())
                                            .then(CURSOR_WAIT).otherwise(CURSOR_DEFAULT));

Would this be a separate issue or somehow related?

If your action on the ComboBox is somehow invoking the background thread, then it might be related, else it is difficult to comment.

查看更多
登录 后发表回答