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直接註冊為依賴屬性,是不是就可以了。

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

後來,面向了搜索引擎了一下,發現這篇文章  //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 });

        });

調試測試符合預期。

 

Tags: