如下圖,有這么一個常見需求,在修改表單明細的蘋果價格時,總價會改變,同時單據總和也隨之改變。
按照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 百度翻譯 譯