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

posted @ 2009-07-22 08:33  包建强  Views(11658)  Comments(16Edit  收藏  举报