PseudoClass: уведомление от состояний, нарушенных при установке пользовательского состояния - PullRequest
0 голосов
/ 02 декабря 2018

Ниже приведен пример, который не ведет себя так, как я ожидаю, что он будет вести себя - хотел бы выяснить, является ли поведение или мои ожидания неправильными (вполне может быть, что я делаю что-то глупо неправильно;).

Что он делает:

  • он реализует пользовательскую кнопку с пользовательским псевдоклассом почти так же, как в примере кода в java doc
  • он регистрирует прослушиватель в наборе observableSet, возвращаемом getPseudoClassStates() кнопки, который регистрирует активные состояния при изменении
  • у него есть пользовательский интерфейс (простая кнопка), который переключает специальное состояние пользовательской кнопки

Ожидаемое поведение : уведомления о взаимодействии с пользовательской кнопкой с помощью мыши / клавиатуры (при наведении, фокус, вооруженный ...)

Фактическое поведение : то же самоекак и ожидалось, если специальное состояние не задано, уведомление о том, что специальное состояние было установлено один раз, отсутствует

Вопросы:

  • ошибка или функция?
  • как изменить настройки, чтобы получать уведомления всегда?

Пример:

public class PseudoStateNotification extends Application {
    private SpecialButton specialButton;
    private SetChangeListener sl;

    public static class SpecialButton extends Button {
        private static PseudoClass SPECIAL = PseudoClass.getPseudoClass("special");
        private BooleanProperty special = new SimpleBooleanProperty(this, "special", false) {

            @Override
            protected void invalidated() {
                pseudoClassStateChanged(SPECIAL, get());
            }

        };

        public SpecialButton(String text) {
            super(text);
        }

        public void setSpecial(boolean sp) {
            special.set(sp);
        }

        public boolean isSpecial() {
            return special.get();
        }

        public BooleanProperty specialProperty() {
            return special;
        }
    }

    private Parent createContent() {
        sl = change -> LOG.info("pseudo-changed: " + change.getSet());
        specialButton =  new SpecialButton("custom buttom");
        specialButton.getPseudoClassStates().addListener(sl);
        BorderPane pane = new BorderPane(specialButton);
        Button toggle = new Button("toggle special state");
        toggle.setOnAction(e -> {
            specialButton.setSpecial(!specialButton.isSpecial());
        });
        pane.setBottom(toggle);
        return pane;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent(), 200, 200));
        URL uri = getClass().getResource("pseudo.css");
        stage.getScene().getStylesheets().add(uri.toExternalForm());
        stage.setTitle(FXUtils.version());
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(PseudoStateNotification.class.getName());

}

Таблица стилей pseudo.css:

.button:special {
  -fx-text-fill: red;
}

Обновление:

Как хорошо прослеживается в ответе Хосе , основная причина в том, что ObservaleSet<PseudoClass> Node.getPseudoClassStates() возвращает свободно летающую неизменяемую оболочку вокруг состояний, которая делает возвращаемый набор мусора-collectable.В моей книге это ошибка - сопоставимый API, как, например, TableView.getVisibleLeafColumns(), реализован правильно и содержит строгую ссылку на оболочку.Хакерство заключается в том, чтобы сохранить сильную ссылку на набор в клиентском коде.

1 Ответ

0 голосов
/ 02 декабря 2018

Это не вопрос пользовательского состояния, но это обычный случай слабых слушателей, которые в какой-то момент собирают мусор: ваш SetChangeListener исчезнет через некоторое время.

Вы можете проверитьэто, не нажимая вообще в кнопке переключателя.Просто наведите несколько раз кнопку и нажмите на нее.Через некоторое время вы не получите никакого уведомления.

Это, например, последовательность изменений:

INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]

После последнего я больше не получаю уведомлений.

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

private ObservableSet<PseudoClass> states;

private Parent createContent() {
    sl = change -> LOG.info("pseudo-changed: " + change.getSet());
    specialButton =  new SpecialButton("custom buttom");
    states = specialButton.getPseudoClassStates();
    states.addListener(sl);
    ...
}

Теперь он будет работать с любым изменением состояния, как с кнопки, так и с переключателя.кнопка.

INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: []
INFO: pseudo-changed: [special]
INFO: pseudo-changed: [hover, special]
INFO: pseudo-changed: [hover, pressed, special]
INFO: pseudo-changed: [hover, pressed, focused, special]
...
INFO: pseudo-changed: [focused, special]
INFO: pseudo-changed: [special]
INFO: pseudo-changed: []
INFO: pseudo-changed: [hover]
...

РЕДАКТИРОВАТЬ

Несмотря на то, что finalize устарел, он все еще может использоваться для проверки того, что слушатель gc'ed в первом сценарии:

specialButton.getPseudoClassStates().addListener(new SetChangeListener<PseudoClass>() {
        @Override
        public void onChanged(Change<? extends PseudoClass> change) {
            LOG.info("pseudo-changed: " + change.getSet());
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            LOG.info("SetChangeListener finalized by gc");
        }
    });

Итак, в приведенном выше тесте вы получите:

...
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: SetChangeListener finalized by gc

и больше никаких уведомлений после этого, как упоминалось ранее.

Однако, если выудерживайте сильную ссылку, даже если вы вызовете явно System.gc(), слушатель не будет gc'ed.

Стоит также проверить реализацию FXCollection::UnmodifiableObservableSet, где WeakSetChangeListener равно используется , удерживая ссылку на самого слушателя:

private SetChangeListener<E> listener;
private void initListener() {
        if (listener == null) {
            listener = c -> {
                callObservers(new SetAdapterChange<E>(UnmodifiableObservableSet.this, c));
            };
            this.backingSet.addListener(new WeakSetChangeListener<E>(listener));
        }
    }

Это означает, чтоСсылка private SetChangeListener<E> listener; на самом деле не нужна.

...