获取 Stage 以外的散焦事件 - JavaFX

Obtaining a de-focus event other than from Stage - JavaFX

我有一段代码很像这样:

package blah;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextInputControl;
import javafx.stage.Stage;

public class SimpleExample {

TextInputControl textFieldForWork;
LocalTextChangeListener localTextChangeListener;


public SimpleExample(TextInputControl textFieldForWork, Stage s) {
    this.textFieldForWork = textFieldForWork;
    localTextChangeListener = new LocalTextChangeListener();

    System.out.println("Creating new focus listener for TextField component");
    LocalFocusListener localFocusListener = new LocalFocusListener();

    s.focusedProperty().addListener(new ChangeListener<Boolean>() {
        @Override
        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
            if (observable.getValue().toString().equals("false")) {
                System.out.println("Removing TextField focus listener");
                textFieldForWork.focusedProperty().removeListener(localFocusListener);
            } else {
                System.out.println("Adding TextField focus listener");
                textFieldForWork.focusedProperty().addListener(localFocusListener);
            }
        }
    });
}

private class LocalFocusListener implements ChangeListener {
    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
        if (observable.getValue().toString().equals("true")) {
            System.out.println("Adding text change listener");
            textFieldForWork.textProperty().addListener(localTextChangeListener);
        } else {
            System.out.println("Removing text change listener");
            textFieldForWork.textProperty().removeListener(localTextChangeListener);
        }
    }
}

private class LocalTextChangeListener implements ChangeListener {
    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
        System.out.println("Textfield changed - do processing");
    }
}}

此代码的目的是每次用户在文本字段中键入内容时触发侦听器。此代码的另一个必要功能是,在对话框散焦时,应删除文本字段侦听器。

感谢任何帮助!

我不确定我是否真的理解为什么你需要观察舞台的焦点属性。您不能只在文本字段中注册一次侦听器并将其留在那里吗?除非文本更改,否则不会调用它。

如果你真的需要你描述的功能,你可以做到。以下是对正在发生的事情的描述:

文本域的focusedProperty跟踪当前场景图中具有焦点的Node是否是文本域。它是 "local to the scene graph",这意味着它与 window 是活动的还是聚焦的 window.

无关

window 的 focusedProperty 跟踪 window 是否有焦点。如果您将应用程序移至后台等,这会发生变化。

显然,在您创建文本字段时,还没有添加到场景或 window,所以只需这样做

textFieldForWork.getScene().getWindow().focusedProperty().addListener(...)

不会起作用,因为此时 getScene() 会 return null。即使场景是非空的,它可能还不属于 window,因此您可能有 getScene() 非空但在某些时候 getScene().getWindow() 为空。

所以你真正想做的是观察属性的顺序。从textFieldForWork.sceneProperty()开始观察;如果它发生变化并且不为空,则观察 textFieldForInput.getScene().windowProperty();当它发生变化并且不为空时,观察 textFieldForInput.getScene().getWindow().focusedProperty()

您可以自己处理这个问题,为链中的每个步骤创建侦听器并根据需要添加和删除它们,但是 EasyBind 框架有 API 来管理这个用例。使用 EasyBind 你可以做到

    MonadicObservableValue<Boolean> stageFocused = 
            EasyBind.monadic(textFieldForWork.sceneProperty())
                .flatMap(Scene::windowProperty)
                .flatMap(Window::focusedProperty)
                .orElse(false);
    stageFocused.addListener((obs, wasFocused, isNowFocused) -> {
        if (isNowFocused) {
            // stage now has focus...
        } else {
            // stage has lost focus...
        }
    });

如果你想检查文本字段有焦点的条件 包含它的 window 有焦点,你可以做

BooleanBinding stageAndTextFieldFocused = Bindings.createBooleanBinding(() -> 
    stageFocused.get() && tf.isFocused(),
    stageFocused, tf.focusedProperty());

stageFocused同上。然后做

stageAndTextFieldFocused.addListener((obs, wasFocused, isNowFocused) -> 
    { /* etc ... */ });