深入浅出WPF(8)——数据的绿色通道,Binding(中)

简介:
深入浅出WPF(8)——数据的绿色通道,Binding(中)
小序:
今天中午吃完饭回工位的路上,和俺们组资深的Level 2技术支持肖老师聊了几句。我跟肖老师说,最近我在学习Binding,肖老师说——那可不是个好东西!因为如果在程序中使用了Binding,当出现错误的时候,比较难于调试。道理很简单——以前使用事件(C++里是回调)的时候,能明确地在事件处理函数里去跟踪调试,现在使用Binding,数据源和UI之间是一支封闭的“管道”,在代码中很难看到他们是在哪里关联上的、出了问题也不知道在哪里拦截、设断点。
这的确是个问题!我又去请教了别的同事。Allen同学告诉我,在Binding和数据目标(的Dependency Property)有一些事件,会在数据有传输的时候被激发,或者在Binding的Converter和Validator上下下功夫。看来Binding值得研究的地方还真多呀!
文章的篇幅毕竟有限,我只能捡工作当中用的最多的来介绍。那么,我还有哪些东西需要介绍呢?
  • 让数据“为我所用”的Converter
  • 让数据“干干净净”的Validation
  • 集合控件与集合数据的Binding
  • 偷懒专用的数据中转站——DataContext
希望我的工作能给大家搭起一个良好的学习框架——全面细致的内容尽在MSDN里,请大家阅读的时候会轻松一些。
正文 
不拘一格用数据的Converter
上篇文已经说明,Binding就是数据源与目标之间的“关联”。大多数情况下,数据从Source到Target以及从Target返回Source都是“直来直去”的,但有些场景却需要我们对数据做些转换才能为我所用。举两个典型的例子:
  • 如果数据源里的值是Y和N,如果是Y,那么UI上的CheckBox就被勾选,否则就不勾选,这就需要我们把string(也许是char)类型的数据转换成bool?类型再使用。如果Binding是TwoWay的,CheckBox的勾选操作还会把值传回数据源。
  • 如果“评论内容”TextBox里没有内容,则“提交”Button不可以点击。这是个典型的OneWay数据Binding,因为只有TextBox去影响Button的份儿。具体如何实现,大家可以先猜猜;)
想要实现这类的转换,就需要为Binding这个“绿色通道”设置“关卡”,这里我们用到的关卡就是“数据转换器”(Data Converter)。Converter实际上就是一个类,它这个类有个要求——它需要实现 IValueConverter这个接口。这个接口的内容非常简单——只有两个方法,它们分别是:
  • Convert方法:按照你的要求,把从数据源传来的数据转成你想要的数据——至于是加减乘除还是煎炒炸炖,那就要看你怎么实现函数体了
  • ConvertBack方法:如果Binding是TwoWay的,那么数据目标会回传经用户改动后的数据,这时候你就不得不把数据转换回数据源里的格式——大多数情况下,它是Convert方法的逆运算,具体情况还要具体分析。(不过,熟饭估计怎么着也变不成生米了,呵呵~~)
 下面是第一个例子的核心代码,我来一步一步实现。
第一步:先声明一个类。我的习惯是 用Converter开头,后缀是“源类型2目标类型”,这里的“2”是“to”的意思。
 
  1. class ConverterYN2TF   
  2. {   
  3.   
  4. }  
第二步:让这个类实现 IValueConverter接口。这里有个使用VS2008的小窍门——在类名后写上“:  IValueConverter”后,按下键盘上的“Shift+Alt+F10”会弹出VS2008的智能菜单,选择其中的第一项“实现IValueConverter的方法”,VS2008会自动为我们生成需要实现的方法体:
  1. class ConverterYN2TF : IValueConverter   
  2.  
  3.     #region IValueConverter Members   
  4.   
  5.     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)   
  6.     {   
  7.         throw new NotImplementedException();   
  8.     }   
  9.   
  10.     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)   
  11.     {   
  12.         throw new NotImplementedException();   
  13.     }  
  14.     #endregion   
  15. }  
第三步:添加Attribute。这步不是必需的,但加上有是有好处的——告诉Converter数据的源类型与目标类型各是什么。值得注意的是,CheckBox的IsChecked属性是bool?类型的(可空bool类型),意思是说可以是True/False/Null三种值,表现在UI上就是勾选/不勾选/中间态。如果想让CheckBox能显示中间态,需要把它的IsThreeState属性设为True。
  1. [ValueConversion(typeof(string), typeof(bool?))] //数据的源类型是string,目标类型是bool?   
  2. class ConverterYN2TF : IValueConverter   
  3.  
  4.     #region IValueConverter Members   
  5.   
  6.     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)   
  7.     {   
  8.         throw new NotImplementedException();   
  9.     }   
  10.   
  11.     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)   
  12.     {   
  13.         throw new NotImplementedException();   
  14.     }  
  15.     #endregion   
  16. }  
第四步:实现这两个方法。在开始动手前,我们先分析一下这两个方法的参数和返回值。
首先,这两个方法的参数和返回值都是object类型的,之所以这样做,是因为接口的设计者并不知道你要传入和传出的数据是什么类型的,只好用它们“绝对正确”的基类——object了。
其次,对于Convert方法来说, value是从数据源传来的数据,返回值是转换好后发送给数据目标的数据。对于ConvertBack方法正好反过来,value是从数据目标(比如UI)传回来的数据,返回值是要与数据源匹配的数据。
再次,偶尔我们会用到parameter那个参数。比如在转换某些数据的时候,我们需要依赖一些其它的外部数据来辅助我们的数据转换,这时候就可以在parameter上打主意了。如果想传多个参数的话,可以把这些参数打包成数组或者class/struct等数据结构再传进来。在我们工作的代码中用到过一次parameter,我为我的Converter类准备了一个带参数的构造函数,把外部的辅助数据传给Converter
最后,如果你的Binding是OneWay的,那么恭喜你——你的ConvertBack函数体随便怎么实现都可以——因为它不可能被调用。
完成的类是这样的:
  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.         bool? b = System.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);  
 
至于上面的第二个例子,留给大家自己动手去实现吧。想一想:怎样才能让Button的IsEnable属性与TextBox中文本的有无关联上呢? 
  让数据“干干净净”的Validation
再让我们来看看如何对数据进行“安检”。 
首先,这里有一个“霸王条款”——Binding认为从数据源出去的数据都是“干净”的,所以不进行校验;只有从数据目标回传的数据才有可能是“脏”的,需要校验。
其次,对于一个Binding而言,Converter只能有一个,而校验条件可以是好几个——它们存储在Binding的ValidationRules这个集合里。其实,数据校验与转换做的事儿差不多。
下面给出一个例子:我们以一个Slider为数据源,它的滑块可以从Value=0滑到Value=100;同时,我们以一个TextBox为数据目标,并通过Validation限制它只能将20到35之间的数据传回数据源。现实当中恐怕很少有这么干的,我们这个例子只是为了说明校验的使用方法:)
若要创建一个自定义的校验条件,需要声明一个类,并让这个类派生自ValidationRule类。ValidationRule只有一个名为Validate的方法需要我们实现,这个方法的返回值是一个ValidationResult类型的实例——这个实例携带着两个信息:
  • bool类型的IsValid属性告诉Binding回传的数据是否合法
  • object类型(一般是存储一个string)的ErrorContent属性告诉Binding一些信息,比如当前是进行什么操作而出现的校验错误等等,一般我会把这些信息写进Log文件里
实现好的类是这样的:
  1. public class MyValidationRule : ValidationRule   
  2. {   
  3.     public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)   
  4.     {   
  5.         double d = 0.0;   
  6.         if (double.TryParse((string)value, out d) && d >= 20 && d <= 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);  
程序执行起来之后的效果是这样的:
我可以使用Slider滑出从0到100的值来,也可以使用TextBox输入20到35之间的值;但当我输入小于20或者大于35的数字以及非数字时,值就不会被传回到Slider(数据源),同时,TextBox还会被一个红色的边框圈起来以示警告。至于警告的风格,我们可以自定义,具体怎么定义我会放在Style一节去叨叨。
 
TO BE CONTINUE...
下篇文中我们将介绍非常实用的集合Binding与懒人的最爱——DataContext
 
小知识:什么是L2
CA有些同事(而且是C++比较牛的同事)是做Level 2的,也就是“二级技术支持”,Level 1是接客户电话、对客户进行支持的——他们最重要的工作就是当用户发现bug时用开心的口吻告诉他们:“Hi,that a feature:)”,呵呵,开个玩笑,实际上他们经常面对怒气冲冲、快要发疯的客户。如果真的是个bug,那L1的同学们也搞不定,这时候就要把bug提交给L2的同学们了,L2的同学们会连夜改代码、解bug、出SP包……算是我们的“救火队员”了,公司的良好信誉离不开他们的努力。
======================================================
广而告之:
  1. 急聘.NET开发3~5年经验者3名,C++开发3~5年经验者3名!有意者请将简历发送至[email]no_sound@hotmail.com[/email],或在我的空间留言。
  2. 在下长期帮助朋友招聘.NET开发/C++开发/Java开发/软件测试俊杰人才,有意者请将简历发送至[email]no_sound@hotmail.com[/email],或在我的空间留言。我的空间同时也有不少猎头朋友关注,机会也很多。
  3. 在下可以在业余时间提供.NET开发的入门和初级培训。对于非营利机构及与学生学习就业相关的社团组织,本人将提供免费培训服务
  4. 转载本文,请将上述广告一并转载,谢谢!  









本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/95275,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
|
3月前
|
传感器 C# 监控
硬件交互新体验:WPF与传感器的完美结合——从初始化串行端口到读取温度数据,一步步教你打造实时监控的智能应用
【8月更文挑战第31天】本文通过详细教程,指导Windows Presentation Foundation (WPF) 开发者如何读取并处理温度传感器数据,增强应用程序的功能性和用户体验。首先,通过`.NET Framework`的`Serial Port`类实现与传感器的串行通信;接着,创建WPF界面显示实时数据;最后,提供示例代码说明如何初始化串行端口及读取数据。无论哪种传感器,只要支持串行通信,均可采用类似方法集成到WPF应用中。适合希望掌握硬件交互技术的WPF开发者参考。
75 0
|
3月前
|
数据处理 开发者 C#
WPF数据绑定实战:从零开始,带你玩转数据与界面同步,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据绑定是核心技能之一,它能实现界面元素与数据源的同步更新。本文详细介绍了WPF数据绑定的概念与实现方法,包括属性绑定、元素绑定及路径绑定等技术,并通过示例代码展示了如何创建数据绑定。通过数据绑定,开发者不仅能简化代码、提高可维护性,还能提升用户体验。无论初学者还是有经验的开发者,都能从中受益,更好地掌握WPF数据绑定技巧。
91 0
|
3月前
|
开发框架 前端开发 搜索推荐
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
|
3月前
|
开发框架 .NET C#
WPF/C#:显示分组数据的两种方式
WPF/C#:显示分组数据的两种方式
60 0
|
3月前
|
XML C# 数据格式
WPF/C#:如何将数据分组显示
WPF/C#:如何将数据分组显示
38 0
|
3月前
|
C# Windows
WPF/C#:如何显示具有层级关系的数据
WPF/C#:如何显示具有层级关系的数据
53 0
|
存储 自然语言处理 C#
WPF技术之Binding
WPF(Windows Presentation Foundation)是微软推出的一种用于创建应用程序用户界面的框架。Binding(绑定)是WPF中的一个重要概念,它用于在界面元素和数据源之间建立关联。通过Binding,可以将界面元素(如文本框、标签、列表等)与数据源(如对象、集合、属性等)进行绑定,从而实现数据的双向传递和同步更新。
288 2
WPF技术之Binding
|
算法 C# UED
浅谈WPF之控件模板和数据模板
WPF不仅支持传统的Windows Forms编程的用户界面和用户体验设计,同时还推出了以模板为核心的新一代设计理念。在WPF中,通过引入模板,将数据和算法的“内容”和“形式”进行解耦。模板主要分为两大类:数据模板【Data Template】和控件模板【Control Template】。
206 8
WPF-Binding问题-模板样式使用Binding TemplatedParent与TemplateBinding区别
WPF-Binding问题-模板样式使用Binding TemplatedParent与TemplateBinding区别
232 0