WindowsPhone基础琐碎总结-----数据绑定(一)
一、什么是数据绑定
官方这样解释:数据绑定是在应用程序 ui 与业务逻辑之间建立连接的过程。简单的理解就是通过数据绑定的方式实现了后台数据和前台ui元素的关联,可以比喻成ui元素和数据源的桥梁,更详细点的说数据绑定提供了一种数据呈现与交互的简捷方式,使得数据与ui分离,并能使得数据源和ui元素之间的自动更新、同步。
二、数据绑定的几个名词
在正式介绍数据绑定之前简单说几个关于数据绑定的常用名词,我想大家只要看一遍都会理解什么意思,不需要多说。
(1)绑定源:即数据的来源,绑定源可以是任意的clr对象。不过实际需要的时候则是该对象的某一特定属性。
(2)绑定目标:即从绑定源获得的数据绑定到的ui元素。可以是frameworkelement类型的任意对象,相对于绑定源,实际上绑定的是该控件的一个特定属性。
(3)、绑定模式:这个也很好理解,a、onetime:仅仅一次绑定,在绑定创建时使用源数据更新目标,适用于只显示数据而不进行数据的更新。简单理解为:绑定源仅仅把数据给绑定目标一次后,两者变得毫不相关。b、oneway:单向绑定,在绑定创建时或者源数据发生变化时更新到目标,适用于显示变化的数据。简单理解为数据源能把数据更新到绑定目标,而绑定目标的变化却不能影响到数据源。 c、twoway:双向绑定,在任何时候都可以同时更新源数据和目标。前两种绑定方式理解了这个相对理解就容易,通俗的讲就是绑定源和绑定目标是互通的,无论谁的变化都能够影响到对方,比如绑定源数据有变化就能马上把绑定目标的数据更新,反之一样。
(4)绑定对象:当绑定目标需要绑定数据源时该借助什么呢?绑定对象来完成,可以比喻成中介,起了个名字:binding。这个对象有三个重要属性(它们当然与绑定源,绑定目标等有关系,毕竟它是两者的中介):a、source:字面意思就能知道指的是绑定源对象 b、relativesource:指定绑定源相对于绑定目标的位置来标识绑定源 c、elementname,绑定源也是ui对象时候,设置其绑定源的的名称即可。说了这三种属性,其主要的目的就是找到绑定源,因为binding一般会写在绑定目标中,这样只要找到绑定源就好了,这样就能把两者联系起来。这三个属性的功能就是找到绑定源。所以这三种方式指定任意一种即可,只要让中介binding找到绑定源就好了。不过事实上我们在写程序的过程中很少这么用的,微软提供了一个更加通用的方法就是:通过设置ui元素的datacontext属性来制定绑定源,这样以上三个属性都无需指定,下文我会在做些介绍。
(5)path(路径):binding对象的一个属性,来指示绑定源对象中用以提供数据的属性。
三、通过实例理解数据绑定
下面我会通过在页面上绑定一个学生信息,来逐步探究数据绑定。
1、基本数据绑定
(1)我有这样一个需求想把一个学生信息绑定到ui界面上,我们该如何实现?首先考虑ui界面上的元素,这个不用多说,直接上图如下:
(2)简单说明下,布局很简单,canvas里面包含着一个image控件(显示学生照片),9个textblock控件用来显示学生相关信息,还有两个button按钮,当我们点击小红和小明时分别显示他们的信息到ui控件。主要xaml代码如下:
<phone:phoneapplicationpage
x:class="数据绑定实例1.mainpage"
xmlns=""
xmlns:x=""
xmlns:phone="clr-namespace:microsoft.phone.controls;assembly=microsoft.phone"
xmlns:shell="clr-namespace:microsoft.phone.shell;assembly=microsoft.phone"
xmlns:d=""
xmlns:mc=""
mc:ignorable="d" d:designwidth="480" d:designheight="768"
fontfamily="{staticresource phonefontfamilynormal}"
fontsize="{staticresource phonefontsizenormal}"
foreground="{staticresource phoneforegroundbrush}"
supportedorientations="portrait" orientation="portrait"
shell:systemtray.isvisible="true">
<!--layoutroot 是包含所有页面内容的根网格-->
<grid x:name="layoutroot" background="transparent">
<grid.rowdefinitions>
<rowdefinition height="auto"/>
<rowdefinition height="*"/>
</grid.rowdefinitions>
<!--titlepanel 包含应用程序的名称和页标题-->
<stackpanel x:name="titlepanel" grid.row="0" margin="12,17,0,28">
<textblock x:name="applicationtitle" text="数据绑定实例" style="{staticresource phonetextnormalstyle}"/>
<textblock x:name="pagetitle" text="学生信息" margin="9,-7,0,0" style="{staticresource phonetexttitle1style}"/>
</stackpanel>
<!--contentpanel - 在此处放置其他内容-->
<grid x:name="contentpanel" grid.row="1" margin="12,0,12,0">
<button content="小红" height="72" horizontalalignment="left" margin="29,337,0,0" name="buttonxiaohong" verticalalignment="top" width="160" click="buttonxiaohong_click" />
<button content="小明" height="72" horizontalalignment="left" margin="232,337,0,0" name="buttonxiaoming" verticalalignment="top" width="160" click="buttonxiaoming_click" />
</grid>
<canvas height="253" horizontalalignment="left" margin="21,54,0,0" name="canvas1" verticalalignment="top" width="441" grid.row="1">
<image height="150" name="image1" stretch="fill" width="164" canvas.left="20" canvas.top="50" />
<textblock height="30" name="textblockname" text="" canvas.left="269" canvas.top="50" />
<textblock height="30" name="textblocksex" text="" canvas.left="269" canvas.top="86" />
<textblock height="30" name="textblockage" text="" canvas.left="269" canvas.top="121" />
<textblock height="30" name="textblockbirthday" text="" canvas.left="309" canvas.top="157" />
<textblock height="30" name="textblockblog" text="博客地址:" canvas.left="20" canvas.top="206" />
<textblock canvas.left="203" canvas.top="50" height="30" name="textblock1" text="姓名:" />
<textblock canvas.left="203" canvas.top="86" height="30" name="textblock2" text="性别:" />
<textblock canvas.left="203" canvas.top="121" height="30" name="textblock3" text="年龄:" />
<textblock canvas.left="203" canvas.top="157" height="30" name="textblock4" text="出生日期:" />
</canvas>
</grid>
</phone:phoneapplicationpage>
(3)ui设计好了,在准备下资源,两个人需要两张图片了(mm.jpg和gg.jpg),网上随便找的,加入项目中去,下一步当然是把页面抽象成类了,下面我们将新建一个学生类,作为基本的数据源。代码如下:
namespace 数据绑定实例1
{
public class student
{
public string name { set; get; }
public string sex { set; get; }
public int age { set; get; }
public string birthday { set; get; }
public bitmapimage picture { set; get; }
public string blog { set; get; }
}
}
(4)现在我们开始构造小红和小明这两个人,并给出他们的具体信息,代码如下:
student xiaohong = new student()
{
name = "小红",
sex = "女",
age = 20,
birthday = "1988/8/1",
picture =new bitmapimage(new uri("mm.jpg",urikind.relativeorabsolute)),
blog = ""
};
student xiaoming = new student()
{
name = "小明",
sex = "男",
age = 22,
birthday = "1989/6/30",
picture =new bitmapimage(new uri("gg.jpg",urikind.relativeorabsolute)),
blog = ""
};
(5)下面剩余的关键问题就是把美女和帅哥的信息绑定到ui控件了。绑定代码如下:
首先引入命名空间:using system.windows.data;因为binding对象在这个命名空间下。
在buttonxiaohong_click中加入如下代码:
private void buttonxiaohong_click(object sender, routedeventargs e)
{
//绑定名字
binding bdname = new binding();
bdname.source = xiaohong;
bdname.path = new propertypath("name");
this.textblockname.setbinding(textblock.textproperty, bdname);
//绑定性别
binding bdsex = new binding();
bdsex.source = xiaohong;
bdsex.path = new propertypath("sex");
this.textblocksex.setbinding(textblock.textproperty, bdsex);
//绑定年龄
binding bdage = new binding();
bdage.source = xiaohong;
bdage.path = new propertypath("age");
this.textblockage.setbinding(textblock.textproperty, bdage);
//绑定生日
binding bdbirthday = new binding();
bdbirthday.source = xiaohong;
bdbirthday.path = new propertypath("birthday");
this.textblockbirthday.setbinding(textblock.textproperty, bdbirthday);
// 绑定照片
binding bdpicture = new binding();
bdpicture.source = xiaohong;
bdpicture.path = new propertypath("picture");
this.image1.setbinding(image.sourceproperty, bdpicture);
// 绑定博客地址
binding bdblog = new binding();
bdblog.source = xiaohong;
bdblog.path = new propertypath("blog");
this.textblockblog.setbinding(textblock.textproperty, bdblog);
}
(6)现在我们可以运行下看看,单击小红,效果图如下:
(7)很高兴我们实现了我们想要的结果,小明的绑定和小红的道理一样,我不在赘述,通过上面我们可以总结出绑定的一般步骤:
第一步:实例化一个绑定对象(找到中介);第二步:设置绑定对象的source 属性,即设定绑定源(数据来源的地方);第三步:设置绑定路径,即绑定到绑定源的那个属性;第四步调用ui控件的绑定方法实现绑定,即实现把数据绑定到绑定目标的那个属性。以上是基本的数据绑定流程,也是数据绑定的基础。也许你还有些疑问:比如没有设置mode、convert属性,没有设置说明取了默认值,mode默认为oneway,convert没有设置说明没有需要数据转化,下文我也将会介绍。
2、通过datacontext(数据上下文)属性指定数据源
通过上面的例子知道我们通过binding对象实现了疏浚到ui的绑定,但是我们写代码时很明显意识到了代码有很多的冗余,比如说 bdpicture.source = xiaohong;设置绑定源是重复的。鉴于此,我们还可以用另一种方式设置数据源的方式,即通过ui元素的数据上下文datacontext属性来指定数据源。开始接触datacontext可能不好理解,我简单说下我的理解:一般来说每个ui控件都是有父类控件的,比如我们上面所用到的image,textblock,button控件父控件是canvas,canvas的父控件是grid;每个控件都会有datacontext属性,因为每种控件都继承自framworkelement,一旦我们指定了这个属性,其子元素控件在不指定source和datacontext情况下,都默认使用该属性指定对象作为绑定源。那上面例子说,我们要是设置canvas控件的datacontext为小红:this.canvas1.datacontext = xiaohong;那么canvas子控件在没有指定datacontext 属性情况下,都默认使用xiaohong作为数据源。再深层次理解下,其实只要设置了父类控件的datacontext 属性后,程序会自动通过遍历树(内部数据结构为树)而找到要绑定数据源的合适属性。甚至来说可以设置整个页面的datacontext为xiaohong,当然一般我们不会这么做。实际上我们一般的做法是先用datacontext指定高层的元素的数据上下文,然后对特殊的子元素再另行指定绑定源。
通过以上解释,我们很容易理解可以把上面例子的代码作如下改进:去掉所有的设置绑定源的语句,我们只用指定this.canvas1.datacontext = xiaohong;即可实现同样的功能。
3、通过xaml代码实现绑定
以上例子都是通过c#代码后台绑定,当然我们也可以通过xaml代码更容易的实现绑定。绑定语法说下:<ui标记 绑定属性="{binding path=*,mode=*,convert+*,source=*;}"/>,下面我们还以上面例子为例子通过xaml来绑定小红和小明。下面我仅仅贴出改动过得代码:
<canvas height="253" horizontalalignment="left" margin="21,54,0,0" name="canvas1" verticalalignment="top" width="441" grid.row="1">
<image height="150" name="image1" stretch="fill" width="164" canvas.left="20" canvas.top="50" source="{binding path=picture}" />
<textblock height="30" name="textblockname" text="{binding path=name}" canvas.left="269" canvas.top="50" />
<textblock height="30" name="textblocksex" text="{binding path=sex}" canvas.left="269" canvas.top="86" />
<textblock height="30" name="textblockage" text="{binding path=age}" canvas.left="269" canvas.top="121" />
<textblock height="30" name="textblockbirthday" text="{binding path=birthday}" canvas.left="309" canvas.top="157" />
<textblock height="30" name="textblockblog" text="{binding path=blog}" canvas.left="20" canvas.top="206" />
<textblock canvas.left="203" canvas.top="50" height="30" name="textblock1" text="姓名:" />
<textblock canvas.left="203" canvas.top="86" height="30" name="textblock2" text="性别:" />
<textblock canvas.left="203" canvas.top="121" height="30" name="textblock3" text="年龄:" />
<textblock canvas.left="203" canvas.top="157" height="30" name="textblock4" text="出生日期:" />
</canvas>
当然数据源的指定我们仍然通过canvas的datacontext属性在c#代码中指定:
private void buttonxiaohong_click(object sender, routedeventargs e)
{
this.canvas1.datacontext = xiaohong;
}
private void buttonxiaoming_click(object sender, routedeventargs e)
{
this.canvas1.datacontext = xiaoming;
}
效果图如下:
4、数据转换器(converter)的使用。
如果认真看完代码的同学肯定会注意到picture这个属性,我用的是bitmapimage类型,实例化学生对象,对picture属性这样赋值:picture =new bitmapimage(new uri("gg.jpg",urikind.relativeorabsolute)),这样虽成功实现了,但总感觉有点不妥。我们一般会把图片的地址存为字符串类型,但是ui控件source属性却需要的是bitmapimage类型。该如何解决这个矛盾呢?数据转换器来帮您解决。
根据上面的叙述和需求我们不难总结出转换器的作用:当绑定源提供的数据格式或者类型与绑定目标所需不一致时就需要通过一个转换器来进行转换。converter的类型其实是一个实现了ivalueconverter接口的类。ivalueconverter接口中定义了两个方法convert和convertback用来对数据的双向转换。从字面意思很容易理解,我不在多说。本实例中我们需要的是想把string类型的地址转换为bitmapimage类型,不需要双向的转换,所以我仅仅实现ivalueconverter接口中的convert方法。
首先新建一个转换类:string类型到bitmapimage类型,并实现ivalueconverter接口,需要注意的是:引入using system.windows.data命名空间,ivalueconverter在此命名空间。
第一步:
using system;
using system.windows.data;
using system.windows.media.imaging;
namespace 数据绑定实例1
{
public class stringtobitmapimage:ivalueconverter
{
#region ivalueconverter 成员
public object convert(object value, type targettype, object parameter, system.globalization.cultureinfo culture)
{
//value为object类型,需强制转换(string)value
bitmapimage picture = new bitmapimage(new uri((string)value, urikind.relativeorabsolute));
return picture;
}
public object convertback(object value, type targettype, object parameter, system.globalization.cultureinfo culture)
{
throw new notimplementedexception();
}
#endregion
}
}
代码我就不解释了,我想大家都会很容易的理解。
第二步:定义好了转换类,下面就是我们该如何用了.
a、我们需要在xaml页面引入此类的名字空间,并指定一个key。在<phone:phoneapplicationpage />中引入命名空间:
xmlns:my="clr-namespace:数据绑定实例1"
b、接着把此转换类定义为一个静态资源,我定义为canvas控件的静态资源,当然你也可以定义为grid,甚至整个页面,这是资源样式的知识我不再细说。
<canvas.resources>
<my:stringtobitmapimage x:key="stringtobitmapimagekey"></my:stringtobitmapimage>
</canvas.resources>
我们可以通过指定的key来访问转换类。
c、最后就是转换器了的使用了:
<image height="150" name="image1" stretch="fill" width="164" canvas.left="20" canvas.top="50" source="{binding path=picture,
converter={staticresource stringtobitmapimagekey}}" />
d、完成以上代码后我们回到c#代码中把picture属性统一的改为string类型,然后f5,结果和原来一样,我们成功实现了转换。改动的地方如下:
public class student
{
public string name { set; get; }
public string sex { set; get; }
public int age { set; get; }
public string birthday { set; get; }
public string picture { set; get; }
public string blog { set; get; }
}
student xiaohong = new student()
{
name = "小红",
sex = "女",
age = 20,
birthday = "1988/8/1",
picture ="mm.jpg",
blog = ""
};
student xiaoming = new student()
{
name = "小明",
sex = "男",
age = 22,
birthday = "1989/6/30",
picture ="gg.jpg",
blog = ""
};
this.canvas1.datacontext = xiaoming;
通过这个小例子我想我们能够知道了转换器的用法,需要说明的是转换器不仅可以实现这些简单的类型转换、格式转换,甚至可以转换一些更为复杂的逻辑转换,当然道理都是大同小异。
5、数据绑定模式
开篇已经做了相关介绍,我就不再做详细说明,仅仅简单说下,前面我们没有设置绑定模式,默认情况下都是oneway。onetime属于与一次性绑定很好理解,用到的也很少。twoway属于双向绑定,可以实现数据在数据源与绑定目标之间可以相互流动,也即源与目标任何一方发生改变都会立即通知到对方并引起对方数据的更新,可以说是实时同步的。下面通过实例主要说明下twoway绑定原理。
为了还是用上面的例子,我们需要再加入一个没有什么实际意义的textbox控件,仅仅为了做演示(上面用的控件都是没法改变其绑定值的)。在canvas再放置一个textbox控件,并绑定name属性,如下图:
xaml代码如下:
<textbox canvas.left="6" canvas.top="242" height="74" name="textboxname" text="{binding path=name}" width="429" />
运行下,顺利把小红的名字信息绑定到了textbox控件中,这也就是说实现了默认的oneway,可是我们该如何更改textbox的值同时更新到数据源呢?在正式开始写代码之前我们还需要了解,当选择oneway或者towway时候,为了绑定源的的变化能够实时的通知到绑定目标,源对象必须实现inotifypropertychanged接口(这个接口在using system.componentmodel命名空间下,记得加入)。换句话说如果使绑定目标的显示与绑定源的同步,必须满足两个条件:一是设定绑定模式为oneway或者twoway;而是绑定源实现inotifypropertychanged接口。下面我们通过实例具体演示:
第一步:源对象实现inotifypropertychanged接口
namespace 数据绑定实例1
{
public class student:inotifypropertychanged
{
private string name;
public string name
{
set
{
this.name = value;
notifypropertychanged("name");
}
get
{
return this.name;
}
}
public string sex { set; get; }
public int age { set; get; }
public string birthday { set; get; }
public string picture { set; get; }
public string blog { set; get; }
#region inotifypropertychanged 成员
public event propertychangedeventhandler propertychanged;
#endregion
public void notifypropertychanged(string propertyname)
{
if (propertychanged!=null)
{
propertychanged(this, new propertychangedeventargs(propertyname));
}
}
}
}
说明:上面代码我仅仅贴出了name属性的相关代码,其他属性与其类同,读者可自行加上。其实简单的理解就是每当绑定源的相关属性发生改变时候,绑定源通过以上的通知机制告诉绑定目标“我改变了,你如果是twoway或者是oneway,你也赶紧改变吧”。
第二步:设置绑定模式
<textbox canvas.left="6" canvas.top="242" height="74" name="textboxname" text="{binding path=name, mode=twoway}" width="429" />
好了,现在我们运行程序看看会有什么变化,程序启动后单击小红:
改变下textbox的内容,然再单击小红看看会有什么情况发生?在预料之中当我们修改完textbox的内容后,姓名被改变了,这也正是我们想要的结果。
细心的你这时可能再会单击下小红,甚至单击小明后再单击小红,发现“小红”这个名字再也回不来了,因为数据源发生了改变。也许你还会问照片左边的姓名我们没有设定绑定模式啊,怎么也改变了。是的我们没有设定,但默认oneway的。其实过程简单的是这样的:我改变了textbox的内容,因为它和数据源是twoway方式所以,它将会通知数据源我改变了你也改变吧,然后数据源就改变了,数据源改变了他当然要通知实现oneway的照片旁边的姓名,我被textbox改变了,你也跟着改变吧。其实也算是个连锁反应,好了不解释了,应该能理解的。
好了,可以结尾了,终于把基本的数据绑定通过实例的方式讲解完了,当然这在实际运用当中还是远远不够的,随后我将在windowsphone基础琐碎总结-----数据绑定(二)中介绍集合对象的数据绑定,敬请关注。
作者:gavindream