【.NET深呼吸】INotifyPropertyChanged接口的真故事

时间:2021-11-19 11:55:54

无论是在流氓腾的问问社区,还是在黑度贴吧,或是“厕所等你”论坛上,曾经看到过不少朋友讨论INotifyPropertyChanged接口。不少朋友认为该接口是为双向绑定而使用的,那么,真实的情况是这样的吗?

INotifyPropertyChanged接口位于System.ComponentModel命名空间,在该命名空间下还有另一个接口:INotifyPropertyChanging。INotifyPropertyChanging接口定义了PropertyChanging事件,应该在在属性值正在改变时引发;INotifyPropertyChanged接口定义了PropertyChanged事件,应当在属性的值已经改变后引发。

由于INotifyPropertyChanging接口仅在完整的.net库才有,在可移植的库里面并没有定义,因此,INotifyPropertyChanged接口的使用频率更高。而且,多数情况下,我们只关心属性值是否已经改变,而对属性值的修改过程并不关注。

上面废话了一大堆,本文的主旨问题就来了——INotifyPropertyChanged接口是否只是跟双向绑定有关?

下面我们考虑第一种情况。

在单向绑定中,使用INotifyPropertyChanged接口和不使用INotifyPropertyChanged接口会有什么不同。

咱们定义一个类,这个类有一个公共的Value属性,当实例化类时,会通过Timer类,每隔3秒钟更新一下Value属性,属性值使用随机整数。代码如下:

    public class TestDemo
{
Timer _timer = null;
Random _rand = null; public TestDemo ()
{
_rand = new Random();
TimerCallback cb = ( s ) =>
{
Value = _rand.Next();
};
_timer = new Timer(cb, null, TimeSpan.FromSeconds(), TimeSpan.FromSeconds());
} int _val;
public int Value
{
get { return _val; }
set { _val = value; }
}
}

代码我不解释了,相信大家能看懂,因为不复杂,注意的是,Timer对象一但实例化就会马上计时的。
现在把这个示范类用在单向绑定上,让Value属性的值显示在TextBlock上。

<Window x:Class="SampleApp1.MainWindow"
……
xmlns:local="clr-namespace:SampleApp1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<local:TestDemo x:Key="td"/>
</Grid.Resources>
<TextBlock FontSize="24" Text="{Binding Source={StaticResource td},Path=Value,Mode=OneWay}"/>
</Grid>
</Window>

OneWay就是单向绑定,现在运行应用程序,这时会发现,TextBlock上的文本一值没有改变。那是不是计时器没有成功计时呢?

通过断点调试发现,计时器是成功计时了,而Value属性也顺利地被修改,如下图:

【.NET深呼吸】INotifyPropertyChanged接口的真故事

按理说,单向绑定会让数据从数据源流向绑定目标的,那为什么TextBlock控件没有即时更新呢? 原因是Binding没有接收到属性更改通知,故没有去取最新的值。

下面我们让示范类实现INotifyPropertyChanged接口。

    public class TestDemo:INotifyPropertyChanged
{
Timer _timer = null;
Random _rand = null; public TestDemo ()
{
_rand = new Random();
TimerCallback cb = ( s ) =>
{
Value = _rand.Next();
};
_timer = new Timer(cb, null, TimeSpan.FromSeconds(), TimeSpan.FromSeconds());
} int _val;
public int Value
{
get { return _val; }
set
{
if (_val != value)
{
_val = value;
// 引发属性更改通知事件
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
} // 实现INotifyPropertyChanged接口的事件
public event PropertyChangedEventHandler PropertyChanged;
}

这时候,再次运行应用程序,就发现TextBlock中的值能够自动更新了。
【.NET深呼吸】INotifyPropertyChanged接口的真故事     【.NET深呼吸】INotifyPropertyChanged接口的真故事

上面的例子说明了什么? 它表明,属性更改通知并不是只有在双向绑定中才使用,在单向绑定中同样需要。

下面再看看双向绑定的情况。

我们先来验证一个问题:作为数据源的类型是不是一定要实现INotifyPropertyChanged接口才能被UI更新呢?

先定义一个用来测试的类。

    public class Employee
{
private string _name;
private string _city; public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
} public string City
{
get
{
return _city;
}
set
{
_city = value;
}
}
}
<Window x:Class="SampleApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApp2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<local:Employee x:Key="emp" Name="小明" City="重庆"/>
</Grid.Resources> <Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0">
<GroupBox.Header>
<TextBlock Text="修改信息" FontSize="24" Foreground="Blue"/>
</GroupBox.Header>
<StackPanel DataContext="{Binding Source={StaticResource emp}}">
<TextBlock Text="贡工姓名:"/>
<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Margin="0,15,0,0" Text="所在城市:"/>
<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=City,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</GroupBox> <GroupBox Grid.Row="1" Margin="0,20,0,0">
<GroupBox.Header>
<TextBlock Text="显示信息" Foreground="Blue" FontSize="24"/>
</GroupBox.Header>
<TextBlock DataContext="{Binding Source={StaticResource emp}}">
员工姓名;
<Run Text="{Binding Name}"/>
<LineBreak/>
所在城市:
<Run Text="{Binding City}"/>
</TextBlock>
</GroupBox>
</Grid>
</Window>

Employee类并没有实现INotifyPropertyChanged接口,但是运行上面程序后会发现,在TextBox中修改数据后,下面的TextBlock是可以自动更新的。下面我们把上面例子改一下,不通过Binding来更新数据,而是用代码来手动改。

            <StackPanel DataContext="{Binding Source={StaticResource emp}}">
<TextBlock Text="贡工姓名:"/>
<!--<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>-->
<TextBox Width="200" HorizontalAlignment="Left" x:Name="txtName"/>
<TextBlock Margin="0,15,0,0" Text="所在城市:"/>
<!--<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=City,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>-->
<TextBox Width="200" HorizontalAlignment="Left" x:Name="txtCity"/>
<Button Content="更 新" Click="OnClick" Width="200" HorizontalAlignment="Left" Margin="0,10,0,0"/>
</StackPanel>
        private void OnClick ( object sender, RoutedEventArgs e )
{
Employee emp = layoutRoot.Resources["emp"] as Employee;
if (emp != null)
{
emp.Name = txtName.Text;
emp.City = txtCity.Text;
}
}

这种情况下,是通过代码来修改示例对象的属性。运行示例程序后,会发现,修改内容后,下面的TextBlock控件不会自动更新。而通过断点调试,发现Employee实例的属性值确实已经被更新,可是TextBlock没有显示新的值。

【.NET深呼吸】INotifyPropertyChanged接口的真故事

然后,我们让Employee类实现

    public class Employee : INotifyPropertyChanged
{
private string _name;
private string _city; public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
} public string City
{
get
{
return _city;
}
set
{
if (_city != value)
{
_city = value;
OnPropertyChanged();
}
}
} private void OnPropertyChanged([CallerMemberName] string propName=""){
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
} public event PropertyChangedEventHandler PropertyChanged;
}

在每个属性值发生更改后都要引发PropertyChanged事件,这里用一个OnPropertyChanged方法封装起来,参数是发生更改的属性的名字。该处用到一个技巧,就是在参数上附加CallerMemberNameAttribute特性,并给参数一个默认值:空字符串。
在属性的set访问器中调用OnPropertyChanged方法时就不需要写上属性的名字了,CallerMemberNameAttribute会自动把调用方的成员名字赋给方法参数,由于OnPropertyChanged方法是在被更改的属性内调用的,所以CallerMemberNameAttribute得到的正是这个属性的名字,如此一来我们就省事很多了。

现在运行应用程序。修改对象属性,TextBlock就能够自动更新了。

【.NET深呼吸】INotifyPropertyChanged接口的真故事

通过以上各例,可以发现,INotifyPropertyChanged接口并不是绝对地与双向绑定有关,在完全使用Binding进行双向处理的时候,即使不实现INotifyPropertyChanged接口也可以实现获取更新,当然,Binding的源一定是同一个实例。但如果修改数据不是通过Binding来完成的,使用数据源的各个客户方就不会获得属性更改通知,因此这时候需要实现INotifyPropertyChanged接口。

经过上面几个演示,我们可以发现,INotifyPropertyChanged接口并不一定要在双向绑定的时候使用,但是为了让使用数据的代码能够及时获得属性更改通知,数据源对象都应该实现INotifyPropertyChanged接口,大家可以看看Linq to SQL或者实体模型中,开发工具生成的实体类型都是实现INotifyPropertyChanged接口的,这正是考虑到要让所有数据使用都能及时获得更新通知的做法。

希望,通过我这篇烂文的讲述,大家能够对INotifyPropertyChanged有新的认识。

【.NET深呼吸】INotifyPropertyChanged接口的真故事的更多相关文章

  1. 如何优雅的实现INotifyPropertyChanged接口

    INotifyPropertyChanged接口在WPF或WinFrom程序中使用还是经常用到,常用于通知界面属性变更.标准写法如下: class NotifyObject : INotifyProp ...

  2. INotifyPropertyChanged接口的PropertyChanged 事件

    INotifyPropertyChanged 接口用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知. 例如,考虑一个带有名为 FirstName 属性的 Person 对象. 若要提供 ...

  3. &lbrack;C&plus;&plus;&rsqb; 对象地址与接口指针的故事

    刚想到一个问题,一直在思考C++中对象地址与接口指针的故事. 问题:多继承的情况下,一个具体类对象实现了多个接口,那么多该对象获取的不同接口指针是一样的吗?不是一样吗?不是对象地址吗?-- 困惑原因, ...

  4. &lbrack;译&rsqb;WPF MVVM 架构 Step By Step&lpar;5&rpar;(添加actions和INotifyPropertyChanged接口)

    应用不只是包含textboxs和labels,还包含actions,如按钮和鼠标事件等.接下来我们加上一些像按钮这样的UI元素来看MVVM类怎么演变的.与之前的UI相比,这次我们加上一个"C ...

  5. 转载:WPF MVVM之INotifyPropertyChanged接口的几种实现方式

    原文地址:http://www.cnblogs.com/xiwang/ 序言 借助WPF/Sliverlight强大的数据绑定功能,可以比实现比MFC,WinForm更加优雅轻松的数据绑定.但是在使用 ...

  6. C&num; ListView 控件和 INotifyPropertyChanged 接口

    ListView 控件和 DataGridView 控件 ListView 是跟 Winform 中 DataGridView 用法以及显示效果差不多的一个 WPF 控件,可以通过列表的方式方便的显示 ...

  7. INotifyPropertyChanged 接口 CallerMemberName属性

    调用方信息 使用调用方信息属性,可以获取关于调用方的信息传递给方法. 可以获取源代码.行号在源代码和调用方的成员名称的文件路径. 此信息用于跟踪,调试和创建诊断工具非常有用.若要获取此信息,则使用适用 ...

  8. INotifyPropertyChanged 接口

    INotifyPropertyChanged 接口 用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知. 例如,考虑一个带有名为 FirstName 属性的 Person 对象.若要提供 ...

  9. 转载:如何优雅的实现INotifyPropertyChanged接口

    转载来源:http://www.cnblogs.com/TianFang/p/6240933.html 如何优雅的实现INotifyPropertyChanged接口 INotifyPropertyC ...

随机推荐

  1. solr全文检索原理及solr5&period;5&period;0 Windows部署

    文章原理链接:http://blog.csdn.net/xiaoyu411502/article/details/44803859 自己稍微总结:全文检索主要有两个过程:创建索引,搜索索引 创建索引: ...

  2. &lbrack;c&plus;&plus;&rsqb; STL &equals; Standard Template Library

    How many people give up, because of YOU. Continue... 先实践,最后需要总结. 1. 数据流中的数据按照一定的格式<T>提取 ------ ...

  3. IEEE 802&period;15&period;4协议学习之物理层

    在详细讲述IEEE 802.15.4协议之前,谈谈自己这两个星期看协议过程中的一点心得,或者是收获吧. 看协议文档,一定要看有书签的,边看边在旁边做些备注,以便于后期整理.对于协议层次相关的,最好在纸 ...

  4. SpringMVC11文件上传

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"% ...

  5. centos 6&period;5 搭建ftp服务器

    linux下一般使用vsftpd作为ftp服务器. vsftpd是一款在Linux发行版中最受推崇的FTP服务器程序.特点是小巧轻快,安全易用. 下面是安装配置步骤: 1.安装vsftpd yum i ...

  6. shell 数据流重定向操作符总结

    最近看了鸟哥私房菜关于shell数据流重定向的内容,总结一下. 操作符: 1.标准输入(stdin):代码为0,符号:< 或者<< 2.标准输出(stdout):代码为1,符号:&g ...

  7. 把一下程序中的print&lpar;&rpar;函数改写成

    源代码: #include <iostream> using namespace std; void print( int w ) { ; i <= w ; i++ ) { ; j ...

  8. asp&period;net mvc简单分页实例

    @{ ViewBag.Title = "Index"; } @{ int pageIndex = (int)ViewBag.CurrentPage; int pageCount = ...

  9. 使用Java语言开发微信公众平台(三)——被关注回复与关键词回复

    在上一篇文章中,我们实现了文本消息的接收与响应.可以在用户发送任何内容的时候,回复一段固定的文字.本章节中,我们将对上一章节的代码进行适当的完善,同时实现[被关注回复与关键词回复]功能. 一.微信可提 ...

  10. ubuntu18&period;04 mariadb start失败

    在Ubuntu 安装mariadb 再restart 后出现错误 journalctl -xe 发现 apparmor权限问题 AppArmor 是一款与SeLinux类似的安全框架/工具,其主要作用 ...