一、INotifyPropertyChanged接口
在Windows Presentation Foundation(WPF)中,INotifyPropertyChanged是一个核心接口,用于实现类与视图之间的数据双向绑定。当实体类的某个属性值发生变化时,通过实现此接口可以立即通知绑定到属性的所有UI控件进行更新,ICommand主要针对的是关联到任何实现了ICommand接口的对象的方法。
在C#中,CallerMemberName是.NET框架提供的一个编译器特性(Compiler Feature),它允许你获取调用当前方法的成员名称,而无需硬编码该名称。这对于实现INotifyPropertyChanged接口特别有用,因为它可以减少手动输入属性名的工作量,提高代码的健壮性和可维护性。
不管是ICommand还是INotifyPropertyChanged都必须首先将ViewMode的实现设置为控件或整个界面的DataContext。如:
this.DataContext = new MainViewModel();
DataContext是UI层与数据逻辑层的桥梁
二、DataContext
DataContext是一个非常关键的概念,它是实现数据绑定的基础。DataContext是所有WPF控件都具有的一个依赖属性。它属于System.Windows.FrameworkElement类。这意味着所有继承自该类的控件都可以使用DataContext。
2.1/DataContext作用
DataContext作为一个容器,提供了UI层和数据层之间的连接点。在MVVM(Model-View-ViewModel)架构模式中,通常将ViewModel设置为控件或整个界面的DataContext,这样UI控件可以通过绑定直接访问ViewModel中的数据和命令。
2.2/DataContext特性
继承性:DataContext具有继承特性,子控件如果没有明确设置自身的DataContext,则会从其元素继承DataContext的值。这意味着在整个控件树中可以共享同一个数据上下文对象。
数据绑定:在WPF中,当你写一个数据绑定表达式如{Binding Path=PropertyName}时,默认情况下,Binding将查找当前元素的DataContext,并在其中寻找指定路径的属性。
- 实现数据驱动视图:通过将业务逻辑对象或ViewModel对象设置为控件或整个界面的DataContext,WPF可以自动根据这些对象中的数据变化更新相关联的用户界面元素。
2.3/DataContext实例
<Window> <Window.DataContext> <local:MyViewModel/> </Window.DataContext> <StackPanel> <TextBox Text="{Binding Name}" /> <Button Command="{Binding SaveCommand}" Content="Save" /> </StackPanel> </Window>
上面的示例,Window的DataContext被设置为了MyViewModel实例,因为TextBox和Button都可以通过数据绑定访问到MyViewModel中的Name属性和SaveCommand命令。
三、INotifyPropertyChanged接口的几种实现方式
3.1/简单INotifyPropertyChanged绑定
- 定义实例类并继承接口INotifyPropertyChanged
public class Person:INotifyPropertyChanged { private string m_Name="默认值是XXXX"; public string Name { get =>m_Name; set { if (m_Name == value) return; m_Name = value; this.Notify("Name"); } } public event PropertyChangedEventHandler PropertyChanged; public void Notify(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if(handler!=null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
- 界面绑定ViewModel
<Window x:Class="WpfToolkitApp.WinMvvmFirst" ... xmlns:vm="clr-namespace:WpfToolkitApp.Model"> <Window.DataContext> <vm:Person></vm:Person> </Window.DataContext> <Grid> <Label Content="名称" HorizontalAlignment="Left" Margin="21,104,0,0" VerticalAlignment="Top"/> <TextBox HorizontalAlignment="Left" Margin="60,104,0,0" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" Width="221" Height="25"/> </Grid> </Window>
- 界面构造函数绑定DataContext
public partial class WinMvvmFirst : Window { Person person = null; public WinMvvmFirst() { InitializeComponent(); person = base.DataContext as Person; } }
3.2/使用Lambda表达式,静态扩展语法
public static class NotificationExtensions { public static void Notify(this PropertyChangedEventHandler eventHandler, Expression<Func<object>> expression) { if (eventHandler == null) { return; } var lambda = expression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } var constantExpression = memberExpression.Expression as ConstantExpression; var propertyInfo = memberExpression.Member as PropertyInfo; foreach (var del in eventHandler.GetInvocationList()) { del.DynamicInvoke(new object[] { constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name) }); } } }
静态扩展方法使用:
public class Employee : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _trueName; public string TrueName { get{return this._trueName} set { this._trueName = value; this.PropertyChanged.Notify(()=>this.TrueName); } } }
还可以添加一个很实用的扩展:
public static void SubscribeToChange<T>(this T objectThatNotifies, Expression<Func<object>> expression, PropertyChangedEventHandler<T> handler) where T :INotifyPropertyChanged { objectThatNotifies.PropertyChanged +=(s, e) => { var lambda = expression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } var propertyInfo = memberExpression.Member as PropertyInfo; if (e.PropertyName.Equals(propertyInfo.Name)) { handler(objectThatNotifies); } }; }
3.3/Net4.5,框架提供的解决方法
private string m_myProperty; public string MyProperty { get{return m_myProperty;} set{ m_myProperty = value; OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed") { }
属性CallerMemberName的解决办法和方法二是基本相同的,不同的这个在net框架中解决的。更多信息可以查看CallerMemberName,Net4.5还提供了CallerFilePath、CallerLineNumber,稍微会详细讲解。
四、INotifyPropertyChanged总结
INotifyPropertyChanged
接口用于向客户端发出某一属性值已更改的通知。在应用有两种方式OneTime
模式、OneWay
模式和TwoWay
模式。
- OneTime模式
OneTime
模式是一个初始化一次绑定。不常用。
- OneWay模式
- 绑定源的每一次变化都会通知绑定目标,但是绑定目标的改变不会改变绑定源。当绑定源的数据实体类没有实现INotifyPropertyChanged接口时,当改变了数据源,发现绑定目录的UI上的相应的数据不会立即变化。
- TwoWay模式
TwoWay模式下,当绑定源的数据实体类没有实现INotifyPropertyChanged接口时,空间的更改会让数据源立即发改变,但是改变数据源,绑定目标控件却不会立即发送改变,所以当我们需要数据源改变时相对应的UI立即改变时,需要实现INotifyPropertyChanged接口。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/songjianlong/article/details/138584695