• 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比较合适,例如在此示例中:

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

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

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

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

  • 相关阅读:
    工具安装
    Windbg调试
    SQL学习
    Pwnable小结
    how2heap总结
    堆利用小结
    栈溢出利用小结
    格式化字符串利用小结
    python 节假日爬取
    selenuim学习
  • 原文地址:https://www.cnblogs.com/Yukang1989/p/2872571.html
Copyright © 2020-2023  润新知