再话WPF绑定

在实际WPF项目中,可能会遇到非常复杂的数据绑定情况,本文针对一些常见情况,归纳出简单易用的数据绑定方法。

WPF基本绑定模式

WPF支持三种基本的绑定模式:①源数据单向绑定到目标控件;②目标控件单向绑定到源数据;③源数据和目标控件双向绑定。

有以下几点值得注意并好好体会:

  • 当源数据是自定义类的属性时,在程序中修改属性时需要手动通知控件更新显示。
  • 控件的值发生变化时,会自动同步到属性,WPF原生支持,不需要人为添加额外代码。
  • 以上两种方式都会修改源属性,也就会执行属性的setter逻辑。

项目中的复杂绑定情况

情况1

场景描述:在实际项目中,可能会有非常复杂的属性(例如类对象数组),界面上的多个控件都以它的一部分信息作为数据源,并且该复杂属性只在某些事件中被修改,如下图所示:

不可能直接将该复杂属性绑定到这些相关控件上,那么怎么处理呢?

① 可以通过定义派生属性来解决该问题,派生属性只实现get逻辑,在该逻辑中根据复杂属性计算出需要的信息。每个派生属性作为数据源单向绑定到对应控件上。当复杂属性发生变化时,在其set代码中添加通知各派生属性的逻辑,从而驱动更新各控件更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ViewModel : INotifyPropertyChanged {
// 属性通知
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public ViewModel() {
Students = new ObservableCollection<Student>() {
new Student(){ Name="Tom",Age=30},
new Student(){ Name="Liza",Age=1},
new Student(){ Name="Tim",Age=30},
};
Students.CollectionChanged += Students_CollectionChanged;
}

// 集合元素变化事件处理器
private void Students_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
OnPropertyChanged(nameof(Names));
OnPropertyChanged(nameof(Ages));
}

// 复杂属性
private ObservableCollection<Student> _students = new ObservableCollection<Student>();
public ObservableCollection<Student> Students {
get => _students;
set {
if (_students != value) {
_students = value;
OnPropertyChanged(nameof(Students));
OnPropertyChanged(nameof(Names));
OnPropertyChanged(nameof(Ages));
}
}
}

// 派生属性Names,从复杂属性中查询到所有学生的名字,与界面的一个ListBox单向绑定
public List<string> Names=> (from s in Students
select s.Name).ToList();
// 派生属性Ages,从复杂属性中查询到所有学生的年龄,与界面的一个ListBox单向绑定
public List<int> Ages=> (from s in Students
select s.Age).ToList();
}
1
2
<ListBox ItemsSource="{Binding Names,Mode=OneWay}"/>
<ListBox ItemsSource="{Binding Ages,Mode=OneWay}"/>

② 还有一种思路,定义普通形式的派生属性(包含setter和getter),与对应控件单向绑定。复杂属性变化时,在其setter中计算所有派生属性的值并给派生属性赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class ViewModel : INotifyPropertyChanged {
// 属性通知
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public ViewModel() {
Students = new ObservableCollection<Student>() {
new Student(){ Name="Tom",Age=30},
new Student(){ Name="Liza",Age=1},
new Student(){ Name="Tim",Age=30},
};
Students.CollectionChanged += Students_CollectionChanged;
}

// 集合元素变化事件处理器,从复杂属性中查询出数据为派生属性赋值
private void Students_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
Names = new ObservableCollection<string>(from s in Students select s.Name);
Ages = new ObservableCollection<int>(from s in Students select s.Age);
}

// 复杂属性
private ObservableCollection<Student> _students = new ObservableCollection<Student>();
public ObservableCollection<Student> Students {
get => _students;
set {
if (_students != value) {
_students = value;
OnPropertyChanged(nameof(Students));

Names = new ObservableCollection<string>(from s in Students select s.Name);
Ages = new ObservableCollection<int>(from s in Students select s.Age);
}
}
}

// 派生属性Names,定义成普通属性,与界面的一个ListBox单向绑定
private ObservableCollection<string> _names = new ObservableCollection<string>();
public ObservableCollection<string> Names {
get => _names;
set {
if (_names != value) {
_names = value;
OnPropertyChanged(nameof(Names));
}
}
}
// 派生属性Ages,定义成普通属性,与界面的一个ListBox单向绑定
private ObservableCollection<int> _ages = new ObservableCollection<int>();
public ObservableCollection<int> Ages {
get => _ages;
set {
if (_ages != value) {
_ages = value;
OnPropertyChanged(nameof(Ages));
}
}
}
}

方法①把派生属性的计算放到各派生属性的getter逻辑中;方法②集中放在复杂属性的setter中。方式①更佳!

情况2

场景描述:在实际项目中,可能存在一个属性与某个控件双向绑定,但同时又与另一个控件单向绑定的情况,控件1驱动源数据发生变化,接着驱动控件2发生变化。

① 如果控件2直接将该属性作为数据源,那么非常简单,无需任何额外处理!因为控件1变化后驱动属性变化,属性变化后自己会通知控件2发生变化。

② 如果控件2依赖该属性的部分数据,就也可以采用情况2中的派生属性的方法进行处理。

情况3

场景描述:在实际项目中,可能存在一个属性与某个控件双向绑定,但该属性同时又跟随另一个属性而变化。

这种情况非常简单,属性2与控件双向绑定后,在属性1的set代码中修改属性2的值即可(不需要通知属性2,属性2变化时它会自己通知控件更新)。

总结

关联属性的两种处理方式:

  • 定义只读派生属性,在派生属性中的getter计算信息,在复杂属性的setter中通知;
  • 定义常规派生属性,在复杂属性的setter中计算派生属性。

评论