WPF学习笔记2---数据绑定1

通过C#代码进行数据绑定

在使用C#代码进行数据绑定之前,要绑定的数据需要具有通知变更的能力,因此它需要继承自INotifyPropertyChanged并实现PropertyChanged接口

例如我们需要将一个TextBox的值与Class1name属性绑定,我们可以这么做

首先在Class1中定义相关属性并实现接口

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
// Class1.cs
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp1
{
//继承INotifyPropertyChanged类
class Class1 : INotifyPropertyChanged
{
//实现PropertyChanged接口
public event PropertyChangedEventHandler? PropertyChanged;
string name;
public string Name {
get { return this.name; }
set { name = value;
//当属性变更时激发事件
if(this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); }

}
}

}
}

PropertyChanged.Invoke方法接受2个参数,一个是事件来源,一个是属性变更事件的参数

然后我们就可以在xaml文件对应的cs文件中进行数据的绑定,首先要定义Binding对象并设置相关参数,参数包括数据源(Source),路径(Path),

绑定的目标属性

举一个例子,我现在将TextBox中所显示的值与Class1的name属性相绑定,那么Source就是Class1,Path就是Name,目标属性就是TextBox.TextProperty,

为什么是TextBox.TextProperty而不是TextBox.Text,那是因为他所绑定的必须是一个依赖属性,在WPF中带有Property后缀的通常就是一个依赖属性

我们在代码初始化后添加如下代码

1
2
3
Binding bind = new Binding();
bind.Source = class1;
bind.Path = new PropertyPath("Name");

然后调用BindingOperations.SetBinding方法进行绑定

1
BindingOperations.SetBinding(this.inputbox,TextBox.TextProperty, bind);

其实上面的代码可以简化为下面一行

1
this.inputbox.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = class1 });

数据流的方向

在上面的代码中我们还可以设置一个Mode属性,用于指定数据流的方向,具体参照下面表格

Mode 解释
OneWay 数据单向绑定,只有当数据源变化时所绑定的对象才变化,而所绑定对象的值变化时数据源不变化
TwoWay 数据双向绑定,当数据源变化时所绑定的对象变化,所绑定对象的值变化时数据源也变化
OneWayToSource 数据单向绑定,与OneWay相反,只有绑定对象的值变化数据源变化,数据源变化时绑定对象的值不变化
OneTime 只有第一次加载时才显示数据源,以后则不随数据源而变化

示例代码

1
this.inputbox.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = class1,Mode=BindingMode.OneTime });

控件与控件之间属性绑定

xaml代码中,可以使用标记扩展的功能实现控件与控件之间的属性绑定,例如我们可以将一个TextBox的值与一个Slider的值进行绑定,就像下面这样

而要实现上面的效果只需要这几行代码

1
2
<TextBox Text="{Binding Path=Value, ElementName=ASli}"></TextBox>
<Slider x:Name="ASli" Maximum="1000"></Slider>

很简单就不过多解释了需要注意的一点是当我们改变TextBox的值,下面的Slider不会立即变化而是需要当TextBox失去焦点时,我们可以通过设置UpdateSourceTrigger的值来改变这一点,UpdateSourceTrigger可以设置为一下几个值

  • PropertyChanged即为当数据改变时立即更新

  • LostFocus即当控件失去焦点时

  • Explicit只有当应用程序调用UpdateSource方法时,通常用于按下按钮进行更新

DataContext数据上下文

在上面的代码中我们实现了TextBox组件与Class1name属性进行绑定,其中我们在xaml文件对应的cs文件中实例化了一个Class1对象,并进行了数据绑定,如果我们不想再cs文件中实例化对象进行数据绑定,我们便可以使用DataContext

我们在xaml界面添加如下代码

1
2
3
<Window.DataContext>
<local:Class1 Name="默认名称"></local:Class1>
</Window.DataContext>

然后再进行数据绑定

1
<TextBox x:Name="inputbox" Height="88" FontSize="24" Text="{Binding Path=Name}"></TextBox>

当我们使用DataContext作为数据源时,无需手动设置它的Source属性,因此称之为无源数据绑定

而当我们的C#代码需要访问它的DataContext时需要进行数据转换,如下

1
2
3
4
5
6
private void OutputButton_Click(object sender, RoutedEventArgs e)
{
Class1 cl1 = (Class1)this.DataContext;
this.outputbox.Text = cl1.Name;

}

我们也可以在后端实例化对象,并将添加到页面的DataContext中,如下

首先实例化Class1对象

1
Class1 class1=new Class1() { Name = "name"};

然后再初始化代码后添加DataContext

1
this.DataContext = class1;

此时我们便无需在xaml中创建对象

将集合对象作为列表控件的ItemsSource

我们可以将一个集合对象作为列表控件的ItemsSource,ItemsSource是ItemControl类的一个属性,任何继承自这个类的空间都可以设置ItemSource属性,例如ListBox,ItemsSource属性作为它的数据源时它会自动迭代这个List/Array中的所有项,并将它们作为ListBox的每一项的数据源,我们还可以通过设置DisplayMemberPath属性为它们指定Path,例如在下面这个例子中我们定义了一个列表,并初始化了一些内容,并将它与ListBox进行数据绑定

这是Class1.cs的内容

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
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp1
{
class Class1 : INotifyPropertyChanged
{
string name;
public string Name {
get { return name; }
set { name = value;
if(this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); }

}
}
int age;
public int Age
{
get { return age; }
set
{
age = value;
if (this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Age")); }
}
}
string phonetype;
public string Phonetype
{
get { return phonetype; }
set
{
phonetype = value;
if (this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Phonetype")); }

}
}

int id;
public int ID
{
get { return id; }
set
{
id = value;
if (this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("ID")); }
}
}

public event PropertyChangedEventHandler? PropertyChanged;
}
}

这里定义了一些属性,并实现了PropertyChanged接口,然后我们在xaml对应的cs文件中创建一个Class1类型的列表,并初始化一些值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ObservableCollection< Class1 > olist = new ObservableCollection<Class1>() { 
new Class1 { Name = "小明",Phonetype="小米 13",ID=1,Age=14 },
new Class1 { Name = "小花",Phonetype="华为 mate 60 pro",ID=2,Age=24 },
new Class1 { Name = "小华",Phonetype="魅族 10",ID=3,Age=16 },
new Class1 { Name = "小军",Phonetype="一加 8",ID=4,Age=19 },
new Class1 { Name = "小俊",Phonetype="荣耀 11",ID=5,Age=10 },
new Class1 { Name = "小胡",Phonetype="苹果 14",ID=6,Age=12 },
new Class1 { Name = "小虎",Phonetype="OPPO A8",ID=7,Age=25 },
new Class1 { Name = "小帅",Phonetype="Vivo X100",ID=8,Age=32 },
new Class1 { Name = "小美",Phonetype="IQOO Neo 9",ID=9,Age=17 },
new Class1 { Name = "小壮",Phonetype="小辣椒 1",ID=10,Age=16 },
new Class1 { Name = "小丽",Phonetype="诺基亚 8",ID=11,Age=22 },
new Class1 { Name = "李华",Phonetype="OPPO A5",ID=12,Age=43 },
};
//设置源和路径
this.ID_ListBox.ItemsSource = olist;
this.ID_ListBox.DisplayMemberPath = "Name";
//将TextBox与被选中的Items的ID绑定
Binding binding = new Binding("SelectedItem.ID") { Source=this.ID_ListBox};
this.ID_TextBox.SetBinding(TextBox.TextProperty,binding);

这里使用ObservableCollection而不是List是因为它实现了接口从而使列表的变化即时显示到控件当中,我们在xaml文件中显示一个ListBox控件以及显示每个项的ID

1
2
3
4
5
6
7
<StackPanel>
<TextBlock FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="10">Class1 ID</TextBlock>
<TextBox x:Name="ID_TextBox"></TextBox>
<TextBlock FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="10">Class1 List</TextBlock>
<ListBox Height="200" x:Name="ID_ListBox">
</ListBox>
</StackPanel>

运行结果如下

image-20240713215632677\

我们还可以通过自定义ListBox的数据模版自定义显示的内容,此时我们就不需要设置DisplayMemberPath而是在xaml文件中进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<StackPanel>
<TextBlock FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="10">Class1 ID</TextBlock>
<TextBox x:Name="ID_TextBox"></TextBox>
<TextBlock FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="10">Class1 List</TextBlock>
<ListBox Height="200" x:Name="ID_ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name}" Width="120"></TextBox>
<TextBox Text="{Binding Path=Age}" Width="120"></TextBox>
<TextBox Text="{Binding Path=Phonetype}" Width="420"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>

image-20240713215859948

参考内容

WPF数据绑定

WPF数据绑定与校验转换(上)