如下圖,有這麼一個常見需求,在修改表單明細的蘋果價格時,總價會改變,同時單據總和也隨之改變。

按照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; }

我們再除錯執行一次

 完美!!!

自動判斷
中文
中文(簡體)
中文(香港)
中文(繁體)
英語
日語
朝鮮語
德語
法語
俄語
泰語
南非語
阿拉伯語
亞塞拜然語
比利時語
保加利亞語
加泰隆語
捷克語
威爾士語
丹麥語
第維埃語
希臘語
世界語
西班牙語
愛沙尼亞語
巴士克語
法斯語
芬蘭語
法羅語
加里西亞語
古吉拉特語
希伯來語
印地語
克羅埃西亞語
匈牙利語
亞美尼亞語
印度尼西亞語
冰島語
義大利語
喬治亞語
哈薩克語
卡納拉語
孔卡尼語
吉爾吉斯語
立陶宛語
拉脫維亞語
毛利語
馬其頓語
蒙古語
馬拉地語
馬來語
馬耳他語
挪威語(伯克梅爾)
荷蘭語
北梭託語
旁遮普語
波蘭語
葡萄牙語
克丘亞語
羅馬尼亞語
梵文
北薩摩斯語
斯洛伐克語
斯洛維尼亞語
阿爾巴尼亞語
瑞典語
斯瓦希里語
敘利亞語
泰米爾語
泰盧固語
塔加路語
茨瓦納語
土耳其語
宗加語
韃靼語
烏克蘭語
烏都語
烏茲別克語
越南語
班圖語
祖魯語

自動選擇
中文
中文(簡體)
中文(香港)
中文(繁體)
英語
日語
朝鮮語
德語
法語
俄語
泰語
南非語
阿拉伯語
亞塞拜然語
比利時語
保加利亞語
加泰隆語
捷克語
威爾士語
丹麥語
第維埃語
希臘語
世界語
西班牙語
愛沙尼亞語
巴士克語
法斯語
芬蘭語
法羅語
加里西亞語
古吉拉特語
希伯來語
印地語
克羅埃西亞語
匈牙利語
亞美尼亞語
印度尼西亞語
冰島語
義大利語
喬治亞語
哈薩克語
卡納拉語
孔卡尼語
吉爾吉斯語
立陶宛語
拉脫維亞語
毛利語
馬其頓語
蒙古語
馬拉地語
馬來語
馬耳他語
挪威語(伯克梅爾)
荷蘭語
北梭託語
旁遮普語
波蘭語
葡萄牙語
克丘亞語
羅馬尼亞語
梵文
北薩摩斯語
斯洛伐克語
斯洛維尼亞語
阿爾巴尼亞語
瑞典語
斯瓦希里語
敘利亞語
泰米爾語
泰盧固語
塔加路語
茨瓦納語
土耳其語
宗加語
韃靼語
烏克蘭語
烏都語
烏茲別克語
越南語
班圖語
祖魯語

有道翻譯
百度翻譯
谷歌翻譯
谷歌翻譯(國內)

翻譯 朗讀 複製 正在查詢,請稍候…… 重試 朗讀 複製 複製 朗讀 複製 via 百度翻譯