zoukankan      html  css  js  c++  java
  • Mvvm Light Toolkit for wpf/silverlight系列之Messenger[zhuan]

    在开发Wpf/SL应用时,经常会遇到不同页面和窗体之间的参数传递的问题。对于这类问题,我们一般通过事件实现数据传递,也可以定义全局静态变量来进行数据共享。这里我们则使用了另外一种非常高效而优雅的方法来进行消息传递,这里我称之为Messenger,事实上,Messenger并非mvvm的专利,我们可以把它看作一种设计模式,你可以在其它.net程序中使用它。

    一、Mvvm Light Messenger是什么

    通过Mvvm Light源码我们可以知道Messenger的实现细节,如果你现在还不能理解这些代码也没关系,很多东西理解起来远比使用起来难,Messenger也是如此,它使用起来很简单,由于Messenger只公开了一些消息注册和发送方法,使用者一看便知方法的功能,而只需关注要发送的数据和接收的对象就可以了。

    发送: 

    1. Messenger.Default.Send<bool?>(true);  

    接收:

    1. Messenger.Default.Register<bool?>(this, m => this.DialogResult = m);  

    这是最基本的用法,发送方发送了一个bool?类型的对象(值为true),这样任何只要注册了bool?类型消息的地方都可以接收到这个消息。

    • Send泛型方法很好理解,只是发送一个值为true的bool?类型的对象;
    • Register泛型方法接受2个参数,第一个是接受者,也就是消息的载体,通常是对象本身(this),当然也可以是其他已实例化的对象,第二个参数是Action类型的对象,是接收到消息后执行的方法委托

    Register方法实际上将对象和Action方法添加到全局的字典集合当中,只不过他们关系是弱引用的关系,在Send方法获取对象引用,同时执行Action方法,有关弱引用的介绍,参考弱引用

    Messenger通过全局的字典集合来保存弱引用关系,因此在对象不使用时,我们要养成清理的习惯,调用Unregister来从字典集合中移除引用关系。

    1. Messenger.Default.Unregister(this);  

    二、应用示例

    下面我们会通过登录界面实现和简单的列表增删改的功能来演示Messenger的用法:

    1、登录部分:

    首先创建LoginViewModel,类定义如下:

    WPF:

      #region ICommand
    
            public RelayCommand<object> LoginCommand
            {
                get 
                {
                    return new RelayCommand<object>(
                        (p) => 
                        {
                            System.Windows.Controls.PasswordBox pb = p as System.Windows.Controls.PasswordBox;
    
                            bool isLogon = false;
    
                            // 登录成功
                            if (_userName == "admin" && pb.Password == "123")
                                isLogon = true;
                            else
                                isLogon = false;
                            
                            // 发送消息
                            Messenger.Default.Send<bool?>(isLogon); 
                        }
                        ); 
                }
            }
    
            public RelayCommand CancelCommand
            {
                get
                {
                    return new RelayCommand(
                        () => 
                        {
                            // 发送消息
                            Messenger.Default.Send<bool?>(null); 
                        }
                        );
                }
            }
            
            #endregion
    
            #region 公共属性
            public const string UserNamePropertyName = "UserName";
    
            private string _userName = "";
    
            public string UserName
            {
                get
                {
                    return _userName;
                }
                set
                {
                    if (_userName == value)
                    {
                        return;
                    }
    
                    _userName = value;
    
                    // Update bindings, no broadcast
                    RaisePropertyChanged(UserNamePropertyName);
                }
            }
            #endregion

    SL:

           #region ICommand
    
            public RelayCommand<string> LoginCommand
            {
                get 
                {
                    return new RelayCommand<string>(
                        (p) => 
                        {
                            bool isLogon = false;
    
                            // 登录成功
                            if (_userName == "admin" && p == "123")
                                isLogon = true;
                            else
                                isLogon = false;
                            
                            // 发送消息
                            Messenger.Default.Send<bool>(isLogon); 
                        }
                        ); 
                }
            }
    
            public RelayCommand CancelCommand
            {
                get
                {
                    return new RelayCommand(
                        () => 
                        { 
                            System.Windows.Browser.HtmlPage.Window.Invoke("close"); 
                        }
                        );
                }
            }
            
            #endregion
    
            #region 公共属性
            public const string UserNamePropertyName = "UserName";
    
            private string _userName = "";
    
            public string UserName
            {
                get
                {
                    return _userName;
                }
                set
                {
                    if (_userName == value)
                    {
                        return;
                    }
    
                    _userName = value;
    
                    // Update bindings, no broadcast
                    RaisePropertyChanged(UserNamePropertyName);
                }
            }
            #endregion

    接着创建Login窗体,将按钮命令绑定到LoginViewModel对应的Command,注意WPF中不能绑定PasswordBox的Password属性,因此我们将PasswordBox作为参数传递给LoginViewModel,这种写法不符合mvvm的思想,不过基本只有这里需要这么写,也无伤大雅,页面代码如下:

    WPF:

        <StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" 
                    HorizontalAlignment="Center">
          <TextBlock Text="用户名:" VerticalAlignment="Center"/>
          <TextBox Text="{Binding UserName,Mode=TwoWay}"
                 Width="150" VerticalAlignment="Center"
                 Margin="5,2,5,2"/>
        </StackPanel>
    
        <StackPanel Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" 
                    HorizontalAlignment="Center">
          <TextBlock Text="密  码:" VerticalAlignment="Center"/>
          <PasswordBox x:Name="password" Width="150" VerticalAlignment="Center" 
                       Margin="5,2,5,2" PasswordChar="*" />
        </StackPanel>
        <StackPanel Grid.Row="3" Grid.ColumnSpan="2" 
                    Orientation="Horizontal" 
                    HorizontalAlignment="Center" VerticalAlignment="Center">
          <Button Content="登录" Command="{Binding LoginCommand}" 
                  CommandParameter="{Binding ElementName=password}" 
                  Width="100" Margin="5,2,5,2"/>
          <Button Content="取消" Command="{Binding CancelCommand}" 
                  Width="100" Margin="5,2,5,2"/>
        </StackPanel>

    SL中只有传递参数不一样:

    1. <Button Content="登录" Command="{Binding LoginCommand}"   
    2.         CommandParameter="{Binding Password,ElementName=password}"   
    3.         Width="100" Margin="5,2,5,2"/>  

    最后在app.xaml.cs中添加登录逻辑

    WPF(需要在xaml中去除StartupUri):

            protected override void OnStartup(StartupEventArgs e)
            {
                base.OnStartup(e);
    
                // 首先显示登录控件
                Login login = new Login();
                LoginViewModel loginViewModel = new LoginViewModel();
                login.DataContext = loginViewModel;
    
                // 将Login设置为主窗体
                this.MainWindow = login;
                MainWindow.Show();
    
                // 注册消息,接收bool类型的参数,true为登录成功
                Messenger.Default.Register<bool?>(
                    this,
                    m =>
                    {
                        // 登录成功后,显示主页面
                        if (m.HasValue && m.Value)
                        {
                            // 更改主窗体
                            this.MainWindow = new MainWindow();
    
                            // 关闭登录窗体
                            login.Close();
                            // 清理释放Login资源
                            loginViewModel.Cleanup();
                            login = null;
    
                            MainWindow.Show();
                        }
                        else if (!m.HasValue)
                        {
                            MainWindow.Close();
                        }
                    }
                    );
            }
    
            protected override void OnExit(ExitEventArgs e)
            {
                Messenger.Default.Unregister(this);
                base.OnExit(e);
            }

    SL:

            private void ApplicationStartup(object sender, StartupEventArgs e)
            {
                Grid rootvisual = new Grid();
    
                // 首先显示登录控件
                 Login login = new Login();
                LoginViewModel loginViewModel = new LoginViewModel();
                login.DataContext = loginViewModel;
    
                rootvisual.Children.Add(login);
                RootVisual = rootvisual;
    
                // 注册消息,接收bool类型的参数,true为登录成功
                Messenger.Default.Register<bool>(
                    this, 
                    m => 
                    {
                        // 登录成功后,显示主页面
                        if (m)
                        {
                            // 移除登录控件
                            rootvisual.Children.Clear();
                            // 添加主页面
                            rootvisual.Children.Add(new MainPage());
                            // 清理释放Login资源
                            loginViewModel.Cleanup();
                            login = null;
                        }
                    }
                    );
                
                DispatcherHelper.Initialize();
            }
    
            private static void ApplicationExit(object sender, EventArgs e)
            {
                Messenger.Default.Unregister(sender);
                ViewModelLocator.Cleanup();
            }

    到这里登录功能就实现了,关键地方就是在添加登录逻辑的地方,通过匿名方法和Lamda表达式,注册一个消息的执行方法就像写方法代码一样简单,只不过消息里的方法要等到send命令发送后才会执行

    2、通过ChildWindow实现列表增删改

    SL中模式对话框通过ChildWindow来实现,WPF通过Window的ShowDialog方法实现,这里我通过模拟SL的ChildWindow来实现WPF的模式对话框,有关如何在WPF中模拟SL的ChildWindow,参考:在WPF中模拟SL的ChildWindow效果

    代码比较多,这里就不贴代码了,我的示例代码中都有详细的注释,下面主要说说一些需要关键的地方,也算是我的一些心得:

    首先是Messenger的一些方法重载:

    void Send<TMessage>(TMessage message);

    发送值为message的TMessage类型的消息

    void Send<TMessage, TTarget>(TMessage message);

    发送值为message的TMessage类型的消息,但是接收对象必须是TTarget类型的对象

    public virtual void Send<TMessage>(TMessage message, object token)

    发送值为message的TMessage类型的消息,与前面不同的是接收对象注册的消息方法拥有相同的token值才能接收到消息值

    void Register<TMessage>(object recipient, Action<TMessage> action);

    注册接收TMessage类型消息的方法,recipient是消息载体,也就是接收消息的对象,action是消息执行方法的委托,该委托接受TMessage类型的参数,也就是Send发送的值

    void Register<TMessage>(object recipient, bool receiveDerivedMessagesToo, Action<TMessage> action);

    注册接收TMessage类型消息的方法,与上面不同的是receiveDerivedMessagesToo指定是否能够接收TMessage派生类型的对象作为消息的值

    public virtual void Register<TMessage>(object recipient, object token, Action<TMessage> action)

    注册接收TMessage类型消息的方法,与前面不同的是必须与Send方法相匹配的token才能接收该Send的消息值

    public virtual void Register<TMessage>(object recipient, object token, bool receiveDerivedMessagesToo,Action<TMessage> action)

    MvvmLight中还封装了一种特殊的消息类型NotificationMessageAction<TMessage>,通过它可以发送一些复杂的对象,并且可以包含回调函数,此示例中在对话框中发送确定的消息给主界面,主界面调用子对象的方法执行数据库操作,如果成功则关闭对话框,如果失败则执行回调函数,将错误信息返回给对话框并显示出来

    最后需要主要注意的就是什么使用Messenger比较合适,例如在此示例中:

    与弹出的对话框进行交互,我会将主界面作为消息的载体,原因如下:

    弹出对话框的生命周期较短,因此将弹出对话框作为发送方,总能发送到它的宿主页面

    弹出对话框一般需要重复打开,在弹出对话框中注册消息方法会增加消息清理的成本,即在每次关闭对话框时要对消息进行清理,否则每打开一次对话框,消息执行次数会递增

     本章节示例代码下载地址:示例下载 

  • 相关阅读:
    Binary Search Tree Iterator 解答
    Invert Binary Tree 解答
    Min Stack 解答
    Trapping Raining Water 解答
    Candy 解答
    Jump Game II 解答
    Implement Hash Map Using Primitive Types
    Gas Station 解答
    Bucket Sort
    HashMap 专题
  • 原文地址:https://www.cnblogs.com/Yukang1989/p/2872571.html
Copyright © 2011-2022 走看看