Как правильно настроить пользовательское событие - PullRequest
0 голосов
/ 26 мая 2019

ОК - я боролся и боролся с этим и прочитал миллион постов, учебных пособий и т. Д. Ни один из них, кажется, не касается непосредственно того, что я пытаюсь сделать здесь. У меня есть небольшой пример кода для иллюстрации.

По сути, я хочу иметь возможность вызывать / запускать пользовательское событие с кнопки, чтобы метка реагировала на это событие.

(Обратите внимание, что я НЕ ЗАИНТЕРЕСОВАН в поиске других способов сделать это, кроме того, что я пытаюсь сделать здесь с реальными объектами Event. Я прекрасно знаю, как это сделать с прослушивателями изменений и тому подобным, но я хочу чтобы научиться делать это так.)

Вот код, который вы видите, не дает мне результат, который я ищу. Многое из этого исходит из некоторых примеров (которые на самом деле не работали для меня), и я признаю, что здесь есть части, которые "гудят" над моей головой.

Если кто-то может помочь мне заставить его работать, я могу позже проанализировать его, чтобы точно понять, что происходит. Вот код:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

//-------------------------------------------------------------------
public class MyDemo extends Application {

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

    @Override
    public void start(Stage primaryStage) {

    // label that should receive the event and react to it
    MyLabel lblReceiver = new MyLabel("And I Should Receive & React");

    // button to firs the event
    Button btnSender = new Button("Initate Event");
    btnSender.setPrefWidth(200);
    btnSender.setOnAction(e -> {
        MyEvent.fireEvent(lblReceiver, e); // really confused what the first parameter here is supposed to be.
    });

    // set up stage and show it
    Stage stage = new Stage();
    VBox root = new VBox(btnSender, lblReceiver);
    root.setSpacing(10);
    root.setPadding(new Insets(10, 10, 10, 10));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
    }

}
//-------------------------------------------------------------------

// Interface for objects that want to listen to my event
//-------------------------------------------------------------------
interface MyEventListener {
    void onMyEvent();
}
//-------------------------------------------------------------------

// My event definition itself
//-------------------------------------------------------------------
class MyEvent extends Event {

    public static final EventType<MyEvent> MY_EVENT = new EventType<>(ANY, "MY_EVENT");

    public MyEvent(EventType<? extends MyEvent> eventType) {
    super(eventType);
    }
}
//-------------------------------------------------------------------

//base/parent class for my label - this is what should receive/respond to event and
// where I'm sure i have problems - just don't know what.
//-------------------------------------------------------------------
class MyLabel extends Label implements MyEventListener {

    public MyLabel(String name) {
    this.setAlignment(Pos.CENTER);
    this.setText(name);
    this.setPrefWidth(200);
    }

    @Override // this is what I'm expecting to happen when i click the button
    public void onMyEvent() {
    this.setText("YAY! i got the event!");
    }

    private final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProp
        = new SimpleObjectProperty<EventHandler<? super MyEvent>>(this, "onMyEvent") {
    @Override
    protected void invalidated() {
        setEventHandler(MyEvent.MY_EVENT, get());
    }
    };

    public final void setOnMyEvent(EventHandler<? super MyEvent> handler) {
    onMyEventProp.set(handler);
    }

    public final EventHandler<? super MyEvent> getOnMyEvent() {
    return onMyEventProp.get();
    }

    public final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProperty() {
    return onMyEventProp;
    }

}
//-------------------------------------------------------------------

1 Ответ

2 голосов
/ 27 мая 2019

Ответ

У вас, кажется, все настроено правильно, кроме двух проблем.

  1. Вы никогда не добавите EventHandler, который прослушивает ваше событие.

    Реализация произвольного интерфейса не заставит ваш объект реагировать на ваше пользовательское событие. Система обработки событий не знает вашего интерфейса и даже не знает, что вы его реализовали. Если вы хотите, чтобы ваш метод onMyEvent() вызывался, когда ваше событие достигает метки, вам нужно сделать что-то вроде:

    public MyLabel() {
        //...
        addEventHandler(MyEvent.MY_EVENT, event -> onMyEvent());
    }
    

    Обратите внимание, что я использовал addEventHandler, чтобы сам класс не полагался на свойство onMyEvent. Если вы полагались на свойство, то внешний код может случайно нарушить ваш код, заменив EventHandler.

    Это заставляет меня задуматься, действительно ли необходим интерфейс MyEventListener. Не могли бы вы просто сделать то, что вам нужно внутри EventHandler?

  2. На самом деле вы никогда не запускаете экземпляр вашего MyEvent.

    У вас сейчас есть:

    btnSender.setOnAction(e -> MyEvent.fireEvent(lblReceiver, e));
    

    Это просто повторяет e (ActionEvent) к lblReceiver. Метод fireEvent является методом static, объявленным javafx.event.Event. Префикс вызова метода с MyEvent не меняет того, какой метод на самом деле вызывается здесь. Измените это на следующее:

    btnSender.setOnAction(e -> Event.fireEvent(lblReceiver, new MyEvent()));
    // or...
    btnSender.setOnAction(e -> lblReceiver.fireEvent(new MyEvent()));
    
    // The second option is a convenience method and simply forwards to Event.fireEvent
    

    Чтобы фактически запустить экземпляр вашего собственного класса событий на вашем ярлыке.


Основы диспетчеризации событий

В JavaFX есть две фазы обработки событий:

  • Фаза захвата

    • Первый этап. Здесь событие проходит от начала своего пути до цели. На каждом шаге пути вызываются соответствующие фильтры . Фильтры добавляются с помощью таких методов, как Node.addEventFilter(EventType,EventHandler).
  • Фаза барботирования

    • Второй этап. Здесь событие проходит от цели обратно до начала пути. На каждом шаге пути вызываются соответствующие обработчики . Обработчики добавляются с помощью таких методов, как Node.addEventHandler(EventType,EventHandler), и с помощью таких свойств, как Node.onKeyPressed и ButtonBase.onAction.

А обработка событий состоит из следующих компонентов:

  • javafx.event.Event (и подклассы)

    • Фактический объект прошел вокруг. Содержит информацию, связанную с событием (например, местоположение курсора для MouseEvent с).

      Event также несет источник . Тем не менее, источник является динамическим; это всегда будет объект, к которому EventHandler в данный момент обрабатывает событие. Таким образом, EventHandler, добавленный к Button, будет иметь Button в качестве источника события, но у того же события будет родительский элемент в качестве источника для EventHandler, добавленного к родителю. Это поведение не меняется, даже если вы оба раза использовали один и тот же экземпляр EventHandler.

  • javafx.event.EventType

    • Уточняет значение класса Event. Например, MouseEvent с типом MOUSE_PRESSED означает, что неудивительно, что мышь была нажата. EventType имеет супертип; это формирует иерархию между всеми типами для каждого вида событий. Обработчик, зарегистрированный для супертипа, также будет уведомлен о подтипах.

      У вас не может быть двух или более EventType с одним и тем же супертипом и именем. Вот почему экземплярами обычно являются public static final поля соответствующего класса событий.

  • javafx.event.EventHandler

    • Обрабатывает любые события, для которых он зарегистрирован. Это функциональный интерфейс (может быть целью лямбда-выражения или ссылки на метод). Это реализации этого интерфейса, которые выполняют работу на уровне приложения (т.е. делают что-то, когда кнопка нажата).
  • javafx.event.EventDispatcher

    • Отвечает за отправку события соответствующим обработчикам. Каждый объект, способный быть целью события, обычно имеет своего собственного диспетчера. Например, каждый экземпляр Window, Scene и Node имеет собственный экземпляр EventDispatcher (содержится в свойстве).

      Примечание. Вне узкоспециализированных ситуаций вам никогда не придется иметь дело с этим интерфейсом напрямую.

  • javafx.event.EventDispatchChain

    • Представляет путь, по которому будет проходить событие при его попадании в цель. Экземпляры этого интерфейса функционируют как стек EventDispatcher с. Когда событие запускается, новая цепочка создается и настраивается на основе цели события. В случае графа сцены и целевым объектом является Node, стек состоит из EventDispatcher, принадлежащих Window, Scene, а затем каждому Node вплоть до (родительский к дочернему) и включая цель. Событие проходит по этой цепочке ( захватывает фазу ), а затем копирует эту цепочку обратно ( всплывающая фаза).

      Примечание. Скорее всего, вам никогда не понадобится использовать этот интерфейс напрямую.

  • javafx.event.EventTarget

    • Фактическая цель события. Интерфейс EventTarget имеет один метод, который используется для построения EventDispatchChain. Другими словами, цель определяет путь события. Это первый аргумент в Event.fireEvent(EventTarget,Event).

      Примечание. Этот интерфейс необходимо использовать только в том случае, если вы создаете объект, который (очевидно) может быть целью событий (и ваш объект не выходит за пределы реализации EventTarget).

...