【问题标题】:GWT Custom EventsGWT 自定义事件
【发布时间】:2010-06-01 16:44:43
【问题描述】:

嘿,我很难理解自定义 GWT 事件处理程序的工作方式。我已经阅读了很多有关该主题的内容,但仍然有些模糊。我在 Stackoverflow 上读过类似GWT Custom Event Handler 的线程。有人可以用如下的应用方式来解释它。

我有 2 个班级,一个街区和一个人班。当人与块碰撞时,人会触发一个事件( onCollision() ),然后块类会监听该事件。

谢谢

【问题讨论】:

    标签: events gwt


    【解决方案1】:

    一般事件:

    总是发送事件以通知某些事情(例如状态更改)。让我们以一个人和一堵墙为例。在这里我们可以想象有一个游戏,用户可以像人一样在迷宫中行走。每次用户撞到墙时,它都应该被告知碰撞,以便它可以对它做出反应(例如,墙可以将自己渲染为被破坏的墙)。这可以通过在每次检测到与墙壁的碰撞时发送碰撞事件来实现。这个事件是由一个人发送的,系统中对事件感兴趣的每个对象都会接收到它,并可以对它做出相应的反应。想要接收事件的对象必须将自己注册为对事件感兴趣。

    这就是事件在每个系统或框架中的一般工作方式(不仅在 GWT 中)。为了在此类系统中发送和接收事件,您必须定义:

    1. 发送的内容(事件是什么样的)
    2. 谁接收事件(事件接收者)
    3. 谁发送事件(事件发送者)

    那么你可以:

    1. 注册想要接收事件的事件接收者
    2. 发送事件

    GWT 中的事件:

    这里我将展示一个在 GWT 中使用自定义事件的示例。我将使用一个负责检查邮箱并通知用户是否有新邮件的系统示例。假设系统中至少有 2 个组件:

    • message checker 负责检查邮箱和
    • 消息显示器负责显示新邮件

    当收到新邮件并且消息显示器接收到这些事件时,消息检查器会发送事件。

    第 1 步:定义事件

    有关新邮件的信息将作为MessageReceivedEvent 类的实例发送。该类包含一个新邮件(为简单起见,我们假设它只是一个String)。

    该类的完整源代码如下所示(对它的注释在源代码下方)。

    public class MessageReceivedEvent extends GwtEvent<MessageReceivedEventHandler> {
    
        public static Type<MessageReceivedEventHandler> TYPE = new Type<MessageReceivedEventHandler>();
    
        private final String message;
    
        public MessageReceivedEvent(String message) {
            this.message = message;
        }
    
        @Override
        public Type<MessageReceivedEventHandler> getAssociatedType() {
            return TYPE;
        }
    
        @Override
        protected void dispatch(MessageReceivedEventHandler handler) {
            handler.onMessageReceived(this);
        }
    
        public String getMessage() {
            return message;
        }
    }
    

    MessageReceivedEventHandler 是一个表示事件接收器的接口。暂时先别管它,这个待会再讨论。

    每个代表 GWT 事件的类都必须扩展 GwtEvent 类。这个类包含两个必须实现的抽象方法:getAssociatedTypedispatch。然而,在每个事件类中,它们通常以非常相似的方式实现。

    该类存储有关接收到的消息的信息(请参阅构造函数)。每个事件接收者都可以使用getMessage 方法获取它。

    第 2 步:定义事件接收器

    GWT 中的每个事件类型都与一个接口相关联,该接口表示该事件类型的接收者。在 GWT 中,接收者称为处理程序。在示例中,MessageReceivedEvent 的事件接收器接口将命名为 MessageReceivedEventHandler。源码如下:

    public interface MessageReceivedEventHandler extends EventHandler {
        void onMessageReceived(MessageReceivedEvent event);
    }
    

    每个处理程序都必须扩展EventHandler 接口。它还应该定义一个在事件发生时将被调用的方法(它应该至少接受一个参数 - 一个事件)。这里方法被命名为onMessageReceived。每个接收者都可以通过实现此方法对事件做出反应。

    示例中唯一的事件接收者是MessageDisplayer组件:

    public class MessageDisplayer implements MessageReceivedEventHandler {
    
        @Override
        public void onMessageReceived(MessageReceivedEvent event) {
            String newMessage = event.getMessage();
            // display a new message
            // ...
        }
    
    }
    

    第 3 步:定义事件发送者

    在示例中,唯一的事件发送者是负责检查邮件的组件 - EventChecker

    public class MessageChecker implements HasHandlers {
    
        private HandlerManager handlerManager;
    
        public MessageChecker() {
            handlerManager = new HandlerManager(this);
        }
    
        @Override
        public void fireEvent(GwtEvent<?> event) {
            handlerManager.fireEvent(event);
        }
    
        public HandlerRegistration addMessageReceivedEventHandler(
                MessageReceivedEventHandler handler) {
            return handlerManager.addHandler(MessageReceivedEvent.TYPE, handler);
        }
    
    }
    

    每个事件发送者都必须实现HasHandlers接口。

    这里最重要的元素是HandlerManager 字段。在 GWT 中,HandlerManager 顾名思义管理事件处理程序(事件接收器)。正如一开始所说的,每个想要接收事件的事件接收者都必须将自己注册为感兴趣。这就是处理程序管理器的用途。它们使注册事件处理程序成为可能,并且可以向每个注册的事件处理程序发送特定事件。

    HanlderManager 被创建时,它的构造函数中有一个参数。每个事件都有一个来源,此参数将用作此处理程序管理器发送的所有事件的来源。在示例中它是this,因为事件源是MessageChecker

    方法fireEvent定义在HasHandlers接口中,负责发送事件。如您所见,它只是使用处理程序管理器来发送(触发)和事件。

    addMessageReceivedEventHandler 被事件接收者用来将自己注册为对接收事件感兴趣。再次使用处理程序管理器。

    第 4 步:将事件接收者与事件发送者绑定

    当一切都定义好后,事件接收者必须在事件发送者中注册自己。这通常在创建对象期间完成:

    MessageChecker checker = new MessageChecker();
    MessageDisplayer displayer = new MessageDisplayer();
    checker.addMessageReceivedEventHandler(displayer);
    

    现在checker 发送的所有事件都将由displayer 接收。

    第 5 步:发送事件

    要发送事件,MessageChecker 必须创建一个事件实例并使用fireEvent 方法发送它。这个手杖在newMailReceived 方法中完成:

    public class MessageChecker implements HasHandlers {
    
        // ... not important stuff omitted
    
        public void newMailReceived() {
            String mail = ""; // get a new mail from mailbox
            MessageReceivedEvent event = new MessageReceivedEvent(mail);
            fireEvent(event);
        }
    
    }
    

    我希望它很清楚并且会有所帮助:)

    【讨论】:

    • BTW,目前 HandlerManager 类被标记为弃用,建议改用 SimpleEventBus。
    • 是的,在 GWT 2.1.1 中最好用 EventBus 替换 HandlerManager
    • 看起来您的事件应该扩展 com.google.web.bindery.event.shared.Event 而不是 GwtEvent。来自GwtEvent javadoc:“不需要应用程序的自定义事件类型来扩展 GwtEvent。更喜欢Event。”
    • 很好的答案。不过我很好奇的是,如果事件侦听器在“发送者”(例如组件)停止活动(例如从屏幕上分离)时没有取消注册,这是否容易发生内存泄漏
    • @cellepo 我尝试使用 Event 而不是 GwtEvent,但所有 handlerManagers 都希望获得 GwtEvent。这里有没有遗漏的技巧?
    【解决方案2】:

    由于这个问题和 Piotr GWT 的回答增加了对创建自定义事件的稍微不同的方式的支持。此事件实现是特定构建,可与包 com.google.web.bindery.event.shared 中的 GWT 的 EventBus 一起使用。关于如何为 GWT 2.4 构建自定义事件的示例:

    import com.google.web.bindery.event.shared.Event;
    import com.google.web.bindery.event.shared.EventBus;
    import com.google.web.bindery.event.shared.HandlerRegistration;
    
    /**
     * Here is a custom event. For comparison this is also a MessageReceivedEvent.
     * This event extends the Event from the web.bindery package.
     */
    public class MessageReceivedEvent extends Event<MessageReceivedEvent.Handler> {
    
        /**
         * Implemented by methods that handle MessageReceivedEvent events.
         */
        public interface Handler {
            /**
             * Called when an {@link MessageReceivedEvent} event is fired.
             * The name of this method is whatever you want it.
             *
             * @param event an {@link MessageReceivedEvent} instance
             */
            void onMessageReceived(MessageReceivedEvent event);
        }
    
        private static final Type<MessageReceivedEvent.Handler> TYPE =
            new Type<MessageReceivedEvent.Handler>();
    
        /**
         * Register a handler for MessageReceivedEvent events on the eventbus.
         * 
         * @param eventBus the {@link EventBus}
         * @param handler an {@link MessageReceivedEvent.Handler} instance
         * @return an {@link HandlerRegistration} instance
         */
        public static HandlerRegistration register(EventBus eventBus,
            MessageReceivedEvent.Handler handler) {
          return eventBus.addHandler(TYPE, handler);
        }    
    
        private final String message;
    
        public MessageReceivedEvent(String message) {
            this.message = message;
        }
    
        @Override
        public Type<MessageReceivedEvent.Handler> getAssociatedType() {
            return TYPE;
        }
    
        public String getMessage() {
            return message;
        }
    
        @Override
        protected void dispatch(Handler handler) {
            handler.onMessageReceived(this);
        }
    }
    

    事件使用如下:

    要使用事件总线为该事件注册您的处理程序,请调用 MessageReceivedEvent 类上的静态注册方法:

    MessageReceivedEvent.register(eventbus, new MessageReceivedEvent.Handler() {
       public void onMessageReceived(MessageReceivedEvent event) {
         //...do something usefull with the message: event.getMessage();
       }
    });
    

    现在使用新构造的事件在 eventbus 调用 fireEvent 上触发事件:

    eventBus.fireEvent(new MessageReceivedEvent("my message"));
    

    另一个实现可以在 GWT 自己的EntityProxyChange 事件类中找到。该实现使用 EventBus 的替代选项。它使用通过addHandlerToSource 添加绑定到特定源的处理程序的能力,并且可以通过eventBus.fireEventFromSource 触发。

    这里给出的事件实现也更适合使用 GWT 的Activities

    【讨论】:

    • 感谢更新的 GWT 2.4 代码。您能说明在注册处理程序和触发事件时如何获取对 eventBus 的引用吗?
    • @Hilbrand-Bouwkamp 也对有关 eventBus 的信息感兴趣
    【解决方案3】:

    我通过扩展 GWT 的 Composite 类创建了自己的小部件。我想在这个类中创建我自己的自定义事件。我希望 GWT 的 WindowBuilder 编辑器可以访问这些事件。

    我从这个页面上的答案中学到了很多,但我不得不做出一些改变。

    我想从 Hilbrand Bouwkamp 的答案开始,因为它比较新。但我遇到了几个问题。 1)那个答案提到了事件总线。偶数总线是主程序拥有的全局变量。目前尚不清楚小部件库如何访问它。 2)我不是从头开始的。我正在扩展 GWT 库代码。为了完成这项工作,我必须从 GwtEvent 类开始,而不是 Event 类。

    Piotr 的回答基本上是正确的,但是很长。我的类(间接)扩展了 GWT 的 Widget 类。 Widget 负责许多细节,例如创建 HandlerManager 对象。 (我查看了源代码,这正是标准 Widget 的工作方式,而不是使用 EventBus。)

    我只需要向我的小部件类添加两件事来添加自定义事件处理程序。这些显示在这里:

    public class TrackBar extends Composite {
        
        public HandlerRegistration addValueChangedHandler(TrackBarEvent.Handler handler)
        {
            return addHandler(handler, TrackBarEvent.TYPE); 
        }   
    
        private void fireValueChangedEvent(int value)
        {
            final TrackBarEvent e = new TrackBarEvent(value);
            fireEvent(e);
        }
    

    我的新事件与 Piotr 的事件类几乎完全相同,如上所示。有一点值得注意。基于该示例,我从 getValue() 开始。后来我添加了 getTrackBar() 以提供更多信息。如果我从头开始,我会专注于后者,而不是前者。完整的事件类如下所示。

    import com.google.gwt.event.shared.EventHandler;
    import com.google.gwt.event.shared.GwtEvent;
    
    public class TrackBarEvent extends GwtEvent< TrackBarEvent.Handler >
    {
        public interface Handler extends EventHandler {
            void onTrackBarValueChanged(TrackBarEvent event);
        }
    
        static final Type<TrackBarEvent.Handler> TYPE =
                new Type<TrackBarEvent.Handler>();
    
        private final int value;
    
        public TrackBarEvent(int value) {
            this.value = value;
        }
    
        @Override
        public Type<TrackBarEvent.Handler> getAssociatedType() {
            return TYPE;
        }
    
        public int getValue() {
            return value;
        }
    
        public TrackBar getTrackBar()
        {
            return (TrackBar)getSource();
        }
    
        @Override
        protected void dispatch(Handler handler) {
            handler.onTrackBarValueChanged(this);
        }
    }
    

    【讨论】:

    • 对于用户定义的复合小部件,要添加报告 T 类型值更改的事件,我发现以下答案可以解决问题:stackoverflow.com/a/15477048/685715。该设计可以总结如下:public class MyWidget extends Composite implements HasValueChangeHandlers&lt;T&gt;, HasValue&lt;T&gt; { ... }
    【解决方案4】:

    如果您碰巧在 GWT 之上使用 GWTP 框架,请参阅 this Stack

    GWTP 是“一个完整的模型-视图-展示器框架,用于简化您的下一个 GWT 项目。”

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-03
      • 1970-01-01
      • 1970-01-01
      • 2011-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多