如下图,有这么一个常见需求,在修改表单明细的苹果价格时,总价会改变,同时单据总和也随之改变。
按照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 百度翻译 译