WPF PropertyChanged實現子屬性通知


今天用WPF的View綁定了ViewModel的一個屬性類,結果在屬性類的子屬性修改時,沒有通知到UI.

如有要顯示一個學生信息,采用WPF MVVM的模式,則前端代碼

   <StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="姓名:"/>
                <TextBlock Text="{Binding Student.Name}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="年齡:"/>
                <TextBlock Text="{Binding Student.Age}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="學級:"/>
                <TextBlock Text="{Binding Student.Grade}"/>
            </StackPanel>
        </StackPanel> 

Student實體類,外部引入 PropertyChanged.Fody 第三方庫作為自動屬性通知

   public class Student: INotifyPropertyChanged { public string Name { get; set; } public int Age { get; set; } public int Grade { get; set; } public event PropertyChangedEventHandler PropertyChanged; }

ViewMode

    public ICommand TestCmd => new RelayCommand(() => { Student = new Student(); Random random = new Random(); Student.Name = "張三"; Student.Age = random.Next(5,9); Student.Grade = 1;
        });

測試一下

 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

上面的代碼是沒問題的,也是最常見的思路,因為直接綁定的類的子屬性,在子屬性修改時,通知UI。

不過我的需求則是:當學生的年齡與學級的差大於6時,則需要在原來的年齡上有超齡提醒。

比如:

 

 

那么修改代碼,前端

   <StackPanel Grid.Row="0">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="姓名:"/>
                <TextBlock Text="{Binding Student.Name }"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="年齡:"/>
           <TextBlock Text="{Binding Student,Converter={StaticResource StudentAgeConverter}}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="學級:"/>
                <TextBlock Text="{Binding Student.Grade }"/>
            </StackPanel>
            <Button Command="{Binding TestCmd}" Content="測試"/>
        </StackPanel>

 

新增加的轉換器代碼

  public class StudentAgeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) { return null; } if (value is Student student) { if (student.Age - student.Grade > 6) { return $"超齡,實際年齡為{student.Age}"; } return student.Age; } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }

 

開始調試

 

 納尼???,怎么年齡一直是默認值呢!!!所有的類我都實現了 INotifyPropertyChanged 接口,為什么這個年齡沒有被通知到?為什么姓名和年級就可以正常顯示呢?他們看起來並沒有什么不一樣,不就一個直接綁的對象,一個綁的對象的具體屬性嗎,為什么綁的對像的這個沒有被通知到呢?

仔細想想,前端我Age其實綁定的是Student,之所以顯示Age,是因為轉換器把Student的Age提了出來。也就是說,當Student的子屬性被修改時,並不觸發PropertyChanged。

和之前代碼的區別就是一個直接綁定的子屬性,子屬性修改自然會通知UI,而這個則是綁定的類,類的子屬性修改,並不會通知UI。

那么解決的思路就有兩個了:

 

方法1.前端的Age 改為采用多重綁定來直接綁定 Student 的Age 和Student 的Grade,然后轉換器也改為實現IMultiValueConverter,然后轉換器的傳參用Object數組來接收...,再考慮到VS對Xmal的多重綁定的支持並不太好,我頭就更大了,雖然最后還是寫出來了,這里就不放出來了。

 

方法2.簡單點想,在修改子屬性的時候,手動通知Student不就可以了嘛

說干就干,修改下后端命令代碼

       public ICommand TestCmd => new RelayCommand(() => { Student = new Student(); Random random = new Random(); Student.Name = "張三"; Student.Age = random.Next(5,9); Student.Grade = 1; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Student")); });

調試一下

 

 OK,可以了。不過想了想,還是讓Student的Age屬性自己觸發可能更好點,這樣就不用擔心如果其他地方修改了Age,導致沒有手動觸發的尷尬了。

Student的實體類修改為

   public class Student : INotifyPropertyChanged { public object obj { get; set; } public string Name { get; set; } private int age; public int Age { get { return age; } set { age = value; PropertyChanged?.Invoke(obj, new PropertyChangedEventArgs("Student")); } } public int Grade { get; set; } public event PropertyChangedEventHandler PropertyChanged; }

生成實體時,當前的VIewmodel賦值到Student的obj上

        public ICommand TestCmd => new RelayCommand(() => { Student = new Student(); Student.obj = this; Random random = new Random(); Student.Name = "張三"; Student.Age = random.Next(5, 9); Student.Grade = 1; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Student")); });

測試同樣可以正常運行。

 

后來我又想,如果把Viewmodel的Student直接注冊為依賴屬性,是不是就可以了。

結果是不行,而且,因為是注冊成了依賴屬性,依賴屬性是不支持手動通知的,導致不管怎么修改都不能正確顯示正確結果。

后來,面向了搜索引擎了一下,發現這篇文章  https://blog.csdn.net/Liwuqingxin/article/details/81141856

按照他提供的方法,修改代碼如下:

添加針對依賴屬性的擴展方法

   public static class Dependencyextension { public static object InvokeInternal<T>(this T caller, string method, object[] parameters) { MethodInfo methodInfo = typeof(T).GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic); return methodInfo?.Invoke(caller, parameters); } }

 

ViewModel的Student改為依賴屬性

      public Student Student { get { return (Student)GetValue(StudentProperty); } set { SetValue(StudentProperty, value); } } public static readonly DependencyProperty StudentProperty = DependencyProperty.Register("Student", typeof(Student), typeof(MainWindowViewModel), null);

TestCmd改為

       public ICommand TestCmd => new RelayCommand(() => { Student = new Student(); Student.obj = this; Random random = new Random(); Student.Name = "張三"; Student.Age = random.Next(5, 9); Student.Grade = 1; this.InvokeInternal<DependencyObject>("NotifySubPropertyChange", new object[] { StudentProperty }); });

調試測試符合預期。

 


免責聲明!

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



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