WPF基础之数据绑定

将ViewModel设置为View自身

1
2
3
4
5
//在构造函数中设置
public View(){
InitializeComponent();
DataContext = this;
}

Binding

Binding

在MVVM模型中,将View中的控件的依赖属性与ViewModel或其它实体的内容绑定。

Binding源与路径

控件作为Binding源

1
2
3
4
5
<!-- xaml里不能直接访问控件对象,应使用ElementName指定源 -->
<TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}"/>
<!-- Binding类构造器本身可以接收Path参数可以简写为 -->
<TextBox x:Name="textBox1" Text="{Binding Value,ElementName=slider1}"/>
<Slider x:Name="slider1" Maximum="100" Minimum="0"/>
1
2
3
4
5
this.textBox1.SetBinding(TextBox.TextProperty,new Binding(){Path=new PropertyPath("Value"),ElementName="slider1"});
//C#代码可以直接访问控件对象也可以写为
this.textBox1.SetBinding(TextBox.TextProperty,new Binding(){Path=new PropertyPath("Value"),Source="slider1"});
//使用Binding的构造器写为
this.textBox1.SetBinding(TextBox.TextProperty,new Binding("Value"){Source="slider1"});

Binding的方向及数据更新

  1. 属性Mode控制Binding数据流向可选为 TwoWay,OneWay,OnTime,OnWayToSource,Default
  2. 属性UpdateSourceTrigger控制Binding目标的更新时机 可选为PropertyChanged,LostFocus,Explicit,Default
  3. 属性NotifyOnSourceUpdated和属性NotifyOnTargetUpdated设为True时,当源或目标更新时,会激发SourceUpdated和TargetUpdated事件。

Binding的路径Path

  1. 多级路径
1
2
3
<TextBox x:Name="textBox1" Text="{Binding Text.Length,ElementName=textBox2,Mode=OneWay}"/>
<!-- 集合类型的索引器(Indexer)又称为带参属性,既然是属性,也可以作为Path来使用。 点也可以省略-->
<TextBox x:Name="textBox1" Text="{Binding Text.[3],ElementName=textBox2,Mode=OneWay}"/>
  1. 当使用集合或DataView作为Binding源时,如果我们想把它的默认元素当做Path来使用,则需要使用这样的语法
1
2
3
4
List<string> stringList = new List<string>(){"Tim","Tom","Blog"};
this.textBox1.SetBinding(TextBox.TextProperty,new Binding("/"){Source=stringList});
this.textBox2.SetBinding(TextBox.TextProperty,new Binding("/Length"){Source=stringList,Mode=BindingMode.OneWay});
this.textBox3.SetBinding(TextBox.TextProperty,new Binding("/[2]"){Source=stringList,Mode=BindingMode.OneWay});
  1. 如果集合元素属性仍然是一个集合,使用多级斜线的语法。待完善。

没有Path的Binding

  1. Binding源本身就是数据,用"."表示,xaml可以省略,C#中不可以
1
2
3
4
5
6
7
8
9
10
<StackPanel>
<StackPanel.Resources>
<sys:String x:Key="string1">
本来无一物,何处惹尘埃
</sys:String>
</StackPanel.Resources>
<TextBox x:Name="textBox1" Text="{Binding Path=.,Source={StaticResource ResourceKey=string1}}">
<TextBox x:Name="textBox2" Text="{Binding .,Source={StaticResource ResourceKey=string1}}">
<TextBox x:Name="textBox3" Text="{Binding Source={StaticResource ResourceKey=string1}">
</StackPanel>
1
2
string string1 = "本来无一物,何处惹尘埃";
this.textBox1.SetBinding(TextBox.TextProperty,new Binding("."){Source=string1});

为Binding指定源的几种方法。

  • 普通CLR对象类型,只要类型实现INotifyPropertyChanged接口并在属性的set语句中激发PropertyChanged事件来通知Binding目标已经更新。
  • 普通CLR集合类型,包括数组 List,ObservableCollection 作为ItemsControl的ItemsSource属性的源。
  • ADO.NET数据对象,包括DataTable和DataView等对象。
  • 使用XmlDataProvider把XML数据指定为Source,可以用XML表示单个数据对象或集合,可以作为TreeView和Menu等级联式的控件关联的Binding的源。
  • 把依赖对象指定为Source,依赖对象不仅可以作为Binding的目标,同时也可以作为Binding的源。
  • 把容器的DataContext指定为Source(WPF Data Binding的默认行为)。
  • 通过ElementName指定Source
  • 通过Binding的RelativeSource属性相对的指定Source:当控件需要关注自己的,自己容器的或者自己内部元素的某个值就需要使用这个方法。
  • ObjectDataProvider对象指定Source:当数据源的数据不是通过属性而是通过方法暴露给外界的时候,我们可以使用者两种对象来包装数据源再把他们指定为Source。
  • 把使用LINQ检索得到的数据对象作为Binding的源。

没有Source的Binding----使用DataContext作为Binding的源

当一个Binding只知道Path而不知道Source时,会沿着UI元素树一直向树的根部去寻找
DataContext是一个依赖属性,依赖属性的重要特点是当你没有未控件的某个依赖属性显示赋值时,控件会把自己容器的属性值借过来当做自己的属性值,实际上是属性值沿着UI元素乡下传递了。
既没有Path也没有Source的情况

1
2
3
4
5
6
7
8
9
10
<StackPanel>
<StackPanel.DataContext>
<sys:String>Hello DataContext</sys:String>
</StackPanel.DataContext>
<Grid>
<StackPanel>
<TextBox Text="{Binding}"></TextBox>
</StackPanel>
</Grid>
</StackPanel>

DataContext的灵活用法。

  1. 当UI上的多个控件都使用Binding关注同一个对象时。
  2. 当作为Source的对象不能被直接访问的时候。比如B窗体内的控件想把A窗体的控件当做自己的Binding源时,但A窗体内的控件是private访问级别的,这时候就可以把这个控件(或者控件的值)作为窗体A的DataContext(这个属性是public访问级别的)从而暴露数据。????????

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

1
2
3
4
5
6
7
8
9
List<Student> students = new List<Student>(){
new Student(){Id=0,Name="Tim",Age=20,
···
}
}
this.listBoxStudents.ItemsSource= students;
this.listBoxStudents.DisplayMemberPath="Name";//此属性被赋值后会自动创建Path赋值创 Binding。
Binding binding = new Binding("SelectedItem.Id"){Source=listBoxStudents};
this.textBox1.SetBinding(TextBox.TextProperty,binding);
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 需要设置ItemsSource属性 -->
<ListBox x:Name="listBoxStudents">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Age}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

使用ADO.NET对象作为Binding的源。

listbox

1
2
3
DataTable dt = this.Load();
this.listBoxStudents.DisplayMemberPath="Name";
this.listBoxStudents.ItemsSource=dt.DefaultView;

listView

1
2
3
4
5
6
7
8
9
<ListView x:Name="listViewStudents">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}">
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}">
<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}">
</GridView>
</ListView.View>
</ListView>
  • ListView 派生自 ListBox GridView派生自ViewBase ListView的View属性是一个ViewBase对象,是组合模式。

使用XML数据作为Binding的源

使用XmlDataProvider 和XPath

1
2
3
4
5
6
7
8
9
XmlDocument doc = new XmlDocument();
doc.Load(@"D:\RawData.xml");

XmlDataProvider xdp = new XmlDataProvider();
xdp.Document = doc; //使用Document属性
xdp.Source = new Uri(@"D:\RawData.xml"); //使用SOUr
xdp.XPath = @"/StudentList/Student";
this.listViewStudents.DataContext=xdp;
this.listViewStudents.SetBinding(ListView.ItemsSourceProperty,new Binding())

使用LINQ检索结果作为Binding的源

1
this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;

使用ObjectDataProvider对象作为Binding的源

使用BInding的RelativeSource

  • 当一个Binding有明确的数据来源时我们可以通过为Source或ElementName赋值的办法让Binding与之关联。
1
2
3
4
5
6
//多层布局
RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
rs.AncestorLevel = 1;
rs.AncestorType=typeof(Grid);
Binding binding = new Binding("Name"){RelativeSource=rs};
this.textBox1.SetBinding(TextBox.TextProperty,binding);
1
<TextBox Text="{Binding Name,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}AncestorLevel=1}}"/>
1
2
3
4
5
//自身属性
RelativeSource rs = new RelativeSource();
rs.Mode = RelativeSourceMode.Self;
Binding binding new Binding("Name"){RelativeSource=rs};
this.textBox1.SetBinding(TextBox.TextProperty,binding)

RelativeSourceMode枚举值取值有PreviousData TemplatedParent Self FindAncestor
三个静态属性 PreviousData Self TemplatedParent 直接返回上述类型的三个实例。

Binding对数据的转换与校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//验证逻辑 
public class RangeValidationRule:ValidationRule{
public override ValidationResult Validate(object value,System.Globalization.CultureInfo cultureInfo){
double d = 0;
if(double.TryParse(value.ToString(),out d)){
if(d>=0&&d<=100){
return new ValidationResult(true,null);
}
}
return new ValidationResult(false,"Validation Failed");
}
}
//绑定
Binding binding = new Binding("Value"){Source = this.slider1};
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rule = new RangeValitionRule();
binding.ValidationRules.Add(rule);
this.textBox1.SetBinding(TextBox.TextProperty,binding);

默认校验机制为Source更新Target(用户输入)更新时不校验,如果改变可以设置可已设置ValidatesOnTargetUpdated设置为true;

Binding的数据转换

实现继承IValueConverter的类

1
2
3
<local:MyValueConverter x:key="cts"/>
...
<TextBox Text="{Binding Name,Converter={StaticResource cts}}"/>