5-2.Binding指定源

5-2.Binding指定源

5-2.Binding指定源

指定源的集中方法:

  • 普通对象
  • 集合
  • ADO.NET
  • XML
  • 依赖对象
  • DataContext
  • 通过ElementName
  • 通过RelativeSource
  • ObjectDataProvider
  • Linq

DataContext作为源

DataContext是WPF控件的基类属性,也就是WPF树形结构都具有这个属性,当一个Binding只有Path而没有Source时,会沿着UI元素树一路向树的根部搜索过去,看哪个节点的DataContext具有Path所指向的属性,如果有就把这个对象作为自己的Source,如果没有就一直找下去。

如果不需要数据时,Source属性也可以直接不写Text="{Binding Path=Name}"

自动向根节点查询的原理:DataContext是一个依赖属性,依赖属性的特点是当没有为控件的某个属性显式赋值时,控件会把自己容器的属性当做自己的属性值,也就是属性值沿着UI元素向下传递了。

使用场景:

  1. UI上多个控件关注同一对象
  2. 当Source的对象不能被直接访问时,窗体B想访问窗体A的控件,但是窗体A的控件是Private,这时可以把窗体A的控件作为A的DataContext,因为DataContext属性是Public。

使用集合对象作为列表控件的源

只要为一个ItemsControl对象设置了ItemsSource,就会自动迭代其中的数据元素,并为每个元素准备一个条目容器(条目容器就是数据的外衣),并使用Binding在条目容器和数据元素之间建立联系。

1
2
3
4
5
//为listBox设置Binding
this.listBoxStudents.ItemsSource = stuList;
this.listBoxStudents.DisplayMemberPath="Name";
//当DisplayMemberPath属性被赋值后,ListBox在获得ItemsSource的时候就会创建等量的ListBoxItem条目容器
//并以DisplayMemberPath属性值为Path创建Binding,Binding的目标是ListBoxItem的内容插件(一个TextBox)。

以上创建Binding的过程是在DisplayMemberTemplateSelector类中的SelectTemplate方法中完成的,该方法的返回值为DataTemplate类型,ListBox的ItemTemplate属性的类型是DataTemplate。

自定义设置DataTemplate案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <StackPanel>
<TextBlock Text="id"/>
<TextBox x:Name="txtBoxId" Text="{Binding Path=SelectedItem.Id, ElementName=listBox}"/>
<TextBlock Text="student List"/>
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>

在构造函数中指定源this.listBox.ItemsSource = stuList;

在使用集合作为列表的ItemsSource时,一般考虑用ObservableCollection<T>代替List<T>,因为ObservableCollection<T>实现了INotifyCollectionChanged和INotifyPropertyChanged接口,能把集合的变化立刻通知控件进行显示。

ADO.NET对象作为源

1
2
3
4
5
6
7
this.listView.ItemsSource = stuList;
DataTable dt = Load();
this.listView.ItemsSource = dt.DefaultView;//DataView实现了IEnumerable接口

//不能直接将dt作为ItemsSource使用
//但是当把DataTable放在一个对象的DataContext中,并把ItemsSource与一个既没有指定Source又没有指定Path的Binding关联起来
//Binding会自动找到它的DefaultView作为自己的Source来使用
1
2
3
4
5
6
7
8
9
10
11
<StackPanel>
<ListView x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/>
<GridViewColumn Header="Name" Width="60" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>

ListView是ListBox的派生类,ListView的View属性是一个ViewBase类型(GridView的基类)。

GridView的内容属性是Columns(GridViewColumnCollection类型对象),GridViewColumn对象一个重要属性是DisplayMemberBinding,功能类似于ListBox的DisplayMemberPath属性。如果用更复杂的结构来表示Header和数据,可以为GridViewColumn设置HeaderTemplate和CellTemplate属性,类型都是DataTemplate。

XML作为源

使用XML数据作为Binding的Source时要使用XPath属性而不是Path属性来指定数据的来源

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
<!--在文件中的xml-->
<StudentList>
<Student Id="1">
<Nmae>Tim</Nmae>
</Student>
<Student Id="2">
<Nmae>Tom</Nmae>
</Student>
<Student Id="3">
<Nmae>Vina</Nmae>
</Student>
</StudentList>


<StackPanel>
<ListView x:Name="listView">
<ListView.View>
<GridView>
<!--@符号表示使用的Attribut,不加@符合表示子级元素-->
<GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}"/>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding XPath=Name}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
1
2
3
4
5
6
7
8
9
10
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);

XmlDataProvider xdp = new XmlDataProvider();
xdp.Document = doc;//也可以直接写成xdp.Source = new Uri(xmlPath)


xdp.XPath = @"StudentList/Student";
this.listView.DataContext = xdp;
this.listView.SetBinding(ListView.ItemsSourceProperty, new Binding());

也可以把XML数据和XMLDataProvider对象直接写在XAML代码中

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
<Window.Resources>
<XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
<x:XData>
<FileSystem xmlns="">
<Folder Name="PRO">
<Folder Name="Win">
<Folder Name="A"/>
<Folder Name="A2"/>
<Folder Name="A3"/>
<Folder Name="A4"/>
</Folder>
</Folder>
<Folder Name="PRO1">
<Folder Name="Win1">
<Folder Name="A1"/>
<Folder Name="A12"/>
<Folder Name="A13"/>
<Folder Name="A14"/>
</Folder>
</Folder>
</FileSystem>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<StackPanel>
<TextBlock Text="ok"/>
<TreeView ItemsSource="{Binding Source={StaticResource xdp}}" >

<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
<TextBlock Text="{Binding XPath=@Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>

LINQ结果作为源

Linq查询结果是一个IEnumberable<T>,可以作为列表控件的ItemsSource使用。

this.listView.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") Select stu;

ObjectDataProvider作为源

当需要的数据没有被暴露出来,如设置了private,此时就需要使用ObjectDataProvider,它把对象作为数据源提供给Binding,前面用的XmlDataProvider和ObjectDataProvider的父类都是DataSourceProvider抽象类。

1
2
3
4
5
<StackPanel>
<TextBox x:Name="txtArg1"/>
<TextBox x:Name="txtArg2"/>
<TextBox x:Name="txtResult"/>
</StackPanel>
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
public class Calculator
{
public string Add(string a,string b)
{
return a + b;
}
}


private void SetBindging1()
{
//创建并配置ObjectDataProvider对象
ObjectDataProvider odp = new ObjectDataProvider();
// odp.ObjectInstance = new Calculator();//把一个Calculator对象包装在了ObjectInstance中
//另一种方式
//odp.ObjectType = typeof(Calculator);
//odp.ConstructorParameters.Add("0");
//odp.ConstructorParameters.Add("0");
odp.MethodName = "Add";
odp.MethodParameters.Add("0");
odp.MethodParameters.Add("0");

//以ObjectDataProvider为source创建Binding
Binding bindingToArg1 = new Binding("MethodParameters[0]") {//path是ObjectDataProvider对象MethodParameters属性的第一个元素
Source = odp,
BindsDirectlyToSource =true,//Binding只负责把从UI收集到的数据写入Source(ObjectDataProvider对象),而不是被Provider包装到的Calculator对象
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged //一旦修改立即传回source
};
Binding bindingToArg2 = new Binding("MethodParameters[1]")
{
Source = odp,
BindsDirectlyToSource = true,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};

Binding bindingToResult = new Binding(".") { Source = odp };

//将Binding关联到UI元素上
this.txtArg1.SetBinding(TextBox.TextProperty, bindingToArg1);
this.txtArg2.SetBinding(TextBox.TextProperty, bindingToArg2);
this.txtResult.SetBinding(TextBox.TextProperty, bindingToResult);
}

  • 将枚举作为Combobox的ItemsSource的案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <ObjectDataProvider
    x:Key="CombinationTypeEnum"
    MethodName="GetValues"
    ObjectType="{x:Type sys:Enum}">
    <ObjectDataProvider.MethodParameters>
    <x:Type TypeName="model:CombinationType" />
    </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

    <!--使用-->
    <ComboBox ItemsSource="{Binding Source={StaticResource WorkConditionGradeEnum}}"/>
    上面ObjectDataProvider对应的C#代码为Array a= System.Enum.GetValues(typeof(CombinationType));
    一般情况下,认为数据从哪里来,哪里就是source,到哪去哪里就是Target。所以会认为前两个TextBox应该是ObjectDataProvider的数据源,但实际上,3个TextBox都是以ObjectDataProvider为数据源,前两个TextBox只是在数据流上做了限制。

使用Binding的RelativSource

当不确定源的名字,但是知道源和目标在UI上的相对关系,这时便可以使用RelativeSource属性。

RelativSource属性的数据类型为RelativeSource,可以通过这个类控制搜索相对数据源的方式。

1
2
3
4
5
6
7
8
9
<Grid x:Name="g1" Background="Red" Margin="10">
<DockPanel x:Name="d1" Background="Gray" Margin="10">
<Grid x:Name="g2" Background="Blue" Margin="10">
<DockPanel x:Name="d2" Background="Yellow" Margin="10">
<TextBox x:Name="txtBox" Background="Green" Margin="10"/>
</DockPanel>
</Grid>
</DockPanel>
</Grid>

1
2
3
4
5
RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
rs.AncestorLevel = 1;//设置偏移量
rs.AncestorType = typeof(Grid);//源类型
Binding binding = new Binding("Name") { RelativeSource = rs };
this.txtBox.SetBinding(TextBox.TextProperty, binding);

在XAML的等效代码

<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"/>

关联自身Name属性

1
2
3
RelativeSource rs = new RelativeSource(RelativeSourceMode.Self);
Binding binding = new Binding("Name") { RelativeSource = rs };
this.txtBox.SetBinding(TextBox.TextProperty, binding);

作者

步步为营

发布于

2024-05-08

更新于

2025-03-15

许可协议