UWP: 掌握編譯型綁定 x:Bind

来源:http://www.cnblogs.com/wpinfo/archive/2017/04/06/6673219.html
-Advertisement-
Play Games

在 UWP 開發中,我們在進行數據綁定時,除了可以使用傳統的綁定 Binding,也可以使用全新的 x:Bind,由於後者是在程式編譯時進行初始化操作(不同於 Binding,它是在運行時創建、初始化),所以我們可以稱 x:Bind 為編譯型綁定,正像本文標題一樣。之所以引入 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

 

源碼下載 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.bin:保存可執行文件,即是命令。所有的用戶都可以執行。 2.boot:引導目錄,操作系統引導啟動內核。 3.dev:設備文件,所有的硬體都被抽象成了文件系統。 4.etc:配置文件目錄,其中大多數是以.conf結尾。 5.home:home目錄,即是每個用戶都有自己的home目錄,來進行存儲用 ...
  • 下載MATLAB2015b破解版 操作系統:Ubuntu 16.o4 LTS 程式文件:Matlab2015b glnxa64破解版 解壓提取文件:在ubuntu系統下可以直接提取壓縮文件,得到三個文件: license_standalone.lic Matlab 2015b Linux64 Cra ...
  • win7下安裝Ubuntu後進不去win7的解決方法 剛剛給同學在win7下安裝了Ubuntu16.04,結果在安裝完後竟然無法在電腦重啟後,找到win7的進入選項。 在網上找了找,都不行!就差點重裝了。終於還是找到了! 安裝Ubuntu後直接進入Ubuntu系統, "Ctrl+alt+t" 進入終 ...
  • 解決 Bash On Windows "無法從 Windows 應用商店下載。請檢查網路連接。"的問題 Fiddler和Bash On Windows 源離線壓縮包:http://pan.baidu.com/s/1kVofL4b 不想從百度網盤下載的,可以從官方下載: Fiddler:https:/ ...
  • 作業: 集群搭建 1、部署nginx反向代理三個web服務,調度演算法使用加權輪詢; 2、所有web服務使用共用存儲nfs,保證所有web都對其有讀寫許可權,保證數據一致性; 一、nginx服務 1. 先安裝yum install epel-release -y 2.安裝後發現沒有epel源,重新安裝一 ...
  • C#中遍歷各類數據集合的方法總結: 1.枚舉類型 2.遍歷ArrayList(Queue、Stack) 這裡以string為例,當然ArrayList中的元素可以是任何數據類型,遍歷時須確認ArrayList中的元素都是同一數據類型。 此外遍歷Queue隊列和Stack堆棧的方式與ArrayList ...
  • 三年前寫過基於ConcurrentQueue的非同步隊列,今天在整理代碼的時候發現當時另外一種實現方式-使用BlockingCollection實現,這種方式目前依然在實際項目中使用。關於BlockingCollection的基本使用請查閱MSDN。源碼實現 下麵直接上代碼:(代碼已經放到了我的git ...
  • 抱歉問題解決前沒留截圖,總之就是使用巨集相關的功能都打不開,會彈窗報錯,英文,大意是【一個或多個組件不存在,請嘗試重裝】。 嘗試過: 修複安裝VS2010 按此文照做:https://visualstudioextensions.vlasovstudio.com/2014/02/13/visual-s ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...