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方法中,增加通知 SumPriceAllSumPrice 的代码。

而  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方法,这次不同的是,IsQDget方法的改变是因为订单明细 MXsCount属性改变而改变的。

所以同样我们需要添加对MXsCountIsQD的通知。

此操作在初始化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 百度翻译

posted on   鲁广广  阅读(991)  评论(3编辑  收藏  举报

编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示