zoukankan      html  css  js  c++  java
  • Prism研究(for WPF & Silverlight)8.Event机制

        终于说到Event了。阅读本篇之前,请参阅我的另一篇关于事件的文章:CLR笔记:10.事件

        Prism自带的示例与MVP模式的耦合性太大了,以至于看不出Prism框架中独特的Event机制。于是,我自己写了一个超级简单的Sample,以飨读者。

        示例代码下载:code.zip

        事件的实现很简单,以下是傻瓜化Step by Step

        1.       在公共类库中定义事件AddNotamsEvent

        public class AddNotamsEvent : CompositePresentationEvent<NotamsInfo> { }

     

        其中,NotamsInfo为一个自定义的实体:

        public class NotamsInfo

        {

            public string IATA

            {

                get;

                set;

            }

     

            public string ICAO

            {

                get;

                set;

            }

        }
     

        当然也可以直接使用简单类型,这样就不用自定义实体了:

        public class AddAircraftEvent : CompositePresentationEvent<int>


        2.       在事件发布方:

                News news = new News() {

                    Title = "Bao's demo was published.",

                    Content = "This message is wonderful."

                };

     

                eventAggregator.GetEvent<NewsAddedEvent>().Publish(news);

        3.       在事件订阅方:

                this.eventAggregator.GetEvent<NewsAddedEvent>().Subscribe(ShowMessage);
     

        这里,ShowMessage是一个具有News类型参数的方法:

            public void ShowMessage(News news)

            {

                tbSimpleParam.Text = news.Title;

            }

     

        这样,一个完整的Prism事件机制就完成了。










       
    让我们回过头来,看一下
    IEventAggregator,它有一个常用的方法GetEvent<T>,用来获取注册到其中的事件T

        对于事件的订阅,更正规的写法如下所示:

     

                StandardMessageAddedEvent messageAddedEvent = eventAggregator.GetEvent<StandardMessageAddedEvent>();

     

                if (subscriptionToken != null)

                {

                    messageAddedEvent.Unsubscribe(subscriptionToken);

                }

     

                subscriptionToken = messageAddedEvent.Subscribe(ShowMessage2, ThreadOption.UIThread, true, Filter);
     

        同时,在ShowMessage2方法中,补充一段代码,用来取消事件的调阅:

            public void ShowMessage2(string text)

            {

                tbSimpleParam.Text = text;

     

                StandardMessageAddedEvent messageAddedEvent = eventAggregator.GetEvent<StandardMessageAddedEvent>();

                if (subscriptionToken != null)

                {

                    messageAddedEvent.Unsubscribe(subscriptionToken);

                }

            }
     

        在上面的代码中,我们看到这样的顺序:

    1.       先从IEventAggregator中获取注册到其中的事件对象,也就是messageAddedEvent

                StandardMessageAddedEvent messageAddedEvent = eventAggregator.GetEvent<StandardMessageAddedEvent>();
     

    2.       这个messageAddedEvent对象具有Subscribe方法,它返回一个SubscriptionToken类型的对象,用来标志事件触发后,在订阅一方所调用的方法:

                subscriptionToken = messageAddedEvent.Subscribe(ShowMessage2, ThreadOption.UIThread, true, Filter);
     

    3.       但是在第2步之前,我们要检查SubscriptionToken对象是否为空,否则就要注销之前的messageAddedEventHandler方法:

                if (subscriptionToken != null)

                {

                    messageAddedEvent.Unsubscribe(subscriptionToken);

                }
     

        之所以这么写,是因为我实在不放心弱引用后的垃圾自动回收时间,于是,我将Subscribe方法的第3个参数设置为了true(默认为false,弱引用,垃圾自动回收),这样,订阅的就是强引用了,Prism就会要求我们必须手动取消事件的订阅。

        在上面的Subscribe方法中,我们看到它有4个参数,这是它最完整的一个重载,签名如下所示:

            public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter)
     

        其中,第一个参数就是订阅一方所要调用的方法,在上面的例子中为ShowMessage2

        第二个参数是ThreadOption枚举,相应说明见注释:

        public enum ThreadOption

        {

            // 在Publisher所在的线程上执行,默认值

            PublisherThread,

     

            /// 在UI线程上触发

            UIThread,

     

            ///在后台线程上异步调用

            BackgroundThread

        }
     

        其中,PublisherThread表示在Publisher所在的线程上执行,这是一个默认值。

        UIThread线程表示在UI线程上执行,即调用wpf DispatcherBeginInvoke方法,如下所示:

    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, arg);

        BackgroundThread表示在后台线程上异步调用,即通过BackgroundWorker类来异步操作

            public override void InvokeAction(Action<TPayload> action, TPayload argument)

            {

        BackgroundWorker worker = new BackgroundWorker();

                worker.DoWork += ((sender, e) => action((TPayload)e.Argument));

     

                worker.RunWorkerAsync(argument);

            }

        关于这3个枚举的区别,我也写了一个Demo,请大家根据我的解释去理解(暂付阙如)。

        第三个参数是一个bool值,true表示强引用,要手动取消事件的订阅;false(默认值)表示弱引用,会自动进行垃圾回收。我们在前面有介绍过。

        第四个参数是一个bool类型的委托,它以事件传递的消息类型作为参数,我们观察Demo中的代码:

                subscriptionToken = messageAddedEvent.Subscribe(ShowMessage2, ThreadOption.UIThread, true, Filter);

     

            public bool Filter(string text)

            {

                return true;

                //return false;

            }
     

        使用F5一步步调试,会发现Filter方法是在ShowMessage2方法之前执行的,而且,Filter方法返回true,才会继续执行ShowMessage2方法。于是,我们可以根据这个时间差,来完成一些高难度动作。看下面这个例子,点击Add按钮前后的效果如下图所示:


       
    没错,很像
    Prism自带的那个Demo,位于C:"Users"baoj"Desktop"PRISM"Quickstarts"EventAggregation

        但是这个例子太复杂了,正如我前面提到的那样,和MVP搞在了一起,于是我对其进行了简化,包包版的示例下载:code.zip

        这个例子呢,根据我们选择的是Customer1还是Customer2,来决定文字在Customer1VIew中显示,还是在Customer2VIew中显示。

        对,这就是Filter的用武之处了。2View,即Customer1ViewCustomer2View,都订阅了同一个事件源。于是,当该事件发布时,2View都会被触发,根据事件所带的参数是Customer1还是Customer2,来决定是否在自身View中显示。核心语法就是那个Filter方法了,如下所示:

            public bool FundOrderFilter(FundOrder fundOrder)

            {

                return fundOrder.CustomerId == customerId;

            }     

        但是,在实际项目中,MVP模式和EventAggregator技术往往是同时出现的,这才显示出Prism框架的强大。所以,还是建议大家参考Prism自带的Demo

        那,为什么不使用.NET Framework自带的事件机制呢?我们知道,在.NET基本事件模型中,我们要手动创建一个事件管理器,来负责事件的订阅和发布。而在Prism中,因为依赖注入的引进,我们不再需要手动创建这个管理器了,Prism框架会为我们自动创建管理器的一个实例,也就是EventAggregator,它位于另一个Module中,当然这个ModulePrism框架自带的了,还记得UnityBootstrapperConfigureContainer方法么,对,就是在这里进行注册的。

        那那那,如果我不想传递参数呢?我就是想点击ViewA的按钮,然后ViewBdisabled了。够BT的需求吧。你还别说,Prism框架还真不能提供这样一套不传递参数的机制。否则,你也Publish这样的事件,我也Publish这样的事件,但你我相应的订阅机制不同,可是Prism分不清啊,于是这个世界就乱套了。

        别说Prism设计不出这样的事件机制,就连.NET事件机制也做不到,只能采取折中的办法,比如说ButtonClick事件:

            private void button1_Click(object sender, EventArgs e)
     

        其中,在点击按钮的同时,会把这个按钮对象作为参数sender传递进去,于是,每个按钮的点击事件就区别开了。

        照猫画虎,为了在Prism事件机制中也实现传递空参数的技术,我自己创建了一个空类NUllClass,就靠它混饭吃了:

        public class NullableEvent : CompositePresentationEvent<NullClass> { }

        public class NullClass { }
     

        于是,在发布方传递null

            eventAggregator.GetEvent<NullableEvent>().Publish(null);
     

        而在订阅方订阅依旧,只是在调用方法时对NUllClass参数置之不理:

            this.eventAggregator.GetEvent<NullableEvent>().Subscribe(DoSomething);

     

            public void DoSomething(NullClass nullClass)

            {

                //Do Something();

            }
     

        最后说一句,什么时候使用Event,而什么时候使用Command,貌似它们都能解决相同的问题。在前面的介绍中,我们看到,Event主要适用于View之间的通信。而Command,则主要用于单独的View中。我会在下一章看到CommandPrism中的应用。

        还有,关于Subscribe方法的最后一个参数Filter,还可以写成这样的形式:

                    subscriptionToken = fundAddedEvent.Subscribe(FundAddedEventHandler, ThreadOption.UIThread, false, fundOrder => fundOrder.CustomerId == customerId);

         但是这只能在WPF中使用,因为Silverlight不支持Lambda表达式。

  • 相关阅读:
    python实现git操作
    CentOS 安装R包 报错:internet routines cannot be loaded ,如何解决
    CentOS 6.8上安装指定版本的注释软件 VEP,release 93.4
    Anaconda3安装后在Pycharm中报错:ImportError: DLL load failed: 找不到指定的模块
    CentOS编译fastp 0.19.4 时报错/usr/bin/ld: cannot find -lz
    推荐一款非常好用的在线python编辑器——Colaboratory
    mac如何用quick look预览多个文件或者图片
    Pileup 格式详细说明
    CentOS 7 上安装 Django 2.2.4,解决报错:No module named ‘_sqlite3′
    Message: 'chromedriver' executable needs to be available in the path.
  • 原文地址:https://www.cnblogs.com/Jax/p/1528296.html
Copyright © 2011-2022 走看看