Prism研究(for WPF & Silverlight)8.Event机制

来源:互联网 发布:淘宝直播里的东西好吗 编辑:程序博客网 时间:2024/06/15 23:00
终于说到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 = newNews() {

                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表达式。

原创粉丝点击