UWP: 掌握编译型绑定 x:Bind

在 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 的数据源

与传统绑定较大的区别,是 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();
        }
    }
}

绑定到事件

在使用传统绑定时,对于控件操作的响应,我们一般会用到命令或行为(当控件不支持 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 文件中生成相应的关于绑定的代码,你在这里可以查看动态生成的代码,并设置断点以调试。由于我对此并未作深入的调研,所以在此不再详述;

总结

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

参考资料:

{x:Bind} markup extension

源码下载

时间: 2024-08-05 19:37:42

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

【WIN10】绑定x:Bind

在WP8.WP8中,我们知道有一个绑定{Binding},而在Win10中,新增了一个绑定{x:Bind} x:Bind :为编译时绑定 ,内存.内存相对于传统绑定都有优化 特性: 1.为强类型      2.默认上下文为Page或UserControl       3.Mode默认为OneTime xaml: <TextBlock Text="{x:Bind BindData}"/> 后台代码: 1 public sealed partial class MainPage

解释器和编译器,编译型原理和解释型原理

解释器: 解释器(英语:Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行.解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢.它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去. 解释器运行程序的方法有: 1.直接运行高级编程语言 (如 Shell 自带的解释器) 2.转换高级编程语言码到一些有效率的字节码 (Bytecode),并运行这些字节码

编译型与解释型、动态语言与静态语言、强类型语言与弱类型语言的区别

一.编译型和解释型 我们先看看编译型,其实它和汇编语言是一样的:也是有一个负责翻译的程序来对我们的源代码进行转换,生成相对应的可执行代码.这个过程说得专业一点,就称为编译(Compile),而负责编译的程序自然就称为编译器(Compiler).如果我们写的程序代码都包含在一个源文件中,那么通常编译之后就会直接生成一个可执行文件,我们就可以直接运行了.但对于一个比较复杂的项目,为了方便管理,我们通常把代码分散在各个源文件中,作为不同的模块来组织.这时编译各个文件时就会生成目标文件(Object  

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

readonly 关键字与 const 关键字不同. const 字段只能在该字段的声明中初始化. readonly 字段可以在声明或构造函数中初始化. 因此,根据所使用的构造函数, readonly 字段可能具有不同的值. 另外, const 字段是编译时常量,readonly 字段为运行时常量. 你应该尽量使用运行时常量.原因是变异性常量虽然性能稍微快一些.但是却没有 运行时常量那么灵活.就像第一段中所说,使用readonly时,根据使用的构造函数, readonly字段可能具有不同的值.

Jquery中的事件绑定$(&quot;#btn&quot;).bind(&quot;click&quot;,function(){ })

Jquery中的事件绑定:$("#btn").bind("click",function(){  }) 因为每次都这么调用太麻烦,所以jquery就用$("#btn").click(function(){})来进行简化 Jquery中的事件绑定$("#btn").bind("click",function(){ })

java是编译型还是解释型语言/

有人说Java是编译型的.因为所有的Java代码都是要编译的,.java不经过编译就无法执行. 也有人说Java是解释型的.因为java代码编译后不能直接运行,它是解释运行在JVM上的,所以它是解释型的.对于C和C++,它们经过一次编译之后,可以由操作系统直接执行,所以它们是编译型语言.而Java不一样,它首先由编译器编译成.class(字节码)文件,然后在通过JVM从.class文件中读一行解释执行一行,所以它是解释型的语言.也正是由于java对于多种不同的操作系统有不同的JVM,所以实现了真

jQuery事件绑定方法bind、 live、delegate和on的区别

我们试图绑定一些事件到DOM元素上的时候,我相信上面这4个方法是最常用的.而它们之间到底有什么不同呢?在什么场合下用什么方法是最有效的呢? 1.准备知识 当我们在开始的时候,有些知识是必须具备的: 1).DOM树 下图仅仅是一个示例,这是一个在browser环境下的一棵模拟DOM树,在下面的代码中仅起到演示的作用: 2).Event bubbling (aka event propagation)冒泡 我们的页面可以理解为一棵DOM树,当我们在叶子结点上做什么事情的时候(如click一个a元素)

编译型与解释型

简单概括~~ 编译型:程序写完后需经编译再运行,也就是先在当前系统环境下直接生成与机器交互的二进制文件 优点:运行速度快,安全性高 缺点:跨平台差,修改需重新编译 代表:c字家族,多用于底层架构 解释型:程序在运行的同时进行编译 优点:可跨平台,可直接修改程序 缺点:运行速度慢,安全性差 代表:python,java,go 原文地址:https://www.cnblogs.com/yong2018/p/8289361.html

【UWP】实现 FindAncestor 绑定

原文:[UWP]实现 FindAncestor 绑定 在 WPF 里,我们是可以在 RelativeSource 上面实现的,举个例子: <Grid Tag="2"> <Button> <Grid Tag="1"> <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid, Ancest