WPF笔记--Binding补充

  • 让数据“为我所用”的Converter
  • 让数据“干干净净”的Validation
  • 集合控件与集合数据的Binding
  • 偷懒专用的数据中转站——DataContext
Converter

我们用到的关卡就是“数据转换器”(Data Converter)。Converter实际上就是一个类,它这个类有个要求——它需要实现IValueConverter这个接口。这个接口的内容非常简单——只有两个方法,它们分别是:

  • Convert方法:按照你的要求,把从数据源传来的数据转成你想要的数据 
  • ConvertBack方法:如果Binding是TwoWay的,那么数据目标会回传经用户改动后的数据,这时候你就不得不把数据转换回数据源里的格式——大多数情况下,它是Convert方法的逆运算,具体情况还要具体分析。 
这里讲一下,以前我们在做网站的时候页面上显示的数据有的时候会做个转换,例如页面是显示性别是男或者女,但是数据库里存的是0或者1.那么在显示的时候就要把0-1做转换和下面的这个是一个道理。页面修改了性别后存放在数据库中也会做个转换,这里就类似下面的twoway模式了。

  1. [ValueConversion(typeof(string), typeof(bool?))] //数据的源类型是string,目标类型是bool?  
  2. class ConverterYN2TF IValueConverter  
  3.  
  4.     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
  5.      
  6.         string str System.Convert.ToString(value);  
  7.         switch (str)  
  8.          
  9.             case "Y" 
  10.                 return true 
  11.             case "N" 
  12.                 return false 
  13.             default 
  14.                 return null 
  15.          
  16.      
  17.   
  18.     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
  19.      
  20.         boolSystem.Convert.ToBoolean(value);  
  21.         switch (b)  
  22.          
  23.             case true 
  24.                 return "Y" 
  25.             case false 
  26.                 return "N" 
  27.             default 
  28.                 return "Null" 
  29.          
  30.      
  31. }  
使用这个类的方法是将Binding实例的Converter属性设置为这个类的一个实例:
  1. checkBox1.IsThreeState true 
  2. Binding binding new Binding("Text");  
  3. binding.Source textBox1;  
  4. binding.Converter new ConverterYN2TF(); // 设定Converter  
  5. this.checkBox1.SetBinding(CheckBox.IsCheckedProperty, binding);

Validation

下面给出一个例子:我们以一个Slider为数据源,它的滑块可以从Value=0滑到Value=100;同时,我们以一个TextBox为数据目标,并通过Validation限制它只能将20到35之间的数据传回数据源。现实当中恐怕很少有这么干的,我们这个例子只是为了说明校验的使用方法:)

若要创建一个自定义的校验条件,需要声明一个类,并让这个类派生自ValidationRule类。ValidationRule只有一个名为Validate的方法需要我们实现,这个方法的返回值是一个ValidationResult类型的实例——这个实例携带着两个信息:

  • bool类型的IsValid属性告诉Binding回传的数据是否合法
  • object类型(一般是存储一个string)的ErrorContent属性告诉Binding一些信息,比如当前是进行什么操作而出现的校验错误等等,一般我会把这些信息写进Log文件里
这个东西和我们做网站项目页面的效果也是类似的,我们经常会用JS写一些页面输入信息的客户端基本控制,例如输入日期的时候会有数字的控制,输入数字类型的数据的时候如果输入其它的非数字字符会被过滤掉且还有提示。这里我讲的是效果是一样的。

实现好的类是这样的:

 

  1. public class MyValidationRule ValidationRule  
  2.  
  3.     public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)  
  4.      
  5.         double 0.0;  
  6.         if (double.TryParse((string)value, out d) && >= 20 && <= 35)  
  7.          
  8.             return new ValidationResult(true"OK");  
  9.          
  10.         else  
  11.          
  12.             return new ValidationResult(false"Error");  
  13.          
  14.      
  15. }  
在代码里这样使用它:
  1. Binding binding new Binding("Value");  
  2. binding.Source slider1;  
  3. binding.UpdateSourceTrigger UpdateSourceTrigger.PropertyChanged;  
  4. binding.ValidationRules.Add(new MyValidationRule()); // 加载校验条件  
  5. textBox1.SetBinding(TextBox.TextProperty, binding); 

集合Binding

我们得准备一个用来存放数据的集合,对于这个集合有一个特殊的要求,那就是,这个集合一定要是 实现了IEnumerable接口的集合 。为什么呢?原因很简单,实现了IEnumerable接口就意味着这个集合里的元素是可枚举的,可枚举就意味着这个集合里的元素是同一个类型的(至少具有相同的父类),元素是同一个类型的就意味着在每个元素中我都能找到同样的属性。

如果客户要求显示所有信息,那这种“简装版”的binding就不灵了,因为它只能拿到一个值。这时候,我们需要这样做:

  1. public Window1()
  2. {
  3.     InitializeComponent();
  4.     List stuList new List() 
  5.     {
  6.         new Student{StuNum=1, Name="Tim"Age=28},
  7.         new Student{StuNum=2, Name="Ma Guo"Age=25},
  8.         new Student{StuNum=3, Name="Yan"Age=25},
  9.         new Student{StuNum=4, Name="Xaiochen"Age=28},
  10.         new Student{StuNum=5, Name="Miao miao"Age=24},
  11.         new Student{StuNum=6, Name="Ma Zhen"Age=24}
  12.     };
  13.     this.listBox1.ItemsSource stuList;
  14.     this.listBox1.DisplayMemberPath "Name";
  15.     //this.listBox1.SelectedValuePath "StuNum";
  16.     this.stuNumTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.StuNum"Source this.listBox1 });
  17.     this.nameTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Name"Source this.listBox1 });
  18.     this.ageTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Age"Source this.listBox1 });
  19. }
这回,我们使用的是ListBox的SelectedItem属性——每当我们选中ListBox(包括其它ItemsControl)中的一个Item时,ListBox都会“默默地”自动从数据源集合里选出与当前选中Item相对应的那个条目,作为自己的SelectedItem属性值。而且,上面这个例子里我们使用到了“多级路径”—— "SelectedItem.Age",实际项目中,你可以一路“点”下去,直到取出你想要的值。

DataContext

WPF也为我们准备了一个用来放置数据的“制高点”——DataContext。

怎么理解这个数据制高点呢?让我们接着看上面的程序。现在客户的需求又变了:要求在窗体里显示两个ListBox,一个里面显示学生列表,一个里面显示老师列表,选中任何一个ListBox里的项,下面的TextBox都显示相应的详细信息。

这时候我们遇到困难了!因为一个UI元素不可能binding到两个数据源上啊!怎么办呢?这时候DataContext就派上用场了。

 

  1. <</span>Window x:Class="CollectionBinding.Window1"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="水之真谛" Height="300" Width="300">
  5.     <</span>StackPanel>
  6.         <</span>ListBox Name="stuListBox" Margin="5" Height="70" Background="LightBlue"/>
  7.         <</span>ListBox Name="tchrListBox" Margin="5" Height="70" Background="LightPink"/>
  8.         <</span>TextBox Name="idTextBox"  Margin="5" Background="LightGreen"/>
  9.         <</span>TextBox Name="nameTextBox"  Margin="5" Background="LightGreen"/>
  10.         <</span>TextBox Name="ageTextBox"  Margin="5" Background="LightGreen"/>
  11.     </</span>StackPanel>
  12. </</span>Window>
相应地,我们重构了一下Student类和Teacher类,让它们趋于一致:
  1. interface ISchoolMember
  2. {
  3.      int ID getset}
  4.      string Name getset}
  5.      int Age getset}
  6. }
  7. class Student ISchoolMember
  8. {
  9.     public int ID getset}
  10.     public string Name getset}
  11.     public int Age getset}
  12. }
  13. class Teacher ISchoolMember
  14. {
  15.     public int ID getset}
  16.     public string Name getset}
  17.     public int Age getset}
  18. }
现在让我们看看DataContext是怎么玩儿的:
  1. public Window1()
  2. {
  3.     InitializeComponent();
  4.     List stuList new List() 
  5.     {
  6.         new Student{ID=1, Name="Tim"Age=28},
  7.         new Student{ID=2, Name="Ma Guo"Age=25},
  8.         new Student{ID=3, Name="Yan"Age=25},
  9.     };
  10.     List tchrList new List()
  11.     {
  12.         new Teacher{ID=1, Name="Ma Zhen"Age=24},
  13.         new Teacher{ID=2, Name="Miao miao"Age=24},
  14.         new Teacher{ID=3, Name="Allen"Age=26}
  15.     };
  16.     stuListBox.ItemsSource stuList;
  17.     tchrListBox.ItemsSource tchrList;
  18.     stuListBox.DisplayMemberPath "Name";
  19.     tchrListBox.DisplayMemberPath "Name";
  20.     stuListBox.SelectionChanged += (sender, e) => this.DataContext this.stuListBox.SelectedItem; };
  21.     tchrListBox.SelectionChanged += (sender, e) => this.DataContext this.tchrListBox.SelectedItem; };
  22.     this.idTextBox.SetBinding(TextBox.TextProperty, new Binding("ID"));
  23.     this.nameTextBox.SetBinding(TextBox.TextProperty, new Binding("Name"));
  24.     this.ageTextBox.SetBinding(TextBox.TextProperty, new Binding("Age"));
  25. }

让我们来仔细品尝这段代码: 

stuListBox.SelectionChanged += (sender, e) => this.DataContext this.stuListBox.SelectedItem; };
tchrListBox.SelectionChanged += (sender, e) => this.DataContext this.tchrListBox.SelectedItem; };

这两句是两个Lambda表达式,实际上就是两个事件处理函数的缩写——让下游程序员不用跳转就知道两个ListBox在各自的SelectionChanged事件发生时都做什么事情。我们这里做的事情就是:哪个ListBox的选中项改变了,那就把选中的数据放到窗体的DataContext属性里,隐含地,就把前一个数据给挤走了。 

有意思的是最后三句:在为三个TextBox设置Binding的时候,我没有提供数据源——但程序一样work,为什么呢?前面我说了,DataContext是“制高点”,当一个元素发现自己有Binding但这个Binding没有Source时,它就会“向上看”——它自然会看到制高点上的数据,这时候它会拿这个数据来试一试,有没有Binding所指示的Path——有,就拿来用;没有,就再往上层去找,也就是找更高的制高点  

实际项目中,我会根据数据的影响范围来选择在哪一级上设置DataContext,以及把什么对象设置为DataContext。比如:一个ListBox里的SelectedItem需要被包含它的Grid里的其它元素共享,我就可以把ListBox.SelectedItem设置为Grid的DataContext,而没必要把ListBox设置为最顶层Window的DataContext——原则就是“范围正好,影响最小”。

这个玩意儿个人觉得最好少用,或者用的范围小,否则调试或者维护非常麻烦。





WPF 01-BootstrapperShell是一种用于启动和初始化WPF应用程序的框架。它是指示WPF应用程序在启动时应执行的代码的入口点。通常情况下,我们可以在App.xaml.cs文件中找到它。 BootstrapperShell提供了一种将应用程序的各个部分组织在一起的方式,以便在启动时执行特定的操作。这些操作可以包括设置应用程序的默认样式、添加全局资源、注册服务和创建主窗口等。通过将所有这些相关的代码集中到一个地方,我们可以更好地管控应用程序的启动过程。 通常情况下,BootstrapperShell会执行以下几个步骤: 1. 创建应用程序的主窗口:这个步骤通常在App.xaml.cs文件的OnStartup方法中完成。我们可以在这里创建一个MainWindow实例,并将其设置为应用程序的主窗口。 2. 设置应用程序的默认样式:WPF应用程序通常使用样式来定义应用程序中各个控件的外观和行为。在BootstrapperShell中,我们可以通过添加资源字典来设置应用程序的默认样式。 3. 注册服务和初始化其他组件:在应用程序启动时,我们可能需要注册一些服务或初始化其他组件,以便在应用程序中的其他地方使用。在BootstrapperShell中,我们可以执行这些操作。 4. 处理未捕获的异常:在应用程序中可能会发生未捕获的异常,我们可以通过在BootstrapperShell中实现Application.DispatcherUnhandledException事件处理程序来捕获和处理这些异常。 总而言之,WPF 01-BootstrapperShell是一种用于组织和管理WPF应用程序启动过程的框架。它提供了一个入口点来集中所有与应用程序启动相关的代码和操作,从而更好地控制应用程序的行为和外观。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值