1. 程式人生 > 其它 >WPF使用 INotifyPropertyChanged 實現資料驅動

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方法中,增加通過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 百度翻譯