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

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

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

 WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

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

WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

 然后建立订单和订单明细两个基本类,并实现 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;

    }

运行调试一下

WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

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

WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

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

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

WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

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

所以,如果我们想让价格修改的同时,总价和总和也要通知到,即可以在价格属性的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;

    }

我们再调试运行一次

 WPF使用 INotifyPropertyChanged 实现数据驱动-LMLPHP

 完美!!!

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

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

06-24 15:28