WPF快速入手实例篇 --- Binding使用

 

  本人以前单位是使用JavaB/S开发的。近期刚刚跳槽到了一家不错的公司,很幸运的赶上项目中使用WPF,于是便踏上了WPF之路。虽然以前大学时也学习过很长一段时间的C#,不过那时候的.NET才刚刚到2.0 Beta版,和现在比起来很多特性都还没有,而且缺少项目实践,因此,只是学 习到了C#的基础知识。

 

  经过了一段时间的项目培训和学习,对WPF已经有了一定了了解,总结了以下,拟定了一套比较快速的入手练习,供大家分享,希望能够对各位初学者快速入手有所帮助。练习过程中,部分内容会做一些中英文互译便于大家理解及日后阅读相关资料,再有就是一些不太方便翻译的内容,为了不误导大家还是直接用英文了。

 

 读者定位

  在校学生:在校学生有志向学习新的知识,但是经常苦于没有项目实践,即使有一些模拟项目,但由于没有实际参与过项目的开发,经常会不经意间忽略掉许多实际问题或是没有一个好的成熟的解决方案。(我在学校时就是如此,虽然当时认为自己挺不错的了,但是现在回过头来看看自己当时写的东西,确实自己都有些发笑)因此,在后续的文章中,我会尽量融入一些实际工作中的经验,供大家参考,相信会有一定的指导意义。

项目工作者:项目工作需要接触到WPF,但是还没有入手的朋友们,借此可以体会以下WPF的在项目工作中的实际效益。

熟悉WPF的人:对这些人群就没有什么指导意义了,希望各位大哥帮忙审阅一下吧,如果有什么描述不周的地方还请及时指点。

 

 基础知识

  当然,快速学习绝不是从零学起的,良好的基础是快速入手的关键,下面先为大家摞列以下自己总结的学习WPF的几点基础知识:

 

1)      C#基础语法知识(或者其他.NET支持的语言):这个是当然的了,虽然WPFXAML配置的,但是总还是要写代码的,相信各位读者应该也都有这个基础了。

2)      HTML语言:虽然WPF是窗体程序但是由于使用的XAML语言,如果以前接触过HTMLXHTMLASP.NET之路的东西的话会,接受这些标签会很有帮助的,如果以前一直是从事win form开的人来说可能就要适应一下了。

3)      C#中的代理、事件要做到熟练掌握,在.NET 3.0 版本后有增加了Routed Events,要想日后不糊涂,这个是基础。

4)      有一定的winformASP.NET经验,主要是对控件事件的处理要有写了解。

5)      拥有良好的面向对象的思想:思想是语言的升华(本人的OO思想完全是Java中领悟来的)。在WPF中,经常要灵活运用各种继承关系、多态、重载等,因此一定要把基础知识打牢固。

6)      DataBinding要有所了解:BindingWPF的一大亮点,在接触它以前如果接触国ADO.NET里面的DataBinding的话对相对起来会容易接受一点,虽然这两个有一定的不同(加分项)

7)      对设计模式要有一定的了解:当然是越深入越好了,在实际项目中,各种设计模式经常交融使用,快速的识别并合理的运用,无论是在开发还是调试时都是非常高效的。另外,WPF存在的初衷即是表现与逻辑的松耦合,最普遍的情况就是XAML作为表现层,背后.cs文件作为逻辑层。因此,日后在从事项目工作时,要时刻谨记这一点,千万不可背道而驰。不要为了凸显自己的某一些代码特长而将各种逻辑混写在一起,这样非常不实际的,这一点都在校生应该尤为重要(这里没有鄙视在校学生的意思,因为,我在上学的时候就经常犯这种错误,因此前车之鉴,请各位不要再走前人的弯路罢了)。(加分项)

8)      XML的理解:XAML也是XML,对XML的理解绝对有助于快速的接受和使用XAML,并不需要多XML有多么高深的见解,但是最好还是有空看一看XML相关的书籍http://www.w3.org/TR/2000/WD-xml-2e-20000814。无论是WPFWCP以及底层一些的SOAP等都是很有帮助的。(加分项)

 

 入手练习基础环境

  开发环境:VS 2008

 

  数据库:本机SQL Server 2005(这里给大家一个提醒,如果大家的机子是Windows XP的话,无论是home 或是 professional,一定不要安装SQL Server 2005 Enterprise Edition,在《安装 SQL Server 2005 的硬件和软件要求》中“操作系统要求”列表有写到2005 Enterprise Edition 不支持 XP),建议安装Developer Edition,一定安装数据库实例。

 

 练习内容

  具备以上基础条件后,开始入手练习,大家不要抱怨入手练习有写难度,毕竟是快速入门要有一定的跳跃性:

 

  我们要做一个WPF程序,功能很简单:

 

1)      从数据库(本地数据库(local)/AdventureWorks中的person.contact表中提取用户的ContactID,FirstName,LastName,EmailAddress数据,展示到Form上的一个ListView上。(由于是WPF练习,对于ADO.NET相关的东西在此不做介绍,知识使用而已)

2)      当鼠标或其他设备选中结果某一项记录时,在List框下面展示出细节。

3)      修改其中的内容后,结果联动更新到List框及数据库中。

 

  想象以下这样一个东西如果在以前使用winform实现会是什么样子呢?应该会写不少的方法、属性用于界面之间及界面与数据库之间的联动。今天这个练习就先展示以下WPF的技术亮点之一:DataBinding。在制作过程中,还会为大家不断接受一些控件、布局等相关知识和技巧,部分相关的知识内容与此练习关系不大的,我将会用浅灰色字体带过,对于熟悉这部分内容的读者可以直接跳过。

 建立项目

  打开VS 2008 新建一个WPF应用程序

 

 

 界面布局

  首先会看到一个靓丽的小方框,将鼠标放在方框的边缘点击就会产生相应的分割线,现在我们要做的内容需要将窗体分成三行,可以先随便分割一下,以后在调整相互的大小。这时候会注意到下方的XML代码区域。每个RowDefinition作为一个行被定义出来

 

 

  这里先给大家接受以下高度、宽度的几种定义方式(写过HTML的人可以跳过去了):

 

  绝对尺寸(Absolut sizing):就是给一个实际的数字,像现在例子中那样

自动(Autosizing):值为Auto,实际作用就是取实际控件所需的最小值(Setting Height or Width to Auto, which gives child elements the space that they need and no more)

 

  Proportional sizing(也可以称之为star sizing因为有个*号表示):值为*N*,实际作用就是取尽可能大的值,当某一列或行被定义为*则是尽可能大,当出现多列或行被定义为*则是代表几者之间按比例方设置尺寸。

 

  下面我们来将上面的知识应用以下看看。

 

  将这三行的值设置为如下数值,结果会是如何:

 

            <RowDefinition Height="*" />

            <RowDefinition Height="Auto" />

            <RowDefinition Height="22" />

 

  第0行设为*用来放置ListView,这样会在实际运行过程中尽可能的充满整个区域

  第1行设为Auto尽量紧凑配列,少占用空间(如果里面没有东西的话,他会小到0,这是你会看不到它)

  第2行设为22固定值,只是用来放一个Button使用

 

  对于初学者往往会习惯直接使用控件拖拽的形式来将需要的内容添加到窗体上,会使控件在一定的坐标上固定位置,这是一种不推荐的做法,在此给大家讲解以下WPF中的布局观(Layout Philosophy):

 

  在WPF窗体中,一个窗体只能持有一个控件,当多个控件想要在窗体中展现时,就需要首先设置一个容器控件(Container),然后将其他控件放到这个控件里面,形成树状结构,因此,布局观第一条就是,控件的布局应该有容器来决定,而不是通过自身使用margin之类的东西来控制位置,因为这些属性原本应该是控制自己内部展现或与邻里之间关系的;第二条,控件应避免明确的定义具体的尺寸,因为显示器分辨率及windows窗体的大小都有可能随时改变,如果明确的定义尺寸,当窗体变动后就会出现大面积的空白或是缺失。但为了控件功能及效果的展示,应该限定一个可接受的最大及最小尺寸,通过MinWidth, MinHeight, MaxWidth, MaxHeight属性可以实现这一点。第三条,不要将界面元素位置设置成与屏幕坐标相关,现在显示器分辨率比较多样话(800×6001024×768,我的显示器是一台是1400×1050,还有一个是1024×1280竖式的),这样的做法还是比较有风险的。第四条,容器应将有效空间共享给其子控件,这也是为了不在窗体调整后,遗留出大块的空余。第五条,容器嵌套使用,因为不同的容器,表现效果不同,必要时应结合使用。

 

  接下来在工具箱(Tool Box)中双击ListView,一个小框会出现在界面上。

  接下来在工具箱(Tool Box)中双击WrapPanel,又一个大框会出现在界面上。

  再增加一个Button

  这是你会感觉到界面有点乱了,刚才我们在顶层Grid上面画线到底起什么作用了(到目前位置还没有),让我们来调整一下下面的XAML语句,最终结果如下:

 

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="*" />

            <RowDefinition Height="Auto" />

            <RowDefinition Height="22" />

        </Grid.RowDefinitions>

        <ListView Name="listView1" MinWidth="280" >

            <ListView.View>

                <GridView x:Name="gridView1">

                    <GridViewColumn Header="ContactID"></GridViewColumn>

                    <GridViewColumn Header="FirstName"></GridViewColumn>

                    <GridViewColumn Header="LastName"></GridViewColumn>

                    <GridViewColumn Header="EmailAddress"></GridViewColumn>

                </GridView>

            </ListView.View>

        </ListView>

        <WrapPanel Grid.Row="1" Orientation="Horizontal"></WrapPanel>

        <Button Grid.Row="2" HorizontalAlignment="Right" Click="button1_Click" Name="button1">Refresh</Button>

    </Grid>

 

  这里有几点又需要解释以下了:

 

1)      介绍以下容器控件Panel,现在界面中有两个容器型的控件一个是Grid跟元素,另一个是WrapPanel。它们都是容器型控件,不过表现上有所不同。Grid顾名思义“网格”,在之前我们已经定义了三行高度分辨是*, Auto, 22,其实还可以功过ColumnDefinition定义多个列,他的子控件被放在一个一个实现定义好的小格子里面,整齐配列。而WrapPanel则是将各个控件按照行或列的顺序摞列,当长度或高度不够是就会自动调整换例或行。还有一个常用控件这里稍后会用到StackPanel,将控件按照行或列来顺序排列不会回行。

2)      大家应该注意到了在WrapPanelButton上面的Grid.Row="n",这个就是Attached Properties(不知道怎么翻译了,可能叫‘附着属性’),用来设置WrapPanelButton应该在父容器的什么位置。这是WPF的特性之一,通俗的理解起来就是,别人有的属性,由于你跟他产生了关系所以你也有了这个属于他的属性。记得FantasiaX‘水之真谛’曾经给我通俗的解释过这个特性,这里照搬出来分享给大家:一个小学生,身高、体重是他的自身属性,而这个小学生由于是N年级的X班的学生,因此,这个小学生又带有了一个附加的属性,N年级X班。在这个例子里如果学校作为一个Grid容器,N年级X班可以看作一个小格子,小学生是其中的一个实例,那么,小学生因为安置在这个班级,因此获得了这个班级所拥有的这个属性,当学期末,老师说,这个学生已经升到N+1年级X班时,这个学生以后就跑到另一个小格子里去上课了。Attached PropertiesXAML用法就是在自己的属性设置地方直接使用容器的类型名称.容器属性名称(Grid.Row)设置对应的值。

3)      大家应该注意到类似与ListView.ViewGrid.RowDefinitions用法,这个叫做Complex Properties(应该叫‘复杂属性’吧),其实就是元素的某一个属性由于不能够简单的用名值对实现,因此需要单独标签话声明一下。

4)      再有就是x:Name="gridView1"这种用法,叫做Markup Extensions(‘标记扩展’吧),这个可是一个满不错的特性,由于后面代码中要使用到GridView对象,然而GridView对象有没有Name属性,如果后台代码想要调用他的话就不得不从父容器向下遍历来找到想要的对象,这样无疑增加了后台代码与前台界面之间的耦合度,试想如果那天突然有需求说要把这个对象从这里移到另一个容器上去,那么界面的变动伴随着的就是后台代码的一起变动,这与视图/逻辑分离显然背道而驰,有了Markup Extensions,我们想定位一个没有名字属性的控件,直接为扩展一个名称出来,这个可太方便了(当然,Markup Extensions不只是用来扩展名称的)。

 

  接下来开始为WrapPanel添加ContentControl,用来显示ListView中被选定的记录的明显信息,由于希望此部分内容能占用尽量少的控件,因此使用WrapPanel来布局,这样控件可以随着窗体的拖动不断调整大小一期尽量少的占用空间,然后每一项明细为一个TextBlockTextBox形成的一组名称及输入框对就像(XXXX: NNNN),然而,如果将每个TextBlockTextBox散乱的放在那里直接的后果就会是在当窗体大小重设过程中TextBlockTextBox无法对称在一起,可能出现不成对回行的现象,如下图:

 

 

  如果希望每个TextBlockTextBox成为一对出现的话,应该如何呢?自然是需要一个容器将他们组织起来,同时,希望他们在一条线上不回行。这就用到了,我们前面说到的一个容器StackPanel。组织后的代码如下:

 

        <WrapPanel Grid.Row="1" Orientation="Horizontal">

            <StackPanel Orientation="Horizontal" Margin="5,2,5,2">

                <TextBlock Name="textBlock_ContactID" Text="ContactID:" />

                <TextBox Name="textBox_ContactID" MinWidth="100" />

            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="5,2,5,2">

                <TextBlock Name="textBlock_FirstName" Text="FirstName:" />

                <TextBox Name="textBox_FirstName" MinWidth="100" />

            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="5,2,5,2">

                <TextBlock Name="textBlock_LastName" Text="LastName:" />

                <TextBox Name="textBox_LastName" MinWidth="100" />

            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="5,2,5,2">

                <TextBlock Name="textBlock_EmailAddress" Text="EmailAddress:" />

                <TextBox Name="textBox_EmailAddress" MinWidth="100" />

            </StackPanel>

        </WrapPanel>

 

  这样情况下在试试就可以看到效果了,无论窗体边缘怎么托拉TextBlockTextBox总是成对的,同时,随着窗体的拖动,控件会不断的改变位置一适应最小原则(如果想要让他固定下来的话,那就需要将WrapPanel换成其他的Panel就可以了)。如下图所示:

 

 

 

  补充知识:WPF中所有单一内容控件的统称,对应的父类为System.Windows.Controls.ContentControl;与ContentControl平行的还有一类控件叫做 ItemsControlWPF中所有控件集合对象的统称,例如:ListView, ListBox, TreeView等,这一点一定要区分清楚,可以认为这一类控件是为了展现有效的展现及组织一组控件而设计的,对应的父类为System.Windows.Controls.ItemsControl,这些控件和Panel容器类控件是不同的概念,要了解他们之间的关系可以参看下面这张类继承关系图:

 

 

 

 后台逻辑

  在后台用ADO.NET写一个获取DataTable的方法及一个共有属性拥有获取DataTable.DefaultView,同时,实现Refresh按钮的方法。此部分不是练习重点这里不做介绍了,代码如下:

 

        public Window2()

        {

            InitializeComponent();

            getData();

        }

 

        SqlDataAdapter sda;

        DataTable dt;

 

        void getData()

        {

            //init sqlconnection

            SqlConnectionStringBuilder connbuilder = new SqlConnectionStringBuilder();

            connbuilder.DataSource = "(local)";

            connbuilder.IntegratedSecurity = true;

            connbuilder.InitialCatalog = "AdventureWorks";

 

            //start to make sql query

            SqlConnection conn = new SqlConnection(connbuilder.ConnectionString);

            sda = new SqlDataAdapter("select ContactID,FirstName,LastName,EmailAddress from person.contact where ContactID<=100;", conn);

            SqlCommandBuilder commbuilder = new SqlCommandBuilder(sda);

            sda.UpdateCommand = commbuilder.GetUpdateCommand();

            dt = new DataTable();

            sda.AcceptChangesDuringUpdate = true;

            sda.Fill(dt);

        }

 

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            getData();

        }

 

前台界面与后台数据的Binding

  Binding这个此翻译起来有争议,为了避免误导大家,因此我还是不写成中文了。

  好了!接下来终于到最精彩的地方了。各位读者们一会会为后面所发生的事情而感到震惊的,一切就是这样的完成了……

 

1)      ListView控件指明数据源,通过ItemSource属性设置,这一部分需要通过代码来实现(目前位置还不知道如何通过XAML语言来实现,哪位大哥可以指点一下),在void getData()方法的结尾处增加一行代码“listView1.ItemsSource = dt.DefaultView;

2)      GridViewColumn指明当前列对应于数据源的哪一项,可以通过DisplayMemberBinding属性来实现,其中的值便是上一步中指明的数据源dt.DefaultView的每一个数据项的名称

 

                    <GridViewColumn Header="ContactID" DisplayMemberBinding="{Binding Path=ContactID}"></GridViewColumn>

                    <GridViewColumn Header="FirstName" DisplayMemberBinding="{Binding Path=FirstName}"></GridViewColumn>

                    <GridViewColumn Header="LastName" DisplayMemberBinding="{Binding Path=LastName}"></GridViewColumn>

                    <GridViewColumn Header="EmailAddress" DisplayMemberBinding="{Binding Path=EmailAddress}"></GridViewColumn>

 

  此时试着F5 Debug运行以下,应该已经可以看到这样的画面了

 

 

 

  但是,当我们点击ListView中的记录时,里面的书籍并没有映射到下面的文本框中。接下来我们就来实现这部分功能。

 

1)      为了简化代码,在WrapPanel元素中指明一个公共的上下文,可以通过增加属性DataContext="{Binding ElementName=listView1,Path=SelectedItem}

2)      分别为四个输入框指明相应的数据源,可以通过TextBox元素中的Text属性实现,实现后代码如下:

 

<TextBox Name="textBox_ContactID" MinWidth="100" Text="{Binding ContactID}" />

……

<TextBox Name="textBox_FirstName" MinWidth="100" Text="{Binding FirstName,UpdateSourceTrigger=PropertyChanged }" />

……

<TextBox Name="textBox_LastName" MinWidth="100" Text="{Binding LastName}" />

……

<TextBox Name="textBox_EmailAddress" MinWidth="100" Text="{Binding EmailAddress}" />

 

  上面这一段代码相信有很多刚刚接触WPF的人一定会感到陌生,这里解释一下:

 

  上面这段XAML语句里面,大家看着最不顺眼的应该就是{Binding }这种语句了吧,这个就是WPF重要特性之一“Binding”,他是用来实现界面元素的属性与后台数据之间的Binding,通过这种形式将前台界面与后台数据联系在一起达到界面与数据耦合的目的。与直接覆值相比较,存在这如下几点特性上的差异:

 

1)      Binding可以通过XAML语句实现界面与数据(可以是界面元素或后台对象)的耦合(也可以通过代码来实现),这一实现主要是依靠WPF的另一个特性Dependency Property来实现的。示意图如下:

 

 

 

2)      Binding可以实现制定方向的绑定,方向有三种,OneWay, TwoWay, OneWaytoSource,其形象的表示如下图所示:

 

 

3)      可配置触发器,这一特性用来解释,界面与数据的Binding是什么时候发生的,可以通过UpdateSourceTrigger属性实现,存在如下几种值

 

LostFocus :当控件失去焦点时触发,前面例子里TextBox.Text默认就是这种形式的

PropertyChanged:当属性改变时触发

Explicit:这个就可以看作是需要显示调用了,需要主动取调用相应的UpdateSource方法才可以触发

 

  用法可以像这样“{Binding FirstName,UpdateSourceTrigger=PropertyChanged }

 

4)      不抛出异常,这一点对于开发及测试人员来说可能并不怎么好,当一个数据Binding失败是,程序运行是不受影响的,只是相应的属性值为空了,对于开发人员来讲只能通过VS Debug时的输出窗口看到Binding失败的调试信息。而对于测试人员来说那就只能是靠肉眼了。

 

  具体内容可以参考MSDN中的” Data Binding Overview”这篇文章,这里只是为大家实现一个引路的工作

 

  现在再试试应该已经可以实现ListViewTextBox的联动了,不过此时,对数据的改动还无法更新到数据库中。需要继续修改。

 

1)      button1_Click事件中增加一条语句用于接受书籍更新sda.Update(dt);

2)      XAML设计区,最顶层Window元素中增加属性,Closed="Window_Closed",然后在后台实现相应的方法代码如下

 

  两个方法具体代码如下,由于与WPF关系不大,因此不做讲解了:

 

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            sda.Update(dt);

            getData();

        }

        private void Window_Closed(object sender, EventArgs e)

        {

            sda.Update(dt);

        }

 

  这样就完整的实现了这个练习。

 

  原文:http://blog.csdn.net/mmmaaaggg/archive/2008/06/09/2526596.aspx

 

/

 

 修正上述方法:

 

  在将listView1中选定对象(SelectedItem)的属性binding到下面的TextBox上时,这个方法写的实在有失水准。

 

  在listView1SelectionChanged="listView1_SelectionChanged"事件中进行数据绑定,这个操作实际上并没有体现出binding的优势了,因为每次事件发生时,都重新binding,虽然并没有增加重复binding,但是一方面,增加了系统开销,同时,也是一种多余的操作,使得代码变动混乱。

 

  作为项目经验,个人认为一个项目中,读别人(也可能是自己)代码的时间大于自己写代码的时间,为了让项目组内的成员更高效的读懂代码,写代码一定要尽可能的精简,但精简并不是一味的追求将所有语句写到一行或是尽可能缩短代码量,而是在保证逻辑清晰连贯的前提下,尽量减少代码量及减少程序到处乱跳,调试的时候一会跳东一会跳西的,很容易就乱了,这里给大家推荐尽可能多的使用C# 3.0新特型中的语法Lambda Expressions,尤其是写事件处理方法是,原来即使是一两行代码也要单写找一块地方写成函数,现在只需要在使用处写一个表达式就可以了,读代码及调试的时候都会省很多时间和精力。由于这不是本编的主要内容,这里就不做具体介绍了,只是提出了供有兴趣的同僚们一起学习吧。

 

  在修正这个bug之前,先给大家补充一个概念:同一个对象(特指System.Windows.DependencyObject的子类)的同一种属性(特指DependencyProperty)只能拥有一个binding。这一点可以通过设置binding对象的方法名得知:

 

public static BindingExpressionBase SetBinding(

         DependencyObject target,

         DependencyProperty dp,

         BindingBase binding

)

 

  方法名是SetBinding而不是AddBinding。如果想要验证一下,也可以在listView1_SelectionChanged事件方法中增加断点,可以监视到,当多次在同一个对象上设置Binding时,其实并不会增加多余的binding,而是将原来的binding替换掉了,这里以textBox_ContactID为例如下图:

 

Debug模式下即时窗口调试信息

 

  另一个相对的概念:同一个Binding可以同时与多个对象的多个属性(或同一对象的多个属性)形成Binding这一点就不做实践验证了,在MSDN上《Data Binding Overview》中有一段“Binding and BindingExpression”就是讲这个道理的。

 

  也许很多读者已经知道或潜意识里有了这种概念,这里只是做一些强调。

 

 程序修正,改进Binding

  下面的内容在修正错误的同时,还将对原有的Binding做一些小小的改进。

 

  在上一篇Blog中,我曾经提到过这样一个问题,在将数据库中的DataTable对象的DefaultView通过XAML语言BindinglistView1对象的ItemsSource上时总是失败,这里顺便公布一下答案,那就是使用DataContext,不过在动手之前先来梳理一下Binding的一些基本概念及使用方法。

 

  Binding四个重要组成部分

  继续引用上一篇Blog中的图片

 

Binding figure demonstrates the following fundamental WPF data binding concepts:

 

  在此之前先来点基础知识,都是MSDN上的内容(不过是E文的),感觉不错所以写下来,供大家阅读:

 

l         典型的Binding具有四个重要组成部分:Binding目标对象binding target object)、目标对象属性(target property)、Binding数据源binding source)、Path(用于指明要从数据源中取得的值,就是我们通常写的属性名称)。例如:想要把一个员工(Employee)的姓名(EmpNameBinding显示到一个TextBoxText属性上。分析一下其中的4个组成部分:Binding目标对象TextBox(因为它要最终使用这些数据);目标对象属性是TextProperty(这里要注意,不是Text属性,而是与Text属性相对应的Dependency Property,通常命名是属性名+Property);数据源是员工(Employee);PathEmpName。我的经验是:刚开始使用Binding的时候由于还不熟悉,建议在做之前先在心理把这4个部分对号入座一下,等日后就可以熟悉成自然了。

l         目标对象属性一定要是Dependency Property。由于只有DependencyObject类及其子类能够定义Dependency Property,因此,Binding目标对象就必须是DependencyObject类及其子类了。另一方面反过来说,UIElementDependencyObject的子类)的大多数属性都有与其对应的Dependency Property,因此,大多数的UIElement属性我们都可以利用其Dependency Property作为目标对象属性从而与数据源建立Binding

l        再有一点,建立Binding的数据源不一定要是一个CLR对象,也可以是XML数据、ADO.NET对象及DependencyObject

 

  关于为什么binding的对象一定要是DependencyObject,为什么binding对象的属性一定要是dependency property,这个我就不知道了。

 

  揭开XAMLBinding语句语法的神秘面纱

 

  在我第一次看到在XAML中设置Binding的语法时,简直是满头雾水。这东西到底怎么回事,简直就没有章法可循,一会儿ElementName,一会儿Source,还有时什么都没有,就一个{Binding },再有就是ModeUpdateSourceTrigger等,简直没有头绪。

 

  不过只要打开MSDN,看一下System.Windows.Data.Binding对象,似乎开始有点感觉了,查看Binding对象的属性栏(Properties),你会发现,原来我们在XAML中设置Binding时使用的东西和Binding对象的属性好像都是一一对应的。确实如此,在XAML中设置Binding就是在这只System.Windows.Data.Binding对象,这里先提醒一下大家,WPF可不是就这么一个Binding噢,实时上,从BindingBase继承过来的有三个兄弟,Binding只是最常用而已。

 

      System.Windows.Data..::.Binding       System.Windows.Data..::.MultiBinding       System.Windows.Data..::.PriorityBinding

 

  找到了binding的老家,相信大家对{Binding ElementName=ElemName, Path=PropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, …}这样的语法应该清楚了吧,那么{Binding } {Binding XXXX}这样的语法呢?其实可以就是等于new Binding()new Binding(XXXX),其实可以考虑成是分别使用了Binding的两种构造方法,而XXXX的作用自然也就清晰了,其实和{Binding Path=XXXX}是等价的,都是设置Path属性的。至于这里的Path属性在我们平常使用时其实是用来设置属性名的地方,为什么不直接叫做是PropertyName之类的名字呢?这个又要留着以后思考了。

 

  如何才能使Binding的数据源或目标更新后自动同步

 

  当一个数据源与一个UIElement形成Binding后,数据源的改变,如果传递给UIElement的指定属性?同样,当UIElement中指定属性值改变了,如何才能够通知到数据源?要讨论这个问题,需要按数据源来分类进行讨论。这个分类并不是互斥的之前存在一些交集,因此,同一种Binding场景可能适用于多种情况。下面的内容是来自MSDN中《Binding Sources Overview》中的内容,由于怕翻译的不好而误导大家,所以分类的名称还是使用英文吧。

  Using a CLR Class as the Binding Source Object

  在典型的Binding情景中,一个CLR 对象作为数据源,将其中一个属性值Binding到某个UIElement的属性上(以上面显示员工姓名的情景为例,用XAML实现<TextBox Text=”{Binding Source=Employee, Path=EmpName}” />)。当数据源属性值变化了,相应让UIElement中对应属性变化,需要实现一个中通知机制,最简单的方法是通过实现INotifyPropertyChanged接口完成此操作。具体实现,可以参考MSDN上的文章《How to: Implement Property Change Notification》。

 

  其实就是实现了”public event PropertyChangedEventHandler PropertyChanged;”事件,通常的做法是在上面加封装一个”protected void OnPropertyChanged(string name)”函数。在属性的set方法结尾处,调用此函数实现通知的作用。

 

  如果不实行此接口,则必须自己实现一套通知系统。为你的类中的每一个想要实现通知机制的属性创建一个PropertyChanged模板,即为你的每一个属性创建一个名为PropertyNameChanged事件,其中PropertyName是与这个事件对应的属性名称。然后在属性发生变化是触发这个事件。(听起来就麻烦,我也懒得去实践验证了)

 

  如果以上两种方法都无法实现的话,那么就只好在你认为需要的时候显示的调用UpdateTarget方法来手动更新了。(这种方法没用过,感觉已经失去了Binding的意义了)

  Entire Objects Used as a Binding Source

  可以理解为将整个对象作为数据源,与上一个题目的区别很小,基本上可以认为是原来Binding实现的是数据源属性与UIElement属性之间的Binding,而这次是直接将一个Object对象与一个UIElement的属性进行Binding了。这种情况可以使用BindingSource属性指明数据源(假设Employee对象与一个ListBox实现banding,用XAML实现<ListBox ItemsSource=”{Binding Employee}” />),或者还可以将DataContext属性设置为EmployeeDataContext属性只有UIElement的一个子类System.Windows.FrameworkElement及其子类才会有),这样,在设置ItemsSource时就可以直接使用”{Binding }”这样的语法了。当然,DataContext属性设置成Employee还是需要使用DataContext=”{Binding Employee}”语句的。

 

  有人在这里会嘲笑这一举动实在是啰嗦,事实上,DataContext属性是非常有用的,在上一篇Blog中,我之所以Binding对象失败而最终不得不使用C#语句进行Binding就是因为没有设置DataContext。因为,当我们使用{Binding Source= Employee, Path=”EmpName”}设置属性后,WPF查找Source对象是根据Element Tree逐级向上自己的Parent Element查找的,查找每一Element上的DataContext所指定的内容是否包含这一Source内容,并以第一个匹配到的结果作为最终对象。也就是说,DataContext是按照Element Tree向下继承的,并且决定这WPF在运行时能否找到我们所制定的Source对象。即使我们使用{Binding Path=”EmpName”}的语句也是如此。比较常见的做法是,当我们有大量的Element需要与一个数据源中的众多属性实现Binding时,我们可以直接在一个公共Parent ElementDataContext上设置这一对象(甚至可以是整个windowpage上),这将会极大的加少我们重复的代码量,同时也会是程序更加可读。

  Collection Objects Used as a Binding Source

  当我们需要使用的数据源是一个集合时,如果想要实现改变通知的机制,同样需要实现一个接口INotifyCollectionChanged用来在集合发生改变时发起,用法单个对象是实现INotifyPropertyChanged接口类似。

 

  开始修改代码

 

  首先是将上以前做的不是很好的那一段代码改成XAML语言实现。将TextBox标签中的Text属性与上面listView1中被选定的记录listView1.SelectedItem)中对应属性(例如:ContactID)做Binding。实现后如图所示:

 

<TextBox Text="{Binding ElementName=listView1, Path=SelectedItem.ContactID}" MinWidth="100" />

 

  看以来代码有点长,而且,下面的几个TextBox也都存在大量的重复内容,这里就可以考虑使用上面所说的DataContext了。

 

  我们可以将listView1中被选定的记录listView1.SelectedItem)作为DataContext放在公共Parent Element上,做法如下:

 

<WrapPanel Grid.Row="1" Orientation="Horizontal" DataContext="{Binding ElementName=listView1, Path=SelectedItem}">

 

  然后剩下的代码就可以简化很多了:

 

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="ContactID:" TextAlignment="Center" />

    <TextBox Text="{Binding ContactID}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="FirstName:" TextAlignment="Center" />

    <TextBox Text="{Binding FirstName}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="LastName:" TextAlignment="Center" />

    <TextBox Text="{Binding EmailAddress}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="EmailAddress:" TextAlignment="Center" />

    <TextBox Text="{Binding LastName}" MinWidth="100" />

</StackPanel>

 

  原来的SelectionChanged事件处理方法现在已经没有用了,将多余的代码去掉:<ListView Name="listView1" SelectionChanged="listView1_SelectionChanged" MinWidth="280" >,后台代码中的对应方法也可以删掉了private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e)

 

  F5 Debug运行一下,结果和原来是完全一样的。

 

  开始改进Binding

 

  上一篇Blog中,出于无奈选择使用C#实现Binding的部分代码,现在使用XAML语言实现,有几种方案可以选择,通过如下几步实现:

 

1)      将原先建立给listView1建立Binding那一大串代码删掉,替换成如下代码:

 

listView1.DataContext = dt.DefaultView;

 

  执行结果如下图所示:

  修改后代码截屏

 

2)      将其与listView1ItemsSource属性建立Binding,设置每一列的headerDisplayMemberBinding值,这里由于DataContext的原因,ItemsSource可以写成"{Binding }"的形式而其Children Element也都可以简写成"{Binding ContactID}"这样的形式。

 

<ListView Name="listView1" MinWidth="280" ItemsSource="{Binding }">

    <ListView.View>

        <GridView x:Name="gridView1">

            <GridView.Columns>

                <GridViewColumn DisplayMemberBinding="{Binding ContactID}" Header="ContactID"></GridViewColumn>

                <GridViewColumn DisplayMemberBinding="{Binding FirstName}" Header="FirstName"></GridViewColumn>

                <GridViewColumn DisplayMemberBinding="{Binding LastName}" Header="LastName"></GridViewColumn>

            </GridView.Columns>

        </GridView>

    </ListView.View>

</ListView>

 

  改造完后,运行结果会发现,效果和原来是一样的

 

  原文:http://blog.csdn.net/mmmaaaggg/archive/2008/06/29/2595811.aspx

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页