聊聊WPF中INotifyPropertyChanged
文章目录
一、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接口。