最初始的Binding

UI

  1. <StackPanel>
  2. <TextBox x:Name="text_Show" BorderBrush="Black" Margin="5"/>
  3. <Button x:Name="btn_ShowBinding" Content="Add" Margin="5" Click="btn_ShowBinding_Click"/>
  4. </StackPanel>

后台代码 BindingOperations.SetBinding三个参数分别为绑定的UI界面的控件名,需要绑定上的控件静态属性,绑定实例,数据类实现INotifyPropertyChanged接口,使得数据发生变化后能够通知到View层。

  1. public partial class MainWindow : Window {
  2. Student stu;
  3. public MainWindow() {
  4. InitializeComponent();
  5. //Prepare the data source
  6. stu = new Student();
  7. //Prepare Binding
  8. Binding binding = new Binding();
  9. binding.Source = stu;
  10. binding.Path = new PropertyPath("Name");
  11. //Using Binding
  12. BindingOperations.SetBinding(this.text_Show, TextBox.TextProperty, binding);
  13. }
  14. private void btn_ShowBinding_Click(object sender, RoutedEventArgs e) {
  15. stu.Name = "Jack";
  16. }
  17. }
  18. class Student : INotifyPropertyChanged {
  19. private string name;
  20. public string Name {
  21. get { return name; }
  22. set {
  23. name = value;
  24. if (PropertyChanged != null) {
  25. this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
  26. }
  27. }
  28. }
  29. public event PropertyChangedEventHandler PropertyChanged;
  30. }

因为TextBox这类UI元素的基类FrameworkElement对BindingOperations.SetBinding()方法进行了封装

  1. public BindingExpression SetBinding(DependencyProperty dp, string path);
  2. public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase Binding){
  3. return BindingOperations.SetBinding(this,dp,Binding);
  4. }

代码改写为
SetBinding(UI Property,Instance of binding(binding data property){Source = binding Instance})

  1. public partial class MainWindow : Window {
  2. Student stu;
  3. public MainWindow() {
  4. InitializeComponent();
  5. //Combine three Steps
  6. //SetBinding(UI Property,Instance of binding(binding data property){Source = binding Instance})
  7. this.text_Show.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu = new Student() });
  8. }
  9. private void btn_ShowBinding_Click(object sender, RoutedEventArgs e) {
  10. stu.Name = "Jack";
  11. }
  12. }
  13. class Student : INotifyPropertyChanged {
  14. private string name;
  15. public string Name {
  16. get { return name; }
  17. set {
  18. name = value;
  19. if (PropertyChanged != null) {
  20. this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
  21. }
  22. }
  23. }
  24. public event PropertyChangedEventHandler PropertyChanged;
  25. }
  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/957395/1602744591733-b36d6d83-0c3a-492e-a997-a8d5cfc504b5.png#align=left&display=inline&height=169&margin=%5Bobject%20Object%5D&name=image.png&originHeight=204&originWidth=610&size=13348&status=done&style=none&width=504)

Binding的源和路径

把控件作为Binding源与Binding标记扩展

控件间建立关联

  1. <StackPanel>
  2. <Slider x:Name="_showSlider" Maximum="100" Minimum="0" Margin="5"/>
  3. <TextBox x:Name="_showText" Text="{Binding Path=Value,ElementName=_showSlider}" BorderBrush="Black"/>
  4. </StackPanel>

Binding构造函数有两个重载,Path可以省略Text=”{Binding Value,ElementName=_showSlider}”
C# 中 this._showText.SetBinding(TextBox.TextProperty,new Binding(“Value”){Source = this._showSlider})
在C#中很少用ElementName来绑定,而直接使用Source.

控制Binding的方向及数据更新

控制Binding数据流向的属性是Mode,TwoWay\OneWay\OnTime\OneWayToSource\Default
更新源触发方式的属性UpdateSourceTrigger:PropertyChanged\LostFocus\Explicit\Default

  1. public partial class MainWindow : Window {
  2. public MainWindow() {
  3. InitializeComponent();
  4. this._showText.SetBinding(TextBox.TextProperty, new Binding("Value")
  5. { Source = this._showSlider,Mode = BindingMode.OneWay});
  6. }
  7. }

Binding的路径(Path)

Binding的路径支持多级路径
当使用一个集合或者DataView作为Binding源时,如果想把其默认元素当Path使用,需要用/
/和[0]的效果一样
如果想绑定第二个元素则[2]

  1. public partial class MainWindow : Window {
  2. public MainWindow() {
  3. InitializeComponent();
  4. List<string> strList = new List<string>() { "Tim", "Tom", "Jack" };
  5. this.cmb_Path.SetBinding(TextBox.TextProperty, new Binding("[0]") { Source = strList, Mode = BindingMode.OneWay });
  6. }
  7. }

“没有Path”的Binding

典型的,string\int等基本类型,实例本身就是数据,Path设置为.即可,在XAML中可省略,在C#中不能省略

  1. <StackPanel>
  2. <StackPanel.Resources>
  3. <sys:String x:Key="myString">
  4. Hello Everyone
  5. </sys:String>
  6. </StackPanel.Resources>
  7. <TextBox Text="{Binding Path=.,Source={StaticResource myString}}"/>
  8. </StackPanel>

另一种形式的代码:

  1. string myString = "Hello";
  2. this.textBox1.SetBinding(TextBox.TextProperty, new Binding(".") { Source = myString });

为Binding指定源(Source)的几种方法

  • 把普通CLR类型单个对象指定为Source:.NET Framework自带类型的对象和自定义类型的对象,如果实现了INotifyPropertyChanged接口,则可以在属性的set语句里激发PropertyChanged事件来通知Binding数据已被更新。
  • 把普通CLR集合类型对象指定为Source:包括数组、List、ObservableCollection等集合类型,把控件的ItemSource属性使用Binding关联到一个集合对象上。 ```csharp

namespace WpfPrj { ///

/// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window {
public MainWindow() { InitializeComponent();

  1. //Prepare DataSource
  2. List<Student> stuList = new List<Student>() {
  3. new Student(){ID=0,Name="Jack",Age=24},
  4. new Student(){ID=1,Name="Lucy",Age=25},
  5. new Student(){ID=2,Name="Tom",Age=26}
  6. };
  7. //Binding ListBox
  8. this._stuListBox.ItemsSource = stuList;
  9. this._stuListBox.DisplayMemberPath = "Name";
  10. //Binding TextBox
  11. this._showIDTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Name") { Source = this._stuListBox });
  12. }
  13. }
  14. public class Student {
  15. public int ID { get; set; }
  16. public string Name { get; set; }
  17. public int Age { get; set; }
  18. }

}

  1. PathSource的一个属性值,TextBox绑定的PathListBox所选项目中的Name属性,SelectedItem.Name<br />ListBox.ItemTemplate模板
  2. ```csharp
  3. <StackPanel>
  4. <TextBlock Text="Student ID:" Margin="5"/>
  5. <TextBox x:Name="_showIDTextBox" BorderBrush="Black" Margin="5" Text="{Binding Path=SelectedItem.Name,ElementName=_stuListBox}"/>
  6. <TextBlock Text="StudentList" Margin="5"/>
  7. <ListBox x:Name="_stuListBox" Margin="5" BorderBrush="Black" Height="Auto">
  8. <ListBox.ItemTemplate>
  9. <DataTemplate>
  10. <StackPanel Orientation="Horizontal">
  11. <TextBlock Text="{Binding Path=ID}" Margin="5"/>
  12. <TextBlock Text="{Binding Path=Name}" Margin="5"/>
  13. <TextBlock Text="{Binding Path=Age}" Margin="5"/>
  14. </StackPanel>
  15. </DataTemplate>
  16. </ListBox.ItemTemplate>
  17. </ListBox>
  18. </StackPanel>
  19. public partial class MainWindow : Window {
  20. public MainWindow() {
  21. InitializeComponent();
  22. //Prepare DataSource
  23. List<Student> stuList = new List<Student>() {
  24. new Student(){ID=0,Name="Jack",Age=24},
  25. new Student(){ID=1,Name="Lucy",Age=25},
  26. new Student(){ID=2,Name="Tom",Age=26}
  27. };
  28. //Binding ListBox
  29. this._stuListBox.ItemsSource = stuList;
  30. }
  31. }
  32. public class Student {
  33. public int ID { get; set; }
  34. public string Name { get; set; }
  35. public int Age { get; set; }
  36. }
  • 把ADO.NET数据对象指定为Source:包括DataTable 和DataView等对象 ```csharp

public partial class MainWindow : Window {
public MainWindow() { InitializeComponent(); DataTable dataTable = new DataTable(); dataTable.Columns.Add(“ID”); dataTable.Columns.Add(“Name”); dataTable.Columns.Add(“Age”); dataTable.Rows.Add(0,”Jack”,24); dataTable.Rows.Add(1, “Tim”, 24); dataTable.Rows.Add(2, “Lucy”, 24);

//first style this.listView.ItemsSource = dataTable.DefaultView; //second style this.listView.DataContext = dataTable; this.listView.SetBinding(listView.ItemsSourceProperty,new Binding());

  1. }
  2. }
  3. public class Student {
  4. public int ID { get; set; }
  5. public string Name { get; set; }
  6. public int Age { get; set; }
  7. }
  1. - 使用XmlDataProvider把**XML数据**指定为Source
  2. - 依赖对象:形成Binding
  3. - 把容器**DataContext**指定为Source(WPF的默认行为):不设置源,自动沿着控件树一层一层向外找,直到找到带有Path指定属性的对象为止。“沿着UI树向外寻找”其实是一个错觉,DataContent是一个依赖属性,依赖属性有一个很重要的特点就是当你没有为控件的某个依赖属性显式赋值时,控件会把自己容器的属性值“借”过来。
  4. ```csharp
  5. <StackPanel>
  6. <StackPanel.DataContext>
  7. <local:Student ID="1" Name="Jack" Age="24"/>
  8. </StackPanel.DataContext>
  9. <Grid>
  10. <StackPanel>
  11. <TextBox Text="{Binding Path=ID}"/>
  12. <TextBox Text="{Binding Age}"/>
  13. <TextBox Text="{Binding Name}"/>
  14. </StackPanel>
  15. </Grid>
  16. </StackPanel>
  17. namespace WpfPrj {
  18. public partial class MainWindow : Window {
  19. public MainWindow() {
  20. InitializeComponent();
  21. }
  22. }
  23. public class Student {
  24. public int ID { get; set; }
  25. public string Name { get; set; }
  26. public int Age { get; set; }
  27. }
  28. }
  • 通过ElementName指定Source
  • Binding的RelativeSource属性相对地指定Source:当控件需要关注自己的、自己容器的或者自己内部元素的某个值就需要使用这种方法。
  • ObjectDataProvider对象指定为Source:当数据源的数据不是通过属性而是通过方法暴露给外界的时候,可以使用这种方法来包装数据源再把它们指定为Source

Create ObjectDataProvider Instance
First:Set ObjectType
Second:Set MethodName
Third:Set MethodParameters and Choose Method
BindsDirectlyToSource 直接绑定给数据源而不是包装的对象

  1. <StackPanel>
  2. <TextBox x:Name="arg1" Margin="5"/>
  3. <TextBox x:Name="arg2" Margin="5"/>
  4. <TextBox x:Name="result" Margin="5"/>
  5. </StackPanel>
  6. namespace WpfPrj {
  7. /// <summary>
  8. /// Interaction logic for MainWindow.xaml
  9. /// </summary>
  10. public partial class MainWindow : Window {
  11. public MainWindow() {
  12. InitializeComponent();
  13. //Create ObjectDataProvider Instance
  14. //First:Set ObjectType Second:Set MethodName Third:Set MethodParameters and Choose Method
  15. var odp = new ObjectDataProvider();
  16. odp.ObjectType = typeof(Calculator);//Create Instance
  17. odp.MethodName = "Add";
  18. odp.MethodParameters.Add("0");
  19. odp.MethodParameters.Add("0");
  20. //Create Binding by using ObjectDataProvider Instance
  21. Binding binding1 = new Binding("MethodParameters[0]") {
  22. Source = odp,
  23. BindsDirectlyToSource = true,
  24. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  25. };
  26. Binding binding2 = new Binding("MethodParameters[1]") {
  27. Source = odp,
  28. BindsDirectlyToSource = true,
  29. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  30. };
  31. Binding bindingResult = new Binding(".") { Source = odp };//DataSource is dataself,so it is using .
  32. this.arg1.SetBinding(TextBox.TextProperty, binding1);
  33. this.arg2.SetBinding(TextBox.TextProperty, binding2);
  34. this.result.SetBinding(TextBox.TextProperty, bindingResult);
  35. }
  36. }
  37. public class Calculator {
  38. public string Add(string arg1,string arg2) {
  39. double x = 0;
  40. double y = 0;
  41. double z = 0;
  42. if(double.TryParse(arg1,out x)&&double.TryParse(arg2,out y)) {
  43. z = x + y;
  44. return z.ToString();
  45. }
  46. return "Input Error";
  47. }
  48. }
  49. }
  • 使用Linq检索得到的数据对象作为Source

    Binding对数据的转换与校验

    Binding校验:ValidationRules
    Binding转换:Converter

    Binding的数据校验

    Binding进行校验时,默认行为是来自Source的数据总是正确的,只有来自Target的数据(用户输入的数据)才有可能有问题,为了不让数据污染Source所以需要校验。 ```csharp

namespace WpfPrj { ///

/// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { public MainWindow() { InitializeComponent();

  1. Binding binding = new Binding("Value") {
  2. Source = this.slider,
  3. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  4. };
  5. RangeValidationRule rv = new RangeValidationRule();
  6. binding.ValidationRules.Add(rv);
  7. this.textBox.SetBinding(TextBox.TextProperty, binding);
  8. }
  9. }
  10. //UI Control Data Validate,don't validate binding source data(slider value)
  11. class RangeValidationRule : ValidationRule {
  12. public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
  13. double d = 0;
  14. if (double.TryParse(value.ToString(),out d)) {
  15. if (d >= 0 && d <= 100) {
  16. return new ValidationResult(true, null);
  17. }
  18. }
  19. return new ValidationResult(false, "Validation Failed");
  20. }
  21. }

}

  1. 校验来自Source的数据:ValidatesOnTargetUpdated
  2. ```csharp
  3. public partial class MainWindow : Window {
  4. public MainWindow() {
  5. InitializeComponent();
  6. Binding binding = new Binding("Value") {
  7. Source = this.slider,
  8. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  9. };
  10. RangeValidationRule rv = new RangeValidationRule();
  11. rv.ValidatesOnTargetUpdated = true;//validate data from source
  12. binding.ValidationRules.Add(rv);
  13. this.textBox.SetBinding(TextBox.TextProperty, binding);
  14. }
  15. }
  16. //UI Control Data Validate,don't validate binding source data(slider value)
  17. class RangeValidationRule : ValidationRule {
  18. public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
  19. double d = 0;
  20. if (double.TryParse(value.ToString(),out d)) {
  21. if (d >= 0 && d <= 100) {
  22. return new ValidationResult(true, null);
  23. }
  24. }
  25. return new ValidationResult(false, "Validation Failed");
  26. }
  27. }

路由事件,错误消息的信号传递给textBox.ToolTrip
需要设置NotifyOnValidationError=true
增加一个路由事件

  1. /// <summary>
  2. /// Interaction logic for MainWindow.xaml
  3. /// </summary>
  4. public partial class MainWindow : Window {
  5. public MainWindow() {
  6. InitializeComponent();
  7. Binding binding = new Binding("Value") {
  8. Source = this.slider,
  9. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  10. };
  11. RangeValidationRule rv = new RangeValidationRule();
  12. rv.ValidatesOnTargetUpdated = true;//validate data from source
  13. binding.ValidationRules.Add(rv);
  14. binding.NotifyOnValidationError = true;//error signal
  15. this.textBox.SetBinding(TextBox.TextProperty, binding);
  16. this.textBox.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError));
  17. }
  18. private void ValidationError(object sender, RoutedEventArgs e) {
  19. if (Validation.GetErrors(this.textBox).Count > 0) {
  20. this.textBox.ToolTip = Validation.GetErrors(this.textBox)[0].ErrorContent.ToString();
  21. }
  22. }
  23. }
  24. //UI Control Data Validate,don't validate binding source data(slider value)
  25. class RangeValidationRule : ValidationRule {
  26. public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
  27. double d = 0;
  28. if (double.TryParse(value.ToString(),out d)) {
  29. if (d >= 0 && d <= 100) {
  30. return new ValidationResult(true, null);
  31. }
  32. }
  33. return new ValidationResult(false, "Validation Failed");
  34. }
  35. }

Binding数据转换Converter

实现IValueConverter
当数据流向从Binding的Source流向Target时候,Convert方法将被调用,反之,ConvertBack将被调用。
Binding对象的Mode属性会影响到上述两种方法的调用,OneWay只有Convert被调用。

  1. <Window.Resources>
  2. <models:CategoryToSourceConverter x:Key="cts"/>
  3. <models:StateToNullableBoolConverter x:Key="stnb"/>
  4. </Window.Resources>
  5. <StackPanel Margin="0,0,0,-137">
  6. <ListBox x:Name="listBoxPlane" Margin="5" Height="100">
  7. <ListBox.ItemTemplate>
  8. <DataTemplate>
  9. <StackPanel Orientation="Horizontal">
  10. <Image Height="20" Width="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
  11. <TextBlock Text="{Binding Path=Name}"/>
  12. <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
  13. </StackPanel>
  14. </DataTemplate>
  15. </ListBox.ItemTemplate>
  16. </ListBox>
  17. <Button x:Name="btnLoad" Content="Load" Margin="5" Click="btnLoad_Click"/>
  18. <Button x:Name="btnSave" Content="Save" Margin="5" Click="btnSave_Click"/>
  19. </StackPanel>
  1. private void btnLoad_Click(object sender, RoutedEventArgs e) {
  2. List<WpfPrj.Models.Plane> planes = new List<WpfPrj.Models.Plane>() {
  3. new Models.Plane(){Name ="B-1",State = Models.State.Unknown,Category = Models.Category.Bomber},
  4. new Models.Plane(){Name="B-2",State=Models.State.Available,Category = Models.Category.Fighter},
  5. new Models.Plane(){Name="B-3",State=Models.State.Locked,Category=Models.Category.Bomber},
  6. };
  7. this.listBoxPlane.ItemsSource = planes;
  8. }
  9. private void btnSave_Click(object sender, RoutedEventArgs e) {
  10. StringBuilder stringBuilder = new StringBuilder();
  11. foreach (Models.Plane item in listBoxPlane.Items) {
  12. stringBuilder.AppendLine(string.Format($"Category={item.Category},Name={item.Name},State={item.State}"));
  13. }
  14. File.WriteAllText(@"D:\Gitee\Plane.txt", stringBuilder.ToString());
  15. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Text;
  5. using System.Windows.Data;
  6. namespace WpfPrj.Models {
  7. public enum Category {
  8. Bomber,
  9. Fighter
  10. }
  11. public enum State {
  12. Available,
  13. Locked,
  14. Unknown
  15. }
  16. public class Plane {
  17. public Category Category { get; set; }
  18. public State State { get; set; }
  19. public string Name { get; set; }
  20. }
  21. //Source to Target 把source的类型转换为Target可以接受的类型
  22. public class CategoryToSourceConverter : IValueConverter {
  23. public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
  24. Category c = (Category)value;
  25. switch (c) {
  26. case Category.Bomber:
  27. return @"D:\Gitee\getting-started-with-wpf\WpfPrj\Icons\Bomber.png";
  28. case Category.Fighter:
  29. return @"D:\Gitee\getting-started-with-wpf\WpfPrj\Icons\Fighter.png";
  30. default:
  31. return null;
  32. }
  33. }
  34. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
  35. throw new NotImplementedException();
  36. }
  37. }
  38. public class StateToNullableBoolConverter : IValueConverter {
  39. public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
  40. State state = (State)value;
  41. switch (state) {
  42. case State.Available:
  43. return true;
  44. case State.Locked:
  45. return false;
  46. case State.Unknown:
  47. default:
  48. return null;
  49. }
  50. }
  51. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
  52. bool? nb = (bool?)value;
  53. switch (nb) {
  54. case true:
  55. return State.Available;
  56. case false:
  57. return State.Locked;
  58. case null:
  59. default:
  60. return State.Unknown;
  61. }
  62. }
  63. }
  64. }

多路绑定

  1. <StackPanel Margin="0,0,0,-137">
  2. <TextBox x:Name="text1" />
  3. <TextBox x:Name="text2"/>
  4. <TextBox x:Name="text3"/>
  5. <TextBox x:Name="text4"/>
  6. <Button x:Name="OK" Content="OK"/>
  7. </StackPanel>
  1. public partial class MainWindow : Window {
  2. public MainWindow() {
  3. InitializeComponent();
  4. SetBinding();
  5. }
  6. private void SetBinding() {
  7. Binding binding1 = new Binding("Text") { Source = this.text1,UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged};
  8. Binding binding2 = new Binding("Text") { Source = this.text2, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
  9. Binding binding3 = new Binding("Text") { Source = this.text3, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
  10. Binding binding4 = new Binding("Text") { Source = this.text4,UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
  11. MultiBinding binding = new MultiBinding() { Mode = BindingMode.OneWay };
  12. binding.Bindings.Add(binding1);
  13. binding.Bindings.Add(binding2);
  14. binding.Bindings.Add(binding3);
  15. binding.Bindings.Add(binding4);
  16. binding.Converter = new MulitConverter();
  17. this.OK.SetBinding(Button.IsEnabledProperty, binding);
  18. }
  19. }
  20. public class MulitConverter : IMultiValueConverter {
  21. public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
  22. if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text))
  23. &&values[0].ToString()==values[1].ToString()
  24. &&values[2].ToString()==values[3].ToString()) {
  25. return true;
  26. }
  27. return false;
  28. }
  29. public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
  30. throw new NotImplementedException();
  31. }
  32. }

PersonalSummary

使用Binding最重要的是设置源和路径 Binding - 图1