WPF使用 INotifyPropertyChanged 实现数据驱动


如下图,有这么一个常见需求,在修改表单明细的苹果价格时,总价会改变,同时单据总和也随之改变。

按照Winfrom事件驱动的思想来做的话,我们就需要在将UI的修改函数绑定到CellEdit事件中来实现。

但是对于WPF,我们完全可以利用WPF的 INotifyPropertyChanged 接口来实现。

 

 

 

 

 首先我们通过nuget引入WPF常用的自动首先通知的第三方包 PropertyChanged.Fody ,它的作用是凡是实现了 INotifyPropertyChanged 的类的属性默认都会通知前端

 

 

 

 

 然后建立订单和订单明细两个基本类,并实现 INotifyPropertyChanged 接口

   public class DJ : INotifyPropertyChanged { public int ID { get; set; } public double SumPrice { get { return MXs.Sum(it => it.Price); } } public ObservableCollection<Models.DJMX> MXs { get; set; } = new ObservableCollection<DJMX>(); public event PropertyChangedEventHandler PropertyChanged; }
  public class DJMX : INotifyPropertyChanged { public object DJ { get; set; } public object MainWindowViewModel { get; set; } public string Name { get; set; } private double price; public double Price { get { return price; } set { price = value; } } public event PropertyChangedEventHandler PropertyChanged; }

前端代码

  <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <DataGrid AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding DJs}">
            <DataGrid.Columns>
                <DataGridTextColumn Width="*" Header="订单号" Binding="{Binding ID}"/>
                <DataGridTextColumn Width="*" Header="总价" Binding="{Binding SumPrice}"/>
            </DataGrid.Columns>
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <DataGrid  AutoGenerateColumns="False" CanUserAddRows="False" SelectionUnit="CellOrRowHeader" ItemsSource="{Binding MXs}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="商品名" Width="100" Binding="{Binding Name}"/>
                            <DataGridTextColumn Header="价格" Width="100" Binding="{Binding Price, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
        </DataGrid>
        <StackPanel Grid.Row="1"  VerticalAlignment="Center" Orientation="Horizontal">
            <TextBlock Text="单据总和: "/>
            <TextBlock Text="{Binding AllSumPrice}"/>
        </StackPanel>
    </Grid>

 

前端对应的ViewModel

  public class MainWindowViewModel : INotifyPropertyChanged { public MainWindowViewModel() { DJs = new ObservableCollection<Models.DJ>() { new Models.DJ(){ ID=1}, new Models.DJ(){ ID=2}, new Models.DJ(){ ID=3}, new Models.DJ(){ ID=4}, new Models.DJ(){ ID=5} }; foreach (var dj in DJs) { dj.MXs = new ObservableCollection<Models.DJMX>() { new Models.DJMX() { Name="苹果", Price=100 }, new Models.DJMX() { Name="鸭梨", Price=200 }, new Models.DJMX() { Name="香蕉", Price=300 }, }; } } public double AllSumPrice { get { return DJs.Sum(it => it.SumPrice); } } public ObservableCollection<Models.DJ> DJs { get; set; } = new ObservableCollection<Models.DJ>(); public event PropertyChangedEventHandler PropertyChanged; }

运行调试一下

 

 

 

发现价格修改并没有影响到总价和总和, 结果并不如预期的那样,我们分析一下:

 

 

 来看总价和总和属性的定义,两个都是只读的,因为没有Set的属性,所以Fody是无法进行通知的,准确的说,是 PropertyChanged 没有设置到该属性。

例如,价格的属性代码完整其实是这样的

 

 

在价格属性改变后,会通过绑定价格属性的前端进行修改。

所以,如果我们想让价格修改的同时,总价和总和也要通知到,即可以在价格属性的Set方法中,增加通知 SumPrice 和 AllSumPrice 的代码。

而  PropertyChanged 需要传入一个当前属性所在的示例和当前属性的名称,在这里,我通过修改 OnPropertyChanged 增加一个 OnNavigationObjDJPropertyChanged 方法,

另外订单明细也需要定义两个新的obj属性用来存放需要通知的实例,达到类似EF导航属性的效果,最终的 DJMX 类代码如下

  public class DJMX : INotifyPropertyChanged { public object DJ { get; set; } public object MainWindowViewModel { get; set; } public string Name { get; set; } private double price; public double Price { get { return price; } set { price = value; OnPropertyChanged(new PropertyChangedEventArgs("price")); OnNavigationObjDJPropertyChanged(DJ, new PropertyChangedEventArgs("SumPrice")); //new OnNavigationObjDJPropertyChanged(MainWindowViewModel, new PropertyChangedEventArgs("AllSumPrice")); //new } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, e); } } 
     //new
public void OnNavigationObjDJPropertyChanged(object objTargert,PropertyChangedEventArgs e) { if (PropertyChanged != null&& objTargert!= null) { PropertyChanged(objTargert, e); } } }

同时 ViewModel 的代码也需要在数据实例化时,增加传入两个通知的实例,代码如下:

    public class MainWindowViewModel : INotifyPropertyChanged { public MainWindowViewModel() { DJs = new ObservableCollection<Models.DJ>() { new Models.DJ(){ ID=1}, new Models.DJ(){ ID=2}, new Models.DJ(){ ID=3}, new Models.DJ(){ ID=4}, new Models.DJ(){ ID=5} }; foreach (var dj in DJs) { dj.MXs = new ObservableCollection<Models.DJMX>() { new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="苹果", Price=100 }, //changed new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="鸭梨", Price=200 }, //changed new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="香蕉", Price=300 }, //changed }; } } public double AllSumPrice { get { return DJs.Sum(it => it.SumPrice); } } public ObservableCollection<Models.DJ> DJs { get; set; } = new ObservableCollection<Models.DJ>(); public event PropertyChangedEventHandler PropertyChanged; }

 

我们再调试运行一次

 

 

 

 完美!!!

 

 

 2021年7月6日,对原代码进行扩展

现在有新需求,datagrid新增一“清单”的checkbox列,当订单的明细大于8时,清单明细为勾选状态,否则为非勾选状态。

如图所示

 

 修改DJ类,新增IsQD字段用于绑定到datagrid清单列,代码如下

    public class DJ : INotifyPropertyChanged { public int ID { get; set; } public double SumPrice { get { return MXs.Sum(it => it.Price); } } public bool IsQD { get { return MXs.Count > 8 ? true : false; } } public ObservableCollection<Models.DJMX> MXs { get; set; } = new ObservableCollection<DJMX>(); public event PropertyChangedEventHandler PropertyChanged; }

通过观察,我们可以发现,新加的IsQD同样也只有get方法,这次不同的是,IsQD的get方法的改变是因为订单明细 MXs 的Count属性改变而改变的。

所以同样我们需要添加对MXs的Count对IsQD的通知。

此操作在初始化MXs添加即可,核心代码如下

      public MainWindowViewModel() { DJs = new ObservableCollection<Models.DJ>() { new Models.DJ(){ ID=1}, new Models.DJ(){ ID=2}, new Models.DJ(){ ID=3}, new Models.DJ(){ ID=4}, new Models.DJ(){ ID=5} }; foreach (var dj in DJs) { dj.MXs = new ObservableCollection<Models.DJMX>() { new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="苹果", Price=100 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="鸭梨", Price=200 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="香蕉", Price=300 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="苹果", Price=100 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="鸭梨", Price=200 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="香蕉", Price=300 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="苹果", Price=100 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="鸭梨", Price=200 }, new Models.DJMX() {   DJ=dj, MainWindowViewModel=this, Name="香蕉", Price=300 }, }; dj.MXs.CollectionChanged += (sender, e) => { if (PropertyChanged != null) { PropertyChanged(dj, new PropertyChangedEventArgs(nameof(Models.DJ.IsQD))); } }; } } .........

 

 运行我们来看效果

 

 嗯哼,似乎哪里不对

我们发现又有新的问题了,价格和总和并没有随着明细条数变化而变化。

因为我们在明细条数修改时,并有没有通知到这两个属性。知道问题原因后,我们添加新的代码

 

 在调试运行一下:

 

 

运行完美!

 

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译 译


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2026 CODEPRJ.COM