您的位置:首页 > 其它

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

2017-12-19 16:49 369 查看
终于说到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 Dispatcher的BeginInvoke方法,如下所示:
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的用武之处了。2个View,即Customer1View和Customer2View,都订阅了同一个事件源。于是,当该事件发布时,2个View都会被触发,根据事件所带的参数是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中,当然这个Module是Prism框架自带的了,还记得UnityBootstrapper的ConfigureContainer方法么,对,就是在这里进行注册的。
   
那那那,如果我不想传递参数呢?我就是想点击ViewA的按钮,然后ViewB就disabled了。够BT的需求吧。你还别说,Prism框架还真不能提供这样一套不传递参数的机制。否则,你也Publish这样的事件,我也Publish这样的事件,但你我相应的订阅机制不同,可是Prism分不清啊,于是这个世界就乱套了。
   
别说Prism设计不出这样的事件机制,就连.NET事件机制也做不到,只能采取折中的办法,比如说Button的Click事件:
       
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中。我会在下一章看到Command在Prism中的应用。
   
还有,关于Subscribe方法的最后一个参数Filter,还可以写成这样的形式:
                subscriptionToken = fundAddedEvent.Subscribe(FundAddedEventHandler,
ThreadOption.UIThread,
false, fundOrder => fundOrder.CustomerId == customerId);

    
但是这只能在WPF中使用,因为Silverlight不支持Lambda表达式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: