在 C# 中消息有两个指向, 一个指向 Message, 一个指向 INotify. 这里主要讲 INotify.
INotify 也有人称之为[通知], 不管叫消息还是通知, 都是一个意思, 就是传递信息.
消息的定义
INotify 消息其实是一个接口, 接口名叫 INotifyPropertyChanged. 接口定义如下:
- // 向客户端发出某一属性值已更改的通知.
- public interface INotifyPropertyChanged
- {
- // 在更改属性值时发生.
- event PropertyChangedEventHandler PropertyChanged;
- }
定义很简单, 我们可以看到这个接口只定义了一个事件属性 --PropertyChanged. 所以这个 PropertyChanged 就是消息的核心了.
那么学习应用消息的方法就出现了, 即, 创建一个继承 INotifyPropertyChanged 接口的类, 然后在类内, 实现 PropertyChanged 就可以了.
消息的应用
上面介绍消息是用来传递信息的. 那么可能会有同学好奇, 引用类型的对象不就可以封装传递信息吗? 为什么还要用消息呢?
因为有些数据是存储在非引用类型的对象中的. 比如字符串, 或数字等.
为了让字符串, 数字等数据的修改也能如引用类型一样, 可以传递回给源, 就需要使用消息了.
下面我们来看下消息的基础用法.
首先, 我们使用 WPF 创建一个项目, 然后创建一个页面, 起名为 WindowNotify, 编辑内容如下:
<Window x:Class="WpfApplication.WindowNotify"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowNotify" Height="120" Width="200">
<Grid>
<StackPanel>
<TextBox Name="txtName" VerticalAlignment="Top" Height="24"></TextBox>
<TextBox Name="txtNameNotify" VerticalAlignment="Top" Height="24"></TextBox>
<Button Click="Button_Click" Height="30" Content="查看结果"></Button>
</StackPanel>
</Grid>
</Window>
接下来, 编辑 Xaml 对于的 cs 文件, 内容如下:
- public partial class WindowNotify : Window
- {
- private string _KName = "Kiba518";
- public string KName
- {
- get { return _KName; }
- set { _KName = value; }
- }
- WindowNotifyViewModel vm;
- public WindowNotify()
- {
- InitializeComponent();
- vm = new WindowNotifyViewModel();
- Binding bding = new Binding();
- bding.Path = new PropertyPath("KName");
- bding.Mode = BindingMode.TwoWay;
- bding.Source = vm;
- txtNameNotify.SetBinding(TextBox.TextProperty, bding);
- txtName.Text = KName;
- txtNameNotify.Text = vm.KName;
- }
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- MessageBox.Show("[txtName:" + KName + "] | [txtNameNotify:" + vm.KName + "]");
- }
- }
- public class WindowNotifyViewModel : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- private string _KName = "Kiba518Notify";
- public string KName
- {
- get { return _KName; }
- set
- {
- _KName = value;
- if (this.PropertyChanged != null)
- {
- this.PropertyChanged(this, new PropertyChangedEventArgs("KName"));
- }
- }
- }
- }
这里我们创建了一个 ViewModel--WindowNotifyViewModel, 我们让这个 VM 继承 INotifyPropertyChanged, 然后定义了一个 KName 属性, 并定义了 PropertyChanged 事件触发的位置.
有同学可能会好奇, PropertyChanged 事件是何时被赋值的呢? 别心急, 请耐心往下看.
ViewModel 定义完成之后, 我们再看 Xaml 对应的 cs 文件. 这里我们也定义了一个 KName 属性. 然后初始化时, 将 cs 文件的 KName 和 VM 的 KName 分别赋值给前台定义的两个 TextBox 控件.
这里用 vm 的 KName 属性赋值时, 稍微有点特别, 稍后再介绍.
然后我们运行页面, 并修改两个文本框内的值. 再点击查看结果按钮. 得到界面如下:
可以从图中看到, 界面修改了 TextBox 的 Text 属性, WindowNotifyViewModel 的 KName 属性对修改的值进行了同步, 而 WindowNotify 的 KName 没有同步.
看完结果, 我们回过来看下 VM 的 KName 的奇怪赋值方式. 我们先看第一句:
Binding bding = new Binding();
这里的 Binding 是绑定的意思, 这行代码很明显是用来定义一个绑定.
绑定是个不好理解的词, 我们该如何理解呢?
很简单, 我们可以将绑定理解为套索, 既然是套索, 那么就该有两个属性, 一个是套头, 一个是套尾.
那么声明了套索之后, 我们便需要为套索的索尾赋值了, 即数据源的这一方.
代码里, 我们通过 Binding 的 Path 和 Source 设置了索尾的数据源和数据源绑定的属性. 之后我们还设置了绑定模式是双向绑定, 即双方修改都会进行数据传递.
设置好了套索后, 我们在让 TextBox 控件自己转进套头里, 并设置了 TextBox 控件绑定的属性. 代码如下:
txtNameNotify.SetBinding(TextBox.TextProperty, bding);
在我们 TextBox 控件自己转进套头里的时候, 会对数据源的 PropertyChanged 进行赋值, 这样我们就实现了字符串数据的传输.
当然, 这样赋值看起来比较笨拙. 那么有更简便的方法吗.
答案当然是: 有.
MVVM 的基础应用
上面的代码已经实现了 ViewModel, 那么只要在这个基础上进行优化, 即可实现最简单的 MVVM 的应用.
优化 Xaml 代码如下:
<StackPanel>
<TextBox Name="txtNameNotify" Text="{Binding KName}" VerticalAlignment="Top" Height="24"></TextBox>
<Button Click="Button_Click" Height="30" Content="查看结果"></Button>
</StackPanel>
优化 Xaml.cs 代码如下:
- public partial class WindowNotify : Window
- {
- public WindowNotify()
- {
- InitializeComponent();
- this.DataContext = new WindowNotifyViewModel();
- }
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- var vm = this.DataContext as WindowNotifyViewModel;
- MessageBox.Show("[txtNameNotify:" + vm.KName + "]");
- }
- }
- public class WindowNotifyViewModel : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- private string _KName = "Kiba518";
- public string KName
- {
- get { return _KName; }
- set
- {
- _KName = value;
- if (this.PropertyChanged != null)
- {
- this.PropertyChanged(this, new PropertyChangedEventArgs("KName"));
- }
- }
- }
- }
从上面的代码中, 我们可以看到在 Xaml 文件中, Text 属性可以使用 {Binding KName} 这种简写的模式, 来实现刚才那个复杂的 binding 赋值.
而在 Xaml.cs 文件中, 我们将 VeiwMode 赋值给了 DataContext 这个数据上下文, 然后, 我们就看到了, 前台直接使用了 VM 里的属性.
这样简单的 MVVM 就实现了.
简洁的 ViewModel
在上面我们看到了 ViewModel 的创建和使用, 但 ViewMode 中每个属性都要设置成如此复杂的形态, 稍微有点难受.
那么, 我们来用 CallerMemberName 继续简化这个 ViewModel.
优化后的代码如下:
- public class WindowNotifyViewModel : BaseViewModel
- {
- private string _KName = "Kiba518";
- public string KName
- {
- get { return _KName; }
- set
- {
- _KName = value;
- OnPropertyChanged();
- }
- }
- }
- public class BaseViewModel : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
- {
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
- }
- }
如上所示, 我们定义了一个 BaseViewModel, 并在 BaseViewModel 里面定义方法 OnPropertyChanged, 并在里面实现事件 PropertyChanged 的触发定义.
最后我们通过 CallerMemberName 特性, 在方法 OnPropertyChanged 里来获取触发该方法的属性的名称.
然后我们就实现了, 比较简洁的 ViewModel.
PS:CallerMemberName 的用法就好像 param 参数一样, 只要如上所示, 写进去即可.
结语
到此, 消息的应用就讲完了. 消息毫无疑问是 MVVM 的技术核心. 学会消息才能更好的理解 MVVM.
并且学会消息, 还能帮助我们更好的理解现在流行的前端 JS 的 MVVM. 虽然实现方式不一样, 但道理是一样的.
来源: https://www.cnblogs.com/kiba/p/9453329.html