代码改变世界

MVVMLight源码分析之消息机制和ViewModelBase

2010-11-06 18:24  撞破南墙  阅读(7759)  评论(7编辑  收藏  举报

 目录

1 介绍 MVVMLight

2 代码分析方法

3 具体剖析

 3.1 消息机制的剖析 

 3.2 ViewModelBase

 3.3 Command 介绍/EventToCommand behavior       

 3.4 DispatcherHelper 

 

 

1介绍MVVMLight 

 试用范围: Windows Presentation Foundation, Silverlight and for Windows Phone 7

作者的介绍 

  • The GalaSoft.MvvmLight library with helper classes:
    • ViewModelBase class to be used as the base class for ViewModels.
    • Messenger class (and diverse message types) to be used to communicate within the application. Recipients only receive the message types that they register for. Additionally, a target type can be specified, in which case the message will only be transmitted if the recipient's type matches the target parameter. 
      Messages can be anything from simple values to complex objects. You can also use specialized message types, or create your own types deriving from them. 
      More information about the Messenger class.
      • MessageBase: A simple message class, carrying optional information about the message's sender.
      • GenericMessage<T>: A simple message with a Content property of type T.
      • NotificationMessage: Used to send a notification (as a string) to a recipient. For example, save your notifications as constant in a Notifications class, and then send Notifications.Save to a recipient.
      • NotificationMessage<T>: Same as above, but with a generic Content property. Can be used to pass a parameter to the recipient together with the notification.
      • NotificationMessageAction: Sends a notification to a recipient and allows the recipient to call the sender back.
      • NotificationMessageAction<T>: Sends a notification to a recipient and allows the recipient to call the sender back with a generic parameter.
      • DialogMessage: Used to request that a recipient (typically a View) displays a dialog, and passes the result back to the caller (using a callback). The recipient can choose how to display the dialog, either with a standard MessageBox, with a custom popup, etc…
      • PropertyChangedMessage<T>: Used to broadcast that a property changed in the sender. Fulfills the same purpose than the PropertyChanged event, but in a less tight way.
    • Command classes optimized for WPF and Silverlight and simplifying commanding in your application. Available with or without generic parameter (RelayCommand<T> and RelayCommand). For an in-depth analysis, I encourage you to read Using RelayCommands in Silverlight and WPF
  • The GalaSoft.MvvmLight.Extras library with optional classes:
    • EventToCommand behavior, allowing you to bind any event of any UI element to an ICommand, for example on the ViewModel, directly in XAML. This makes using Commands much easier, without writing code behind. With the newest version, you can even get the EventArgs of the fired event directly in the ViewModel to handle it.
    • DispatcherHelper class, a lightweight class helping you to create multithreaded applications.
==

2代码分析方法

 2.1 按照骑士的建议,先从测试代码看起。完整了解其功能。

 2.2 使用类图看其结构。

 3.3 顾名思义+debug看其执行。 

  之前我还去TOPLANGUAGE整理了一下牛牛们的想法,也一并贴出来供大家参考。排版乱抱歉。

 

荣耀属于TOPLANGUAGE

1

画UML. 由表及里,由浅入深,由轮廓到详细。 画完了也就分析的八九不离十了。 这个过程需要经验的积累。。刚开始会比较难,后来会越顺手。

2我感觉自己看的效率还是比较高,也是喜欢看底层,弄明白每个类干什么的,除非源码不可见。刚开始看时不去理会每个类的具体实现,而 
 
毕竟

代码从入口(比如一个库暴露的比较重要的API)一层一层往里看,先理清那些主要的类之间的调用关系,在脑力里大致形成了一张类关系图之后再看实现细节。在源码不可见的情况下,通过它们暴露的接口组合也基本能猜出里边的一些实现机制(猜测,不一定正确了).

3

先考虑一个模块 
        a,打开class view,根据class name 来判断这个类的功能。在心里对每一个类功能,已经类之间的相互关系有一个底。 
              想想如果你是这模块的设计者,你应该怎么去设计?一般来说,有经验的程序员代码都会按照MVC模式去设计代码, 
              并且每一个类的功能都会非常简洁。 
          b,读class,先看私有变量,一个class中哪些数据,这是一个class的核心,大部分操作都是相对数据来操作的。 
               然后看接口。

模块之间耦合 
       如果你是设计者你应该怎么去设计?比如要你来设计3D游戏引擎?站在设计者的角度,用设计者的思维去多考虑问题。

有本书叫做code reading

5 divide and conquer

6

我倒是读过十来万行的服务端软件的代码,不过说十多万行,其实我看的也就很小一部分。成熟、设计良好的软件,其主干脉络很清晰的。 
从入口开始,看其初始化过程,可以基本上把握其内部的各类数据结构以及其之间的关系。 
初始化过程之后,如果面向过程的代码,就找其主体逻辑;如果事件驱动的,就找其事件分发部分的代码

最怕的看面向接口编程的带有动态特性的代码,一大堆的接口和ioc,有的时候不用调试方式跑起来看内存根本就不知道具体实例对应的哪个类。

整个过程由于入口到真正核心部分代码的分支可能会有很多,所以这一个不断地探路、回溯的过程。推荐用一段大块、整段的时间去看代码,这样不至于下回看 
的时候就忘记了。

7First of all, you must have a clear understanding about the business logic, 
then you can jump in the code.

  8

1.jinq0123,supern lee——规范写代码,通过名称来了解类、变量、方法的含义。 

2.Alan GAO——通过画UML图,了解软件结构。 
3.云端孤鹜——通过类提供的接口,判断其是否重要,进而深入了解。 
4.Michael——站在模块功能设计的角度上来了解,具体类里面先看私有变量,然后看接口。 
5.sagasw——有本书叫code reading.找感兴趣的部分去读,然后外延,熟能生巧。读别人的设计文档或者看那些读代码的书。 
6.机械唯物主义——逐个击破。 
7.Weiming 
Yin——(看法和我类似)根据软件的运行逻辑,从main开始看,不断延伸,通过打印信息跟踪观察,利用优秀编辑工具能够快速阅读代码。有些地方自己思考后发 现没有难度,可以粗略过,如果有些地方设计到多态或者分支,则需要研读,并延伸到它的数据结构。 
8.Tiny fool——勇敢的移植代码学习者。 
9.Kenny Yuan——规范和结构显得无比重要,大工程是无法掌握的。 
10.Linker——看不懂代码的地方最好走人。(我相信你这么说是认为我们公司缺乏代码规范,不适合发展,其实也不是,就算公司再规范,也不是说就可以轻易 看懂代码的。还是要感谢你的关心,不过我会继续坚持下去的。)

  9

最近刚好看了一个基于业务的C++面向对象的大型代码,说说我自己的感受吧

代码量大概在5w行上下,全部是实现业务,底层封装全部有公司自己的基础类实现了,包括main()函数。。。

我的习惯是首先把大的框架搞懂,明白这个代码的流程是怎样的,大块的功能是什么,如果直接看代码细节的话,会看的不知所以然,看完大体功能,大致就可以知道这个 程序是干嘛的了,然后再去钻研里面的细节。

最重要的是做阅读笔记了,写写流程,把自己的理解写下来。

大量的代码,看了肯定就会忘的,有笔记以后再复习就方便了 
对于有大量基础实现的东西,可能不适应,我也没有这方面的经验。

 

 

  

3具体剖析 

打开源码我们可以看到

 进入测试代码

 3.1 消息机制的剖析

 ViewModelBaseTest ,我们知道他是通过以下 步骤来使用其消息机制的

  void TestSimpleSendANDRevice()
        {
            Messenger.Reset();//把MESSAGER 单例 重置 为 空

            var vm = new TestViewModel();
            
//注册
            
//1接受 string 类型的 消息
            
//2收到消息 时候 调用 HandleStringMessage
            Messenger.Default.Register<string>(vm, vm.HandleStringMessage);

            
const string Content1 = "Hello world";

            Messenger.Default.Send(Content1);//此时发送

            Assert.AreEqual(Content1, vm.ReceivedContent);
             
        }
  
public class TestViewModel : ViewModelBase
    {
string ReceivedContent ;
 
internal void HandleStringMessage(string message)
        {
            ReceivedContent = message;
        }
}

 

 那么他的底层又是如何实现的呢?

让我们进入看看他的Messenger和其接口

 

首先是其Register函数

注册函数分析-很长
 /// <summary>
        
/// Registers a recipient for a type of message TMessage.
        
/// 为T类型的参数 注册一个接受者,
        
/// The action parameter will be executed when a corresponding (相应的)
        
/// (当接收到) 参数action 这个方法将被执行
        
/// message is sent. See the receiveDerivedMessagesToo parameter 
        
/// for details on how messages deriving from TMessage (or, if TMessage is an interface,
        
/// messages implementing TMessage) can be received too.
        
/// 是否 接收  接口的 派生类的 信息
        
/// <para>Registering a recipient does not create a hard reference to it,
        
/// so if this recipient is deleted, no memory leak is caused.</para>
        
/// </summary>
        
/// <typeparam name="TMessage">The type of message that the recipient registers
        
/// for.</typeparam>
        
/// <param name="recipient">The recipient that will receive the messages.</param>
        
/// <param name="token">A token(标记) for a messaging channel. 
        
/// token 是一个 栏目的标记 
        
/// If a recipient registers
        
/// using a token, and a sender sends a message using the same token, then this
        
/// 发送和接收方使用同一个标记 token接收 才能 接收到
        
/// message will be delivered to the recipient. Other recipients who did not
        
/// 
        
/// use a token when registering (or who used a different token) will not
        
/// get the message. 
        
/// 
        
/// Similarly, messages sent without any token, or with a different
        
/// token, will not be delivered to that recipient.</param>
        
/// 
        
/// <param name="receiveDerivedMessagesToo">If true, message types deriving from
        
/// TMessage will also be transmitted to the recipient. For example, if a SendOrderMessage
        
/// and an ExecuteOrderMessage derive from OrderMessage, registering for OrderMessage
        
/// and setting receiveDerivedMessagesToo to true will send SendOrderMessage
        
/// and ExecuteOrderMessage to the recipient that registered.
        
/// 
        
/// <para>Also, if TMessage is an interface, message types implementing TMessage will also be
        
/// transmitted to the recipient. For example, if a SendOrderMessage
        
/// and an ExecuteOrderMessage implement IOrderMessage, registering for IOrderMessage
        
/// and setting receiveDerivedMessagesToo to true will send SendOrderMessage
        
/// and ExecuteOrderMessage to the recipient that registered.</para>
        
/// </param>
        
/// <param name="action">The action that will be executed when a message
        
/// of type TMessage is sent.</param>
        public virtual void Register<TMessage>(//TMessage接收的 类型 
            object recipient,//接收的对象一般是viewmodel
            object token,//标记 ,细分类型下面的 分类。 
            bool receiveDerivedMessagesToo,// 是否也接收 (派生类的) 消息
            Action<TMessage> action) {//当有消息来临的时候的 回调函数
            var messageType = typeof(TMessage);

            Dictionary<Type, List<WeakActionAndToken>> recipients;
            
//============================ 是否也接收 (派生类的) 消息
            if (receiveDerivedMessagesToo) {
                
if (_recipientsOfSubclassesAction == null) {
                    _recipientsOfSubclassesAction = new Dictionary<Type, List<WeakActionAndToken>>();
                }

                recipients = _recipientsOfSubclassesAction;
            }
                
// 默认是 false -即 不接收 (派生类的) 消息
            else {
                
if (_recipientsStrictAction == null) {
                    _recipientsStrictAction = new Dictionary<Type, List<WeakActionAndToken>>();
                }

                recipients = _recipientsStrictAction;
            }
            
//============================

            List<WeakActionAndToken> list;

            
if (!recipients.ContainsKey(messageType)) { //如果不存在 List 就添加
                list = new List<WeakActionAndToken>();
                recipients.Add(messageType, list);
            } else {
                list = recipients[messageType];//存在就取出
            }
            
            
// 新建一个  weakAction 弱引用 方法
            var weakAction = new WeakAction<TMessage>(recipient, action);// new WeakAction<TMessage>(viewmodel, function);
            var item = new WeakActionAndToken {
                Action = weakAction,
                Token = token
            };
            list.Add(item);

            
//清除已经被垃圾回收的对象
            Cleanup();
        }

 

简单说明其各参数的意思。

 public virtual void Register<TMessage>(//TMessage接收的 类型 

            object recipient,//接收的对象一般是viewmodel

            object token,//标记 ,细分类型下面的 分类。 

            bool receiveDerivedMessagesToo,// 是否也接收 (派生类的) 消息

            Action<TMessage> action) {//当有消息来临的时候的 回调函数

}

其实很多都是顾名思义的。不理解的话可以参考测试函数或进一步的分析来了解。

我也是这样的。

其他的代码都是可以自我解释的:创建一个静态的字典来存储注册信息。

其中 引用了一个 WeakAction 类。

正是这个关键的类 来实现了 对象调用函数,并且是弱引用的。

 

弱引用

什么是弱引用?

如果应用程序的代码可以访问一个正由该程序使用的对象,垃圾回收器就不能收集该对象,那么,就认为应用程序对该对象具有强引用

弱引用允许应用程序访问对象,同时也允许垃圾回收器收集相应的对象。如果不存在强引用,则弱引用的有限期只限于收集对象前的一个不确定的时间段。 

弱引用使用的范围

弱引用特别适合以下对象:占用大量内存,但通过垃圾回收功能回收以后很容易重新创建。 

仅在必要时使用长弱引用,因为在终止后对象的状态是不可预知的。

避免对小对象使用弱引用,因为指针本身可能和对象一样大,或者比对象还大。

不应将弱引用作为内存管理问题的自动解决方案,而应开发一个有效的缓存策略来处理应用程序的对象。 

 

 

 为什么要使用弱引用?

当一个应用程序的VIEW非常多的时候,如果我们不得不为每个VIEW注册进全局消息订阅发布的一个机制里。但是有些页面可能并不是经常

用到但因为通常的注册都是强类型的注册(直接添加VIEWMODEL进字典,发布机制必须持有一个VIEWMODEL的引用才能对其调用方法或改变属性),

而不得回收。或许你可以通过令外一种思路,也许更好(比如使用调度器),但持有弱引用显然是个不错主意。

 

如何使用? 

这时候 让我们回到 MESSAGE的三方法之发送

去到参数最多的那个函数

发送部分代码
 /// <summary>
        
/// 发送给目标 或者 类型 
        
/// </summary>
        
/// <typeparam name="TMessage"></typeparam>
        
/// <param name="message"></param>
        
/// <param name="messageTargetType"></param>
        
/// <param name="token"></param>
        private void SendToTargetOrType<TMessage>(TMessage message, Type messageTargetType, object token) {
            var messageType = typeof(TMessage);

            
if (_recipientsOfSubclassesAction != null) {
                
// Clone to protect from people registering in a "receive message" method
                
// Bug correction Messaging BL0008.002
                var listClone = _recipientsOfSubclassesAction.Keys.Take(_recipientsOfSubclassesAction.Count()).ToList();

                
foreach (var type in listClone) {
                    List<WeakActionAndToken> list = null;

                    
if (messageType == type
                        
|| messageType.IsSubclassOf(type)
                        
|| Implements(messageType, type)) {
                        list = _recipientsOfSubclassesAction[type];
                    }

                    SendToList(message, list, messageTargetType, token);
                }
            }

            
if (_recipientsStrictAction != null) {
                
if (_recipientsStrictAction.ContainsKey(messageType)) {
                    var list = _recipientsStrictAction[messageType];
                    SendToList(message, list, messageTargetType, token);
                }
            }

            Cleanup();
        }

 

 然后我们追到

 

private static void SendToList<TMessage>(
            TMessage message,
            IEnumerable<WeakActionAndToken> list,
            Type messageTargetType, 
            
object

token) {

。。。。。。 

    executeAction.ExecuteWithObject(message);

。。。。。 

 

}

 

  最终实现的是 

实现执行发送的代码
  /// <summary>
    
/// Stores an Action without causing a hard reference to be created to the Action's owner.
    
/// 为 拥有者 储存一个 方法 而不用 强引用
    
/// The owner can be garbage collected at any time.
    
/// 而使得 拥有者(viewmodel)能在任意时候被 回收
    
/// </summary>
    
/// <typeparam name="T">The type of the Action's parameter.</typeparam>
    
////[ClassInfo(typeof(Messenger))]
    public class WeakAction<T> : WeakAction, IExecuteWithObject
    {
        
private readonly Action<T> _action;

        
/// <summary>
        
/// Initializes a new instance of the WeakAction class.
        
/// </summary>
        
/// <param name="target">The action's owner.</param>
        
/// <param name="action">The action that will be associated to this instance.</param>
        public WeakAction(object target, Action<T> action)
            : base(target, null)
        {
            _action = action;
        }

        
/// <summary>
        
/// Gets the Action associated to this instance.
        
/// </summary>
        public new Action<T> Action
        {
            
get
            {
                
return _action;
            }
        }

        
/// <summary>
        
/// Executes the action. This only happens if the action's owner
        
/// is still alive. The action's parameter is set to default(T).
        
/// </summary>
        public new void Execute()
        {
            
if (_action != null
                
&& IsAlive)
            {
                _action(default(T));
            }
        }

        
/// <summary>
        
/// Executes the action. This only happens if the action's owner
        
/// is still alive.
        
/// </summary>
        
/// <param name="parameter">A parameter to be passed to the action.</param>
        public void Execute(T parameter)
        {
            
if (_action != null
                
&& IsAlive)
            {
                _action(parameter);
            }
        }

        
/// <summary>
        
/// Executes the action with a parameter of type object. This parameter
        
/// will be casted to T. This method implements <see cref="IExecuteWithObject.ExecuteWithObject" />
        
/// and can be useful if you store multiple WeakAction{T} instances but don't know in advance
        
/// what type T represents.
        
/// </summary>
        
/// <param name="parameter">The parameter that will be passed to the action after
        
/// being casted to T.</param>
        public void ExecuteWithObject(object parameter)
        {
            var parameterCasted = (T) parameter;
            Execute(parameterCasted);
        }
    }

 

这里牵扯到ACTION 和 T 的知识,不熟悉的可以自己去查一查。

 

然后是取消订阅这个比较简单 就是一个遍历然后从原来的列表中删除。

 /// <summary>
        
/// Unregisters a message recipient for a given type of messages and for
        
/// a given action. Other message types will still be transmitted to the
        
/// recipient (if it registered for them previously). Other actions that have
        
/// been registered for the message type TMessage and for the given recipient (if
        
/// available) will also remain available.
        
/// </summary>
        
/// <typeparam name="TMessage">The type of messages that the recipient wants
        
/// to unregister from.</typeparam>
        
/// <param name="recipient">The recipient that must be unregistered.</param>
        
/// <param name="action">The action that must be unregistered for
        
/// the recipient and for the message type TMessage.</param>
        public virtual void Unregister<TMessage>(object recipient, Action<TMessage> action) {
            UnregisterFromLists(recipient, action, _recipientsStrictAction);
            UnregisterFromLists(recipient, action, _recipientsOfSubclassesAction);
            Cleanup();
        }

 

 提一下 Cleanup();

这个函数的一个作用就是当GC回收了弱引用的对象,他就负责将其清除出列表。

 

ViewModelBase

    他的VIEWMODEL只有一个。没有更多派生类。 

其中几个就是下面。  

VIEWMODEL

IsInDesignMode 是否在设计模式之下 

VerifyPropertyName 验证属性是否出错

//封装了 消息的操作

Broadcast  发送属性改变。即消息发送。

继承自 ICleanup 接口的   Cleanup (); 取消

// 

 

其中不少思想都在John Gossman的文章中就有体现了。其中的RelayCommand就是直接在其文中就有。强推此文。

其他的下次有时间再说。