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 百度翻譯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM