简介
第 1 章:“长角”应用模型
第 2 章:生成“长角”应用程序
第 3 章:控件和 XAML
第 4 章:存储
第 5 章:数据绑定
布伦特校长
Wise Owl Consulting
2004 年 2 月
目录
创建数据绑定
数据绑定类型
Transformers
提供属性更改通知
总结
传统意义上的数据绑定是指将某些基础数据与一个或多个用户界面元素相关联。 数据提供要显示的信息。 用户界面元素以适当的格式呈现信息。
“Longhorn”以多种方式扩展了传统的数据绑定理念。 可以将用户界面元素的属性绑定到任何公共语言运行时的属性 (CLR) 对象,或绑定到 XML 节点的属性。
数据绑定可以是单向) 或双向 (。 例如,传统数据绑定将数据源中的信息流向其绑定的用户界面元素。 或者,用户界面元素中的信息可以流回数据源。 当然,双向数据绑定支持每个方向的信息流,并使用户能够通过用户界面元素进行输入来更新数据源中的数据。
数据绑定也可以是静态 (一次,只能) 或动态绑定一次。 使用静态数据绑定时,信息传输会在最初创建数据绑定时发生。 对数据中值的后续更改不会影响用户界面元素中的值。 动态数据绑定允许对数据源中数据的更改传播到用户界面元素,反之亦然。
“Longhorn”数据绑定还支持在数据流入和流出数据源和用户界面元素时转换数据。 此转换使应用程序作者能够向数据添加 UI 语义。
一些典型的转换可能如下所示:
- 用红色显示负数,用黑色显示正数
- 基于处于联机或脱机状态的联系人显示图像
- 通过将矩形的高度绑定到股票价格来创建动态条形图
- 通过将图像的坐标绑定到 CLR 对象的属性来对图像的位置进行动画处理
“Longhorn”数据绑定在本质上也是异步的。 当新的数据源绑定到 该元素时,用户界面元素接收事件。 此外,当数据源收集数据时,它会触发事件以指示其内容已更改。
用户可以将数据绑定到任何 CLR 对象或 XMLnode,因此可以轻松地将数据绑定到“Longhorn”中的各种数据模型- CLR 对象、XML、ADO.NET 数据集、Web 服务消息或 WinFS 对象。 “Longhorn”还提供许多内置数据源类,使你可以轻松地以声明方式以异步方式将数据引入应用程序。 XML、.NET 对象、ADO.NET 数据集和 WinFS 对象有特定的数据源。 数据源模型是可扩展的,因此可以在必要时创建自己的自定义数据源类。
创建数据绑定
可以将用户界面元素的动态属性绑定到任何 CLR 对象的属性。 为此,必须描述数据源的某个项与目标用户界面元素之间的所需对应关系。 每个此类通信或绑定都必须指定以下内容:
- 数据源项
- 数据源项中相应值的路径
- 目标用户界面元素
- 目标用户界面元素的相应属性
数据绑定
框架通过 MSAvalon.Data.Bind 类的实例表示数据绑定。 此类具有许多控制数据绑定的属性: Path、BindType、UpdateType、Transformer、Culture、BindFlags 和 Source。
将 Path 属性设置为一个字符串,该字符串指定绑定对象绑定到的数据源中的属性或值。 BindType 属性控制数据绑定的方向和频率。 它必须是三个值之一: OneWay、TwoWay 和 OneTime。 OneWay 绑定类型会导致数据绑定将新值从数据源传输到目标属性,但它不会将目标属性中的更改传播回数据源。 TwoWay 绑定类型在两个方向上传播更改。 指定 OneTime 绑定类型时,数据绑定仅在首次激活绑定时将值从数据源传输到目标属性。
可以将 Transformer 属性设置为实现 IDataTransformer 接口的任何对象。 当数据绑定传播值时,它会通过转换器传递该值。 转换器检查传入值,并生成新值作为输出。 请注意,输入和输出值不需要是同一类型。 可以将整数标志作为输入,并生成不同的图像文件作为输出。
当绑定类型为 TwoWay 时,UpdateType 属性确定对目标属性的更改何时传播回数据源。 可以指定三个值之一: 即时 表示在目标属性更改后立即将新值传播到数据源; OnLostFocus 表示在目标控件失去输入焦点时传播值;和 Explicit 表示要等到代码调用绑定对象并告知它传播新值。
Source 属性引用绑定的源数据项。 可以使用 DataSource、ElementSource、DataContextSource 或 ObjectSource 属性来设置绑定对象的 Source 属性。 使用 ElementSource 属性将 Source 设置为 Element,方法是提供元素 ID 作为 ElementSource 属性的值。 DataContextSource 属性允许您通过将元素 ID 设置为 DataContextSource,将 Source 设置为另一个元素的数据上下文。 使用 ObjectSource 属性将对象指定为绑定的源。 最后,DataSource 属性允许您将 Source 设置为 DataSource 的 Data 属性。 数据源项部分将对此进行详细讨论。
使用 Culture 属性可以为需要感知区域性的绑定指定 CultureInfo。
BindFlags 属性支持单个非零值:NotifyOnTransfer。 了解数据绑定本质上是异步的,这一点非常重要。 更改数据源中的值时,相应的目标属性不会立即收到更新的值。 新值传播到目标属性可能需要任意时间。 如果需要知道绑定何时完成了目标属性的更新,请将 BindFlags 属性设置为 NotifyOnTransfer 值。 然后,数据绑定将在更新目标属性后触发 MSAvalon.Data.DataTransfer 事件。
使用代码定义绑定表达式
你可以以编程方式创建数据绑定,尽管我预计你很少需要或想要这样做。 只需创建 Bind 类的实例并调用 SetBinding 方法。 下面是一个可能的示例:
using MSAvalon.Data;
Bind binding = new Bind ();
binding.Path = path;
binding.BindType = bindType;
binding.Source = source;
binding.UpdateType = updateType;
element.SetBinding (property, binding);
或者,下面是使用 Bind 类的便利构造函数之一编写先前代码的另一种方法:
using MSAvalon.Data;
Bind binding = new Bind (path, bindType, source, updateType);
element.SetBinding (property, binding);
可以使用其他方便的方法,例如元素上的 SetBinding 方法,并将前面的代码简化为以下代码:
element.SetBinding (property, path, bindType, source, updateType);
使用标记定义绑定表达式
我希望你更愿意使用标记定义大多数数据绑定。 上述所有概念仍然适用 - 创建 Bind 对象,将其属性设置为适当的值,并将其与目标元素的属性相关联。 例如,以下标记创建 Bind 对象作为 Button 对象的 Text 属性的值。
<DockPanel xmlns="https://schemas.microsoft.com/2003/xaml" />
<DockPanel.Resources>
<myNameSpace:Person def:Name="MyPerson" Name="Bob"/>
</DockPanel.Resources> . . .
<Button>
<Button.Content>
<Bind Path="Name" BindType="OneWay" ObjectSource="{MyPerson}" />
</Button.Content>
</Button>
若要将数据绑定与特定用户界面元素的 属性相关联,请使用数据绑定作为 属性的值。 在刚刚显示的示例中,数据绑定将名为 MyPerson 的资源绑定到 Button 元素的 Text 属性,因为我定义了 Button.Text 开始标记和结束标记之间的数据绑定。
DockPanel Resources 属性声明以下子元素是资源。 与运行时分析 XAML 文件时实例化的常规 XAML 元素不同,在实际使用资源之前,运行时不会实例化资源。
在前面的示例中,绑定的 源 是 Person 对象,因此 绑定 实例引用此 Person 对象实例。
Path 属性指定数据源项中相关值的路径。 在前面的示例中,路径只是 Name,因此绑定检索 Person 实例的 Name 属性。 但是,路径可能更为复杂。 例如,如果 Name 属性返回具有附加结构的对象,则路径可能类似于 Name.FirstName。
前面的示例演示了如何使用 Button.Text 属性值的复杂属性定义来定义数据绑定。 但是,可以对数据绑定表达式使用替代定义,并且更为简洁。 在本例中,将字符串定义为数据绑定表达式。 字符串以星号字符开头,XAML 编译器将其解释为转义字符,然后是要实例化的类的名称,然后用括号括起一系列分号分隔的名称值对。
<DockPanel >
§
<Button Text="*Bind(Path=Name;BindType=OneWay)" />
§
</DockPanel>
定义数据绑定但未使用 DataSource、ElementSource、DataContextSource 或 ObjectSource) (指定 Source 属性的值时,数据绑定将从当前元素的 DataContext 属性检索数据源。 当当前元素没有 DataContext 时,绑定对象以递归方式检索父元素的 DataContext 。 这样,便可以在标记中的相应元素上定义数据源一次,然后在子元素上的各种绑定中使用该数据源。
在以下示例中,我将 DockPanel 元素的 DataContext 属性设置为引用数据源的数据绑定。 实际上,当所有子元素不将其设置为其他值时,它们都会继承此 DataContext 属性。 由于 Button 元素上的数据绑定未指定 Source 属性的值,因此绑定使用继承的 DataContext 中的源。 当然,始终可以指定源,使特定数据绑定使用不同的数据源。
<DockPanel xmlns="http:////schemas.microsoft.com//2003//xaml//"
DataContext="{MyPerson}>
§ <Button Text='*Bind(Path="Name";BindType="OneWay")' />
<Button Text='*Bind(Path="Age";BindType="OneWay")' />
§</DockPanel>
数据绑定类型
特定数据绑定可以是三种类型: OneTime、 OneWay 和 TwoWay。 声明绑定时,将 BindType 属性设置为其中一个枚举值。
One-Time数据绑定
请求一次性数据绑定时,运行时使用数据源和指定路径检索源值并将指定目标属性初始化为该值。 通常,当源或目标属性更改值时,后续不会发生任何操作。
但是,有两种特殊情况。 当元素的 DataContext 有效更改时,数据源已更改,因此绑定将执行另一次一次性传输。 此外,在许多情况下,数据上下文是指 对象的集合。 当集合的当前对象发生更改时,数据绑定将执行一次性传输。
One-Way数据绑定
请求单向数据绑定时,运行时将检索源值,并将指定的目标属性初始化为该值。 每次源值更改时,数据绑定都会检索新值并重新初始化目标属性。
Two-Way数据绑定
请求双向数据绑定时,运行时会检索源值,并将指定的目标属性初始化为该值。 每次源值更改时,数据绑定都会检索新值并重新初始化目标属性。 此外,当目标属性更改值时(例如,当用户键入编辑控件时),数据绑定会检索新的目标属性值并将其传播回源。 双向数据绑定是数据绑定的默认类型。
Transformers
转换器允许将值从一种形式转换为另一种形式,因为它在数据源和从数据源传播到目标时。 可以使用转换器将值从其内部表示形式转换为唯一的显示值。 例如,可以使用转换器使用红色文本显示负浮点数,使用黑色文本显示正数。 还可以为客户显示各种信用评级的不同图标。
还可以将转换器用作数据类型转换器。 例如,源值可以是 Point 对象,而要将值绑定到的属性需要 Length 实例。
转换器还会接收用户界面的区域性信息作为其参数之一。 可以使用此信息根据用户的当前区域性定制显示的用户界面,例如,在不同的区域性下运行时,可以提供不同的图标。
IDataTransformer 接口
转换器是实现 IDataTransformer 接口的任何对象。 下面是 接口的定义:
interface IDataTransformer {
object Transform (object o, DependencyID id, CultureInfo culture);
object InverseTransform (object o, PropertyInfo pInfo, CultureInfo culture);
}
数据绑定在将源值传播到目标属性时调用 Transform 方法。 参数 o 是源值,参数 ID 标识目标属性,参数 区域性 标识转换的区域性。
数据绑定在将已更改的目标属性值传播回源时调用 InverseTransform 方法。 在本例中,参数 o 是已更改的目标属性的值, pInfo 标识将值转换为的类型。 和以前一样, 区域性 是转换的区域性。
这两种方法都允许你返回 null ,以指示绑定不应向相应的方向传播值。 下面是一个简单的转换器,它基于整数值返回颜色:
<SimpleText Text="*Bind(Path=Name)" Foreground="*Bind(Path=Age; Transformer=AgeToColorTransformer)"/>
public class AgeToColorTransformer: IDataTransformer {
public object Transform (object o, DependencyID di, CultureInfo culture) {
int age = (int) o;
if (age < 0 || age > 120) return Grey;
if (age <= 30) return Green;
if (age <= 70) return Gold;
if (age <= 120) return Red;
}
public object InverseTransform (object o, PropertyInfo i, CultureInfo c) {
return null;
}
}
提供属性更改通知
CLR 不提供对象通知其客户端其属性之一已更改的通用方法。 但是,动态绑定需要此类通知,以便绑定可以将更改的属性值传播到目标动态属性。 “Longhorn”引入了 IPropertyChange 接口,以允许对象在其某个属性更改值时发出信号。 请注意, 接口定义了 一个 PropertyChangedEventHandler 类型的事件,事件处理程序可以使用处理程序的第二个参数的 PropertyName 属性检索已更改属性的名称。
interface IPropertyChange {
event PropertyChangedEventHandler PropertyChanged;
}
delegate void PropertyChangedEventHandler (object sender,
PropertyChangedEventArgs e);
class PropertyChangedEventArgs : EventArgs {
public virtual string PropertyName { get ;}
}
在以下代码中,我重写了本章前面的 Person 类,以支持更改 Name 和 Age 属性,并在发生此类更改时触发相应的事件。
namespace MyNamespace {
public class Person : IPropertyChange {
private string m_name;
private int m_age;
public event PropertyChangedEventHandler PropertyChanged;
public string Name {
get { return m_name; }
set {
if (m_name != value) {
m_name = value;
RaisePropertyChangeEvent ("Name");
}
}
}
public int Age {
get { return m_age; }
set {
if (m_age != value) {
m_age = value;
RaisePropertyChangeEvent ("Age");
}
}
}
private void RaisePropertyChangedEvent (string propertyName) {
if (PropertyChanged != null)
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
public Person (string name, int age) {
m_name = name; m_age = age;
}
}
}
每当对象的“有趣”属性之一更改值时,对象通过调用 PropertyChanged 委托来实现此接口。 请注意,仅当动态绑定中使用的属性更改值时,才需要调用委托。 对象可以具有不为其触发更改通知的属性。
出于性能原因,仅当 属性确实更改了值时,才应触发更改通知。 当对象不知道哪个属性更改了值时,它可以请求更新自身上任何属性的所有绑定,也可以为更改的属性名称传递 String.Empty 。
数据源项
“Longhorn”提供了一组内置数据源,使你可以轻松地以声明方式以异步方式将数据导入应用程序,而不会阻止 UI。
数据源项是实现 IDataSource 接口的任何对象。
interface IDataSource {
public virtual Object Data { get; }
public virtual void Refresh()
}
此接口具有 Data 属性,该属性允许绑定从数据源项获取数据。 Refresh 方法允许绑定请求数据源项在数据不可用时检索其数据。 “Longhorn”提供了许多数据源类,你很快就会看到其中一些类。
通常,数据源实现还提供一个强类型属性,该属性返回数据提供程序的本机 API。 例如, SqlDataSource 和 XmlDataSource 类分别提供 DataSet 和 Document 属性:
class SqlDataSource : IDataSource, … {
§ DataSet DataSet { get; }
§}
class XmlDataSource : IDataSource, … {
§ XmlDocument Document { get; }
§}
因此,如果确实需要,可以直接访问基础数据提供程序。
使用任何 CLR 对象作为数据源
ObjectDataSource 类允许您创建指定类型的实例作为数据源项。 通常,你将使用 XAML 并将数据源声明为标记中的资源。 例如,假设我在名为 MyAssembly 的程序集中具有以下 Person 类的定义,并且我想将此类的实例用作数据源项:
namespace MyNamespace {
public class Person {
private string m_name;
private int m_age;
public string Name { get { return m_name; } }
public int Age { get { return m_age; } }
public Person (string name, int age) {
m_name = name; m_age = age;
}
}
}
Person 类不需要任何其他支持即可成为数据源项。 我可以使用 ObjectDataSource 类的实例作为数据源项,并通知它基础数据提供程序应是 Person 类的实例。 为此,可以使用标记将 ObjectDataSource 的实例声明为 XAML 资源。
<DockPanel>
<DockPanel.Resources>
<ObjectDataSource def:Name ="source1"
TypeName="MyNamespace.Person, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcde f"
Parameters="Brent, 0x30" />
</DockPanel.Resources>
</DockPanel>
与往常一样,XAML 元素名称表示框架类名。 因此, ObjectDataSource 元素指示创建 ObjectDataSource 类的实例。 def:Name 属性的值是上例) (“source1”的此资源的名称。
ObjectDataSource 实例将通过调用 TypeName 的默认构造函数或指定 Parameters 属性时,通过调用与 Parameter 属性值的签名最匹配的构造函数来创建由 TypeName 引用的类的新实例。
回想一下,标记等效于代码,因此前面的标记与以下代码相同:
ObjectDataSource source1 = new ObjectDataSource();
source1.TypeName = "MyNamespace.Person, MyAssembly, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=0123456789abcdef";
source1.Parameters = "Brent, 0x30";
ObjectDataSource 类还提供对对象调用方法并引用现有对象的机制,以及仅实例化新对象。
将数据源与数据绑定配合使用
可以在元素或应用程序级别资源的 Resources 属性中将数据源声明为资源。 在应用程序资源中声明的任何数据源都可以在任何页面中跨应用程序使用。 元素的资源中定义的数据源只能在元素的范围内使用。
可以在元素或应用程序级别资源的 Resources 属性中将数据源声明为资源。 在应用程序资源中声明的任何数据源都可以在任何页面中跨应用程序使用。 元素的资源中定义的数据源只能在元素的范围内使用。
在刚刚显示的示例中,数据绑定绑定名为 source1 的数据源。可以将 DataSource 属性设置为 XAML 资源的资源 ID。 当资源实现 IDataSource 接口时,运行时会将 Bind 实例的 Source 属性设置为指定 DataSource 资源的 Data 属性返回的对象。 当资源未实现 IDataSource 接口时,运行时会将绑定的 Source 属性设置为资源对象本身。
在前面的示例中, DataSource 属性引用 ObjectDataSource 资源。 因此,绑定从 ObjectDataSource 请求 Data 属性。 数据源反过来会实例化标记中指定的 Person 类。
在第一个 Button 中,绑定实例的 Source 属性引用此 Person 对象实例。 路径只是 Name,因此绑定检索 Person 实例的 Name 属性。
第二个按钮中, DataContext 绑定到 ObjectDataSource。 此操作会将 Button 的 DataContext 设置为 Person 对象,以便 Button 上的任何绑定都将使用 Person 对象作为其绑定的默认源。
同样,可以将数据绑定到任何可用的数据源。 以下段落中提到了“Longhorn”附带的一些其他数据源。 其他数据源(例如用于从 Web 服务获取数据的数据源)将联机。
使用 XML 作为数据源
XmlDataSource 类是使用 XML 文档对象模型 (DOM) 作为基础数据提供程序的数据源。 可以使用标记从统一资源定位符创建 DOM, (URL) 引用 XML 流。 还可以通过提供带有 标记的内联 XML 来创建 DOM,如以下示例所示:
使用 URI
<DockPanel>
<DockPanel.Resources>
<XmlDataSource def:Name="source2"
Source="http://www.wiseowl.com/People.xml"
XPath="/People/Person[@Age>21]" />
使用内联标记
<XmlDataSource def:Nsme="source3"
XPath="/People/Person[@Age>50]" >
<People>
<Person Name='Bambi' Age='61'>
<Person Name='Bozo' Age='54'>
<Person Name='Brent' Age='48'>
§ </People>
</XmlDataSource>
</DockPanel.Resources>
</DockPanel>
使用 数据集 作为数据源
SqlDataSource 类是使用数据集作为基础数据提供程序的数据源。 它创建一个 数据集 ,并通过对数据库执行 SQL 命令来填充数据集。
<DockPanel>
<DockPanel.Resources>
<SqlDataSource def:Name="source4">
ConnectionString="server=localhost;Database=UserGroup"
SelectCommand="SELECT * FROM Members" />
</SqlDataSource>
<DockPanel.Resources>
</DockPanel>
或者,可以使用 ObjectDataSource 类并绑定到已在代码隐藏文件中定义的强类型 DataSet 派生类。
<DockPanel>
<DockPanel.Resources >
<ObjectDataSource def:Name="sds1"
Type="MyDataset"/>
</ObjectDataSource>
</DockPanel.Resources>
</DockPanel>
使用 Windows 存储作为数据源
WinFS DataSource 类使用 WinFS 作为基础数据提供程序。 你可以使用它绑定到 Microsoft® Windows® 存储维护的日常信息。
<DockPanel>
<DockPanel.Resources>
<WinFSDataSource ContextString="c:\">
<WinFSDataSource.Query
Type="Person" Filter="DisplayName='Ted'" Sort="DisplayName ASC">
<Query.ProjectionOptions Field="DisplayName" />
<Query.ProjectionOptions Field="Birthdate">
<Projection.ProjectionOptions … />
</Query.ProjectionOptions>
</ WinFSDataSource.Query>
</WinFSDataSource>
</DockPanel.Resources>
</DockPanel>
使用自定义数据源
还可以将自定义类指定为数据源。 类必须实现 IDataSource 接口。 通常需要将 XML 命名空间前缀与自定义类的命名空间相关联,然后像往常一样在标记中使用前缀限定类名称:
<Canvas … >
§
<Canvas.Resources>
<WO:InfraredDataSource def:Name="source8"
PropA='value1'
PropB='value2'
</WO:InfraredDataSource>
</Canvas.Resources>
</Canvas>
总结
数据绑定提供了一种简单有效的方法,用于将信息连接到显示数据的用户界面元素。 可以在任一方向上自动传播值,一次或重复传播,并能够在需要时动态转换数据表示形式。 使用标记几乎无需编程编码即可执行此操作。 数据绑定允许你从所需位置获取数据,然后继续编写应用程序的其余部分。
继续学习第 6 章:沟通