Как использовать ListenerHandle в JavaFX?

Я прочитал эту статью о ListenerHandles, но Я не мог понять, как реализовать это в моем коде. У меня есть ChangeListener, который я назначаю своему CheckBoxes. Мне нужно изменить значение CheckBoxes так, чтобы Listeners этого не заметил.

Мой код для создания 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");
            }
        };

person Alyona    schedule 21.03.2018    source источник


Ответы (1)


Сначала несколько замечаний:

  1. Ваш код не работает, потому что ваш checkBoxListener() создает новый слушатель каждый раз, когда вы его вызываете. Таким образом, прослушиватель, который вы пытаетесь удалить, не является прослушивателем, который вы изначально добавили, и поэтому вызов removeListener(...) ничего не делает. Хуже того, вы добавите другой слушатель позже, когда попытаетесь восстановить исходный. Следовательно, вы должны обнаружить, что установка флажка после вызова метода deselectCheckBoxes() вызывает прослушиватель несколько раз.
  2. Для меня необходимость делать это вообще обычно является признаком плохого дизайна. На самом деле вам все равно, как снимается флажок (т. е. программно или пользователем). Контроллер действительно должен вызывать методы модели, чтобы модель синхронизировалась с состоянием пользовательского интерфейса. С этой точки зрения причина снятия флажка не должна иметь значения.
  3. Обратите внимание, что эта функциональность реализована «из коробки» в фреймворке ReactFX Томаса Микулы. (В основном вы можете подписаться на «потоки событий», которые могут включать в себя потоки изменений свойств. Вы можете сделать эти потоки «приостанавливаемыми» и приостанавливать их во время определенных операций.)

В любом случае, если предположить, что вам это действительно нужно по какой-то причине, насколько я понимаю, намерение состоит в том, чтобы вы сделали что-то вроде этого:

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();

и чтобы "отключить" обработку, вы бы сделали

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

Обратите внимание, что для того, чтобы это работало, вам по-прежнему необходимо сохранять ссылку на дескриптор (просто вам больше не нужно хранить отдельную ссылку на свойство или узел и держать их «связанными»). В вашем случае, поскольку у вас есть несколько флажков, поскольку у вас есть несколько флажков и несколько слушателей, вам понадобится что-то вроде:

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 или чего-то подобного, но в этом случае вы почти ничего не получите по сравнению с простым сохранением ссылок на слушателей и их удалением.

person James_D    schedule 21.03.2018
comment
Спасибо за Ваш ответ. В настоящее время я изучаю, как лучше всего обращаться со слушателями. Мне нужно было передать объект CheckBox слушателю, в основном мне нужно получить доступ к объекту, который подключен к CheckBox внутри слушателя, например checkBox.getUserData(). Могу ли я сделать что-то подобное? Извините, если это звучит слишком сложно. - person Alyona; 22.03.2018