在 UWP 开发中,我们在进行数据绑定时,除了可以使用传统的绑定 Binding,也可以使用全新的 x:Bind,由于后者是在程序编译时进行初始化操作(不同于 Binding,它是在运行时创建、初始化),所以我们可以称 x:Bind 为编译型绑定,正像本文标题一样。
之所以引入 x:Bind,是因为它相比传统的 Binding 有很多优点,比如:

  • 性能更好;
  • 编译时错误;
  • 便于调试:
  • 使用方便(绑定到函数、事件等)

鉴于 x:Bind 有以上这些优点,所以这里推荐大家在自己的项目中尽可能地使用它;当然,相比 Binding,它也少了一些功能,所以在必要的时候,你任然需要使用传统的绑定。换句话说,在项目中,你可以混合使用这两种绑定方式。再次声明:建议尽可能地使用 x:Bind,除非 x:Bind 不能完成你要的操作时,才考虑使用 Binding。

以下我会把 x:Bind 的使用方法以及上面提及的优点,进行较为详细的说明。本文假设你已经掌握了(或者至少理解) WPF/UWP 中的数据绑定的基本知识;在继续学习下文之前,如果你还不了解数据绑定,建议你最好了解相关知识(相信大多数的 XAML 开发人员都没问题)。另外,由于本文覆盖了 x:Bind 的常见的多种用法;所以内容较长;不过,你不用担心,因为我为本文的讲解写了一个 UWP Demo(文章最后附有下载链接),有不懂的地方,也可结合 Demo 来看;在以下每段讲解后都有相应的代码,这些代码几乎都是从这个 Demo 中贴过来的。

一、x:Bind 的数据源

与传统绑定较大的区别,是 x:Bind 的数据源为当前 View(即页面 Page 或用户控件UserControl)自身,也就是说,它使用 Page 或 User Control 的实例为作数据源;因此如果你设置了 Path 属性, x:Bind 会到当前 Code-Bebind 类中找对应名称的成员(属性、字段、方法)。在下例中,x:Bind 会在当前用户控件实例中找到其 InfoA 属性并进行绑定。

<UserControl x:Class="xBindTest.Controls.BindingModeControl"...
<TextBlock Text="{x:Bind InfoA}" />
...
</UserControl>
    public sealed partial class BindingModeControl : UserControl, INotifyPropertyChanged
{
public string InfoA { get; set; }
}

顺便提一下,如果找不到 InfoA 属性,编译就会失败,这就是 x:Bind 的优点之一,提供编译时错误,不像 Binding 一样,仅在 VS 的 输出(Output) 窗口输入错误提示而已。

在传统的绑定中,Binding 的数据源可以通过四种形式指定,它们分别是 DataContext(默认)、RelativeSource、Source、ElementName。而 x:Bind 既然将当前 View 的实例作为唯一数据源,那么我们就完全不需要像传统 Binding 一样设置 DataContext;而对于后面三种设置数据源的方式, x:Bind 也仅支持以下两种情况:

  • ElementName     
        x:Bind -> {x:Bind slider1.Value}      
        Binding -> {Binding Value, ElementName=slider1}   
  • RelativeSource: Self     
        x:Bind -> <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />        
        Binding -> <Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... /> 

说明:上例中,slider1 和 rect1 都是当前 View 中的控件,本质上它们都是当前 View 的字段,所以可以直接在 x:Bind 中使用;除了上述两种情况外,x:Bind 对于 Source 和其它形式的 RelativeSource 均不支持。

二、绑定模式(Binding Mode)

接下来,我们来看 x:Bind 的绑定模式。

x:Bind 的 Binding Mode 值有以下三项:OneWay、OneTime、TwoWay;它的默认值是 OneTime。记住这一点非常重要,因为在开发过程中,很多时候绑定并不像我们想象的正常工作,就是因为 Mode 没有被设置为合适的值。

OneTime 的意思是仅在界面初始化时去初始化界面中的绑定;这一点也是 x:Bind 性能更优的原因。顺便提一下,传统 Binding 的 Mode 属性默认值是 Default(这个值的意义是对于只读控件它是 OneWay,对于可编辑的控件,它是 TwoWay)。

更具体来说,x:Bind 的绑定是在 Page 或 User Control 的 Loading 事件中初始化的;也就是说,在 Mode=OneTime(默认值)时,仅当一个属性值的设置在 View 的构造函数中时(在 Loading 事件之前)才会在 x:Bind 初始化中被更新到 UI 中;在其它位置(如 Loaded 事件或某一操作的响应事件中等等)修改此属性的值,都不会再被更新(即使调用了 INotifyPropertyChanged 中的 PropertyChanged 事件)。参考以下代码:

<TextBlock Margin="{StaticResource ContentMargin}" Text="{x:Bind InfoA}" />
public BindingModeControl()
{
InfoA = "InfoA: Value for x:Bind (Mode=One Time)";
}

而如果设置了 Mode=OneWay,绑定初始化时,会创建关联,当绑定源的值更改后,绑定目标(UI)也及时更新。参考以下代码:

<TextBlock VerticalAlignment="Center" Text="{x:Bind InfoB, Mode=OneWay}" />
private void btnUpdateValueForOneWay_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
InfoB = "InfoB: Value Updated";
}

现在重新考虑第一种情况,如果我们没有为绑定设置 Mode,它就使用默认值 OneTime。在这种情况下,如果我们确实想要在构造函数之外的其它地方通过更新该属性值以更新 UI,该怎么办呢?这里就需要使用当前 View 的 Bindings 对象。

如果当前 View 中使用了 x:Bind,那么它就会有一个字段 Bindings,这个字段是在 obj 文件夹中生成的 <viewname>.g.cs 文件中动态生成的。它有三个方法如下:

  • Update()  调用此方法将更新当前 View 中所有 x:Bind 绑定的值
  • Initialize()  调用此方法时,将会判断绑定是否初始化;如果没有,就直接调用 Update 方法,如果已经初始化,则什么都不作;
  • StopTracking()  移除初始化 OneWay 和 TwoWay 绑定时创建的所以 Listeners,也即 View 不再监听属性值的更新;

当我们修改了某个属性值时,即使它是 OneTime 绑定模式,通过 Bindings 的 Update 方法也可以更新 UI。参考以下代码:

<TextBlock VerticalAlignment="Center" Text="{x:Bind InfoC}" />
private void btnUpdateValueForOneTime_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
InfoC = "InfoC: Value updated by this.Bindings.Update() method";
this.Bindings.Update();
}

三、转换(Converting)

在数据源属性类型和绑定目标属性类型不一致时,如果我们使用传统的 Binding,可以将一个实现了 IValueConverter 的对象设置到 Binding 的 Converter 属性来实现值的转换。而使用 x:Bind,除了这种方式之外,还有更方便的——绑定属性到函数。也就是说,你可以将一个函数放到 x:Bind 中。当然,x:Bind 仍然是在当前 View 的 Code-Behind 代码中来找所指定的函数。参考如下代码:

        <Border
x:Name="border"
Background="{x:Bind GetBrush(IsPass), Mode=OneWay}">
<Image Margin="20" Source="{x:Bind GetImage(IsPass), Mode=OneWay}" />
</Border>
        public Brush GetBrush(bool isPass)
{
return isPass ? new SolidColorBrush(Colors.LimeGreen) : new SolidColorBrush(Colors.Crimson);
} // both public and private work well
private ImageSource GetImage(bool isPass)
{
return isPass ? new BitmapImage(new Uri("ms-appx:///Assets/Happy.png")) : new BitmapImage(new Uri("ms-appx:///Assets/Sad.png"));
}

在上面的例子中,两处被绑定的函数均接受一个参数,事实上,这里支持多个参数。所以这一点也要比 IValueConverter 方便;此外,绑定属性到函数也支持类似于 IValueConverter 中的双向转换,除了能从源类型转换到目标类型,也支持从目标类型转换到源类型,方法是使用 BindBack 属性指定另外一个方法。

另外,还一个非常便捷的转换是 Visibility 和 bool 之间的转换:控件的 Visibility 可以直接绑定到一个布尔属性或字段;当布尔值为 true 时,Visibility 的值是 Visible,反之,是 Collapsed。参考如下代码:

<Button Content="Logout" Visibility="{x:Bind IsLogin}" />

最后,需要说明的是,上述两项转换功能仅在周年更新(14393/1607)版本及更高版本中才支持。

四、在 DataTemplate 中使用

为列表控件(如 ListView 等)设置 ItemTemplate 属性时,要用到 DataTemplate;如果在 DataTemplate 中使用 x:Bind,要怎么做呢?

首先要为 DataTemplate 指定 x:DataType,告诉它要展示的数据 Model 类,一般情况下,这需要引入 xmlns 命令空间;然后,在 DataTemplate 内部,使用 x:Bind 直接绑定到该 Model 的相关属性。参考以下代码:

<UserControl ...
xmlns:models="using:xBindTest.Models">
<UserControl.Resources>
<DataTemplate x:Key="FriendItemTemplate" x:DataType="models:Friend">
<StackPanel Margin="0,4">
<TextBlock
FontSize="20"
FontWeight="SemiBold"
Text="{x:Bind Name}" />
<TextBlock
Margin="{StaticResource ContentMargin}"
FontSize="14"
Text="{x:Bind Email}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListView ItemTemplate="{StaticResource FriendItemTemplate}" ItemsSource="{x:Bind AllFriends}" />
</Grid>
</UserControl>

编译即可正常运行。如果没有为 DataTemplate 设置 x:DataType,或在 DataTemplate 中绑定了 Model 中不存在的属性都会编译失败。

上面是在当前 View 中引用 DataTemplate 资源。在实际项目开发中,你可能会将资源统一放到一个或若干个 ResourceDictionary 文件中,目的是为了更方便地组织资源。那么上面这个使用了 x:Bind 的 DataTemplate 应该如何被移动到 ResourceDictionary 文件中呢?

首先,直接移动一定会编译出错,原因是使用 x:Bind 的 XAML 文件必须得有 Code-Behind 文件。怎么解决呢?

可以新建一个 Page 或 UserControl,然后,将其基类由 Page 或 UserControl 改为 ResourceDictionary,删去不需要的、默认添加出来的 UI 元素,然后将 DataTemplate 资源项复制进来,即可。参考如下代码:

<ResourceDictionary
x:Class="xBindTest.Styles.DataTemplates"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:xBindTest.Models"
mc:Ignorable="d">
<DataTemplate x:Key="AnotherFriendItemTemplate" x:DataType="models:Friend">
<StackPanel Margin="0,4">
<TextBlock
FontSize="20"
FontWeight="SemiBold"
Text="{x:Bind Name}" />
<TextBlock
Margin="{StaticResource ContentMargin}"
FontSize="14"
Text="{x:Bind Email}" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
using Windows.UI.Xaml;

namespace xBindTest.Styles
{
public sealed partial class DataTemplates : ResourceDictionary
{
public DataTemplates()
{
this.InitializeComponent();
}
}
}

然后,在 App.xaml.cs 中,添加如下代码:

<Application ...
xmlns:style="using:xBindTest.Styles" ... />
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<style:DataTemplates />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

要特别注意上面代码中最中间那一行,也就是加粗那行。一般情况下,在 ResourceDictionary.MergedDictionaries 中引用其它文件,应该新添加一行 <ResourceDicionary Source="..." />,但是如果你这么引用刚才的资源文件,虽然可以编译通过,但运行时不会看到效果(数据模板会是空白)。原因仍然是我们使用了带有 Code-Behind 的 ResourceDictionary,所以应该使用上述代码中的写法。

五、绑定到事件

在使用传统绑定时,对于控件操作的响应,我们一般会用到命令或行为(当控件不支持 Command 或对控件的某一特定事件进行响应时,如 ListView 控件的 SelectionChanged 事件);然而 x:Bind 可以轻松地实现同样的操作,因为它支持绑定到事件,来看代码:

<Button Click="{x:Bind ShowInfoTest1}" Content="Show Info" />
public void ShowInfoTest1()
{
Info = "Update Info in method: ShowInfoTest1()";
}

像绑定属性一样简单,不同的是,被绑定的不再是属性,而是事件名,而 Path 也不是属性名,而是方法名。相比 ICommand 或行为对此操作的实现,要简单的多。

这里需要补充的是,关于方法的签名:

  • 参数可以为空,如:
void ShowInfoTest1()
  • 也可以与被绑定事件的签名一致,如:
void ShowInfoTest1(object sender, RoutedEventArgs e)
  • 还可以是个数与事件签名的个数一致,事件签名中每个参数类型都可以转换为方法中所定义的参数类型,如:
void ShowInfoTest1(object sender, object e)

如果不一致,在项目编译时就不会通过。

另外,在绑定到事件中,x:Bind 除了支持上述灵活的方法签名,对于方法的返回值并没有要求,不仅可以是 void,也可以是其它任何返回类型;并且也支持 async 方法的绑定。

六、MVVM

基本上,x:Bind 的主要特性到这里就基本上都提到了。但是,有一个问题,在 UWP 应用开发过程中,我们一般使用 MVVM 模式,而 x:Bind 将当前 View 作为数据源,怎么才能使其绑定到 ViewModel 中的成员呢?很简单,只要在 Page 或 UserControl 中添加 ViewModel 属性,其类型为对应 View 的 ViewModel,而在 x:Bind 中使用多级 Path 即可。参考如下代码:

    public sealed partial class BindToEventControl : UserControl
{
public BindToEventControl()
{
this.InitializeComponent();
ViewModel = new BindToEventViewModel();
} public BindToEventViewModel ViewModel { get; set; }
}
<Button Click="{x:Bind ViewModel.ShowInfoTest1}" />
<TextBlock Text="{x:Bind ViewModel.Info, Mode=OneWay, TargetNullValue='(no value)'}" />

另外,为了实现 View 与 ViewModel 的解耦,你可能会使用类似 ViewModelLocator 的类来实现对 ViewModel 的定位。在这种情况下,怎么结合 x:Bind 呢?

首先,你仍然可以保留 Page 的 DataContext 对 Locator 的引用;需要进一步处理的是,像上例一样,在 Page 中添加 ViewModel。参考如下代码:

<Page ...
DataContext="{Binding HomeViewModel, Source={StaticResource Locator}}">
    public sealed partial class HomePage : Page
{
public HomePage()
{
this.InitializeComponent();
} public HomeViewModel ViewModel => DataContext as HomeViewModel;
}

七、其它

在使用 x:Bind 时,有以下几点,也值得注意:

  • x:Bind 不支持 UpdateSourceTrigger,所以对于 TextBox 可以在不失去焦点前提下更新绑定源的值(通过设置 UpdateSourceTrigger=ProppertyChanged)这一情况,在 x:Bind 中是不能实现的;也就是说,对于这种需求,你仍然需要使用传统的 Binding;
  • 本文一开始曾提到 x:Bind 的优点之一是便于调试,当你在 View 中使用了 x:Bind,那么在 obj\(x64/x86/ARM)\<viewname>.g.cs 文件中生成相应的关于绑定的代码,你在这里可以查看动态生成的代码,并设置断点以调试。由于我对此并未作深入的调研,所以在此不再详述;
  • 本文曾在几处提到 x:Bind 与 Binding 的区别,但并没有完全列出所有区别,如果你想要了解它们的完整对比,可参考这篇文章:Data binding in depth(在该文的最后部分有一个对比表格)。

总结

本文主要讲到 UWP 中的 x:Bind,包括它的优点以及用法。它有性能更好、使用更方便、编译时检查错误、便于调试等优点,所以给大家的建议就是在你的项目中尽可能地使用它。当然,与传统 Binding 相比,它也有不及的地方,所以你仍然可以结合传统 Binding 完成你所要的功能。最后附上 xBindTest 的截图和源码,它是我针对 x:Bind 写的一个 Demo,本文中引用的代码几乎都在此项目中能找得到。如果什么问题或建议,欢迎随时交流。

参考资料:

{x:Bind} markup extension

源码下载

UWP: 掌握编译型绑定 x:Bind的更多相关文章

  1. 背水一战 Windows 10 (21) - 绑定: x:Bind 绑定, x:Bind 绑定之 x:Phase, 使用绑定过程中的一些技巧

    [源码下载] 背水一战 Windows 10 (21) - 绑定: x:Bind 绑定, x:Bind 绑定之 x:Phase, 使用绑定过程中的一些技巧 作者:webabcd 介绍背水一战 Wind ...

  2. 绑定: x:Bind 绑定, x:Bind 绑定之 x:Phase, 使用绑定过程中的一些技巧

    背水一战 Windows 10 之 绑定 x:Bind 绑定 x:Bind 绑定之 x:Phase 使用绑定过程中的一些技巧 示例1.演示 x:Bind 绑定的相关知识点Bind/BindDemo.x ...

  3. 【WIN10】绑定x:Bind

    在WP8.WP8中,我们知道有一个绑定{Binding},而在Win10中,新增了一个绑定{x:Bind} x:Bind :为编译时绑定 ,内存.内存相对于传统绑定都有优化 特性: 1.为强类型    ...

  4. SLF4j 居然不是编译时绑定?日志又该如何正确的分文件输出?——原理与总结篇

    各位新年快乐,过了个新年,休(hua)息(shui)了三周,不过我又回来更新了,经过前面四篇想必小伙伴已经了解日志的使用以及最佳实践了,这个系列的文章也差不多要结束了,今天我们来总结一下. 概览 这篇 ...

  5. in C#,编译型常量(const)和运行时常量(readonly)

    readonly 关键字与 const 关键字不同. const 字段只能在该字段的声明中初始化. readonly 字段可以在声明或构造函数中初始化. 因此,根据所使用的构造函数, readonly ...

  6. Fluent UDF【8】:编译型UDF

    UDF除了可以以解释的方式外,其还可以以编译的方式被Fluent加载.解释型UDF只能使用部分C语言功能,而编译型UDF则可以全面使用C语言的所有功能. 1 编译型UDF介绍 编译型UDF的构建方式与 ...

  7. 解释型vs编译型 动态vs静态 强类型vs弱类型

    ------------------------------------------------------------ 释型.动态语言与静态语言.强类型语言与弱类型语言的区别 编译型和解释型 我们先 ...

  8. 编译型 解释型 C++工作原理

    C++教程_w3cschool https://www.w3cschool.cn/cpp/ C++工作原理: C++语言的程序因为要体现高性能,所以都是编译型的.但其开发环境,为了方便测试,将调试环境 ...

  9. 【软考5】解释型 or 编译型

    导读:在上篇博客中,我们了解到,目前的编码语言经过不断的发展,已经经历了机器语言--汇编语言--高级语言的过程.虽然我们的编码语言在不停的升级,但作为计算机来说,它始终是一个只能理解0和1构成的机器语 ...

随机推荐

  1. 剑指offer编程题Java实现——二维数组中的查找

    题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数.   下面是我实现的代码 ...

  2. SQL SERVER的事务日志

    1 基本介绍 每个数据库都具有事务日志,用于记录所有事物以及每个事物对数据库所作的操作. 日志的记录形式需要根据数据库的恢复模式来确定,数据库恢复模式有三种: 完整模式,完全记录事物日志,需要定期进行 ...

  3. 每天一个linux命令(56)--crontab命令

    上一节学习了 at  命令是针对仅运行一次的任务,循环运行的例行性计划任务,Linux 系统则是由 cron(crond)这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个 ...

  4. 每天一个linux命令(34)--top命令

    今天给领导发邮件,我这边虽然显示发出去了,但是他那边一直没收到,结果我以为我发了,他又一直在那边等结果.所以说,以后要另外发个信息或者QQ微信之类的说一声. top命令是Linux 下常用的性能分析工 ...

  5. 【openstack N版】——块存储服务cinder

    一.块存储服务介绍 1.1块存储服务通常包含以下组件 cinder-api: 接受API请求,并将其路由到"cinder-volume"执行. cinder-volume: 与块存 ...

  6. How to build mscorlib.dll with visual studio

    Recently, Microsoft Corportation has released a new look for .NET Reference Source. And you may find ...

  7. Springboot启动源码详解

    我们开发任何一个Spring Boot项目,都会用到如下的启动类 @SpringBootApplication public class Application { public static voi ...

  8. cordova crosswalk android 7.0 问题

      带有crosswalk的cordova app 在Android7.0会闪退问题,为什么要crosswalk,我的回答是,还tmd不是要兼容5.0以下系统(4.4,4.2,4.0),这里省略100 ...

  9. hdoj1016 [dfs]

    http://acm.hdu.edu.cn/showproblem.php?pid=1016 题意: 已知一个数n,在1-n(包含 n ,0 < n < 20)里组合形成一个环形,使得每两 ...

  10. python 机器学习 决策树

    决策树(Decision Trees ,DTs)是一种无监督的学习方法,用于分类和回归. 优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关的特征数据缺点:可能会产生过度匹配的 ...