如何在 JavaFX 中使用 ListenerHandle?

How to use ListenerHandle in JavaFX?

我读了 this article on ListenerHandles,但我不明白如何在我的代码中实现它。我有一个 ChangeListener 分配给我的 CheckBoxes。我需要在 Listeners 没有注意到的情况下更改 CheckBoxes 的值。

我的创建代码 CheckBoxes:

for (int i = 0; i<2; i++) {
    CheckBox checkBox = new CheckBox();
    checkBox.setText("CheckBox "+(i+1));
    checkBox.setAlignment(Pos.TOP_LEFT);

    checkBox.selectedProperty().addListener(checkBoxListener(checkBox));

    myVBox.getChildren().add(checkBox);
    }

ChangeListener代码:

private ChangeListener<Boolean> checkBoxListener(CheckBox checkBox) {
        return new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean selected) {
                if (selected) {
                    System.out.println("CheckBox got selected");
                } else {
                    System.out.println("CheckBox got deselected");
                }
            }
        };
    }

需要在 Listener 不注意的情况下完成的代码:

public void deselectCheckBoxes(){
      for(Node node : myVBox.getChildren()){
            CheckBox checkBox = (CheckBox) node;
            System.out.println("Removing listener");
            checkBox.selectedProperty().removeListener(checkBoxListener(checkBox));

            checkBox.setSelected(false);

            System.out.println("Adding listener back");
            checkBox.selectedProperty().addListener(checkBoxListener(checkBox));
       }
}

Listener 仍然注意到我正在通过此功能取消选择 CheckBox。有人可以解释我如何在我的代码中实现 ListenerHandle 方式吗?

非常感谢您的回答。我调整了 ChangeListener,这样我就可以从我的 CheckBox:

中获取对象
ChangeListener<Boolean> listener = (obs, wasSelected, isNowSelected) -> {
            if (isNowSelected) {
                BooleanProperty booleanProperty = (BooleanProperty) obs;
                CheckBox checkBox = (CheckBox)booleanProperty.getBean();

                System.out.println("Selected: "+(Bank)checkBox.getUserData());
            } else {
                System.out.println("Not selected");
            }
        };

首先,几点说明:

  1. 您的代码不起作用,因为您的 checkBoxListener() 每次调用它时都会创建不同的侦听器。因此,您尝试删除的侦听器不是您最初添加的侦听器,因此对 removeListener(...) 的调用没有任何作用。更糟糕的是,当您稍后尝试恢复原始时,您会添加一个 different 侦听器。因此,您会发现在调用 deselectCheckBoxes() 方法后选中复选框会多次调用侦听器。
  2. 对我来说,需要这样做通常是设计不佳的标志。你真的不应该关心如何复选框被取消(即以编程方式或由用户)。控制器实际上应该在模型上调用方法,以便模型与 UI 的状态同步。从这个角度来看,取消选中复选框的原因应该无关紧要。
  3. 请注意,此功能由 Tomas Mikula 的 ReactFX framework "out of the box" 实现。 (基本上你可以订阅 "event streams",它可以包括对属性的更改流。你可以使这些流 "suspendible" 并在特定操作期间暂停它们。)

无论如何,假设您出于某种原因确实需要它,据我了解,您的意图是您会做这样的事情:

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

public class ListenerHandle<T>  {

    private final ObservableValue<T> observable ;
    private final ChangeListener<? super T> listener ;

    private boolean attached ;

    public ListenerHandle(ObservableValue<T> observable, ChangeListener<? super T> listener) {
        this.observable = observable ;
        this.listener = listener ;
    }

    public void attach() {
        if (! attached) {
            observable.addListener(listener);
            attached = true ;
        }
    }

    public void detach() {
        if (attached) {
            observable.removeListener(listener);
            attached = false ;
        }
    }


}

那你就可以用这个了

    ChangeListener<Boolean> listener = (obs, wasSelected, isNowSelected) -> {
        if (isNowSelected) {
            System.out.println("Selected");
        }
        else {
            System.out.println("Not selected");
        }

    };

    ListenerHandle<Boolean> handle = new ListenerHandle<>(checkBox.selectedProperty(), listener);
    handle.attach();

和"turn off"处理,你会做

// ignore this change:
handle.detach();
checkBox.setSelected(false);
handle.attach();

请注意,要使其正常工作,您仍然需要保留对句柄的引用(您不再需要单独保留对 属性 或节点的引用,而是将它们保留 "connected").在你的情况下,因为你有多个复选框,因为你有多个复选框和多个侦听器,你需要这样的东西:

private List<ListenerHandle<Boolean>> checkBoxListenerHandles = new ArraryList<>();

// ...

ChangeListener<Boolean> listener = (obs, wasSelected, isNowSelected) -> {
    if (isNowSelected) {
        System.out.println("CheckBox got selected");
    } else {
        System.out.println("CheckBox got deselected");
    }
}

for (int i = 0; i<2; i++) {
    CheckBox checkBox = new CheckBox();
    checkBox.setText("CheckBox "+(i+1));
    checkBox.setAlignment(Pos.TOP_LEFT);

    ListenerHandle<Boolean> handle = new ListenerHandle<>(checkBox.selectedProperty(), listener);
    handle.attach();
    checkBoxListenerHandles.add(handle);

    myVBox.getChildren().add(checkBox);
}

然后

public void deselectCheckBoxes(){

   checkBoxListenerHandles.forEach(ListenerHandle::detach);

   for(Node node : myVBox.getChildren()){
        CheckBox checkBox = (CheckBox) node;
        checkBox.setSelected(false);
   }

   checkBoxListenerHandles.forEach(ListenerHandle::attach);

}

请注意,如果您想取消选择(并忽略)特定复选框,则必须知道哪个句柄属于该特定复选框。您可以使用 Map 或类似的方法来做到这一点,但与简单地保留对侦听器的引用并删除它们相比,此时您几乎没有任何收获。