共用方式為


深入了解 Windows 資料繫結

在本文中,我們將說明位於 Microsoft.UI.Xaml.Data 命名空間之 API 的 Windows 應用程式 SDK 資料繫結功能。

注意

本主題詳細說明資料繫結功能。 如需簡短的實用介紹,請參閱資料繫結概觀

重要 API

簡介

資料繫結可讓您的應用程式 UI 顯示資料,以及選擇性地與該資料保持同步。 資料繫結可讓您將資料與 UI 分開考量,為應用程式建構更簡單的概念模型,以及更好的可讀性、測試性和維護性。

第一次顯示 UI 時,您可以使用資料繫結單純顯示資料來源的值,但不回應這些值中的變更。 這是一種繫結模式,稱為「一次性」,而且適用於在執行階段期間不會變更的值。 或者,您可以選擇「觀察」這些值,並於值變更時更新 UI。 此模式稱為「單向」,適用於唯讀資料。 最後,您可以選擇觀察和更新,讓使用者對 UI 中的值所做的變更自動推送回資料來源。 此模式稱為「雙向」,適用於讀寫資料。 以下列出一些範例。

  • 您可以使用一次性模式將 Image \(英文\) 繫結到目前使用者的相片。
  • 您可以使用單向模式將 ListView \(英文\) 繫結到依報紙區段分組的即時新聞文章集合。
  • 您可以使用雙向模式將 TextBox \(英文\) 繫結到表單中的客戶名稱。

與模式無關,繫結有兩種,通常都在 UI 標記中宣告。 您可以選擇使用 {x:Bind} 標記延伸 (機器翻譯) 或 {Binding} 標記延伸 (機器翻譯), 您甚至可以在同一個應用程式中,甚至是在同一個 UI 元素中混合兩者使用。 {x:Bind} 是 Windows 10 中 UWP 新增的標記,效能更好。 本主題中介紹的所有詳細資訊都適用於這兩種繫結,除非我們明確指示另有規定。

示範 {x:Bind} 的 UWP 範例應用程式

示範 {Binding} 的 UWP 範例應用程式

每個繫結都涉及下列項目

  • 繫結來源: 這是指繫結的資料來源,只要類別中的成員含有您想在 UI 中顯示的值,該類別的執行個體即可作為來源。
  • 繫結目標: 這是 UI 中顯示資料的 FrameworkElementDependencyProperty
  • 繫結物件: 這是指將資料值從來源傳輸到目標,並選擇性地從目標傳回來源的部分。 繫結物件是透過您的 {x:Bind} (機器翻譯) 或 {Binding} (機器翻譯) 標記延伸,於 XAML 載入時間建立的。

在下列各節中,我們將進一步了解繫結來源、繫結目標及繫結物件。 我們將這些區段與將按鈕內容繫結至名為 NextButtonText 的字串屬性範例連結在一起,該屬性屬於名為 HostViewModel 的類別。

繫結來源

以下是一個非常基本的類別實作,我們可用來做為繫結來源。

public class HostViewModel
{
    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}

HostViewModel 的實作及其屬性 NextButtonText,僅適用於一次性繫結。 但是單向和雙向繫結非常常見,在這些繫結類型中,UI 會自動更新以回應繫結來源資料值的變更。 為了讓這些類型的繫結正常運作,您必須將繫結來源變為對於繫結物件是可觀察的。 因此,在我們的範例中,如果我們想要單向或雙向繫結至 NextButtonText 屬性,則在執行階段發生的任何變更都必須讓繫結物件觀察該屬性的值。

其中一種作法是衍生類別,該類別代表來自 DependencyObject 的繫結來源,並透過 DependencyProperty 公開資料值。 這就是 FrameworkElement 變成可觀察的方式。 FrameworkElement 是立即可用良好的繫結來源。

讓類別成為可觀察且對已經有基底類別的類別來說是必要的更輕量型方法,就是實作 System.ComponentModel.INotifyPropertyChanged。 其實際上只涉及實作名為 PropertyChanged 的單一事件。 使用 HostViewModel 的範例如下。

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set
        {
            nextButtonText = value;
            OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

現在 NextButtonText 屬性變為可觀察的。 當您撰寫該屬性的單向或雙向繫結時 (稍後將展示方法),產生的繫結物件會訂閱 PropertyChanged 事件。 產生該事件時,繫結物件的處理常式會收到包含已變更之屬性名稱的引數。 如此繫結物件便知道要取得哪個屬性值並再次讀取。

因此,您不需要實作上述模式許多次,如果您使用的是 C#,則直接衍生自 QuizGame 範例 (在 "Common" 資料夾) 中的 BindableBase 基底類別即可。 以下是其作法範例。

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set { SetProperty(ref nextButtonText, value); }
    }
}

使用 string.Emptynull 引數引發 PropertyChanged 事件,表示應該重新讀取物件上的所有非索引子屬性。 您可以針對特定索引子使用引數 "Item[indexer]" (其中 indexer 是索引值),或針對所有索引子使用值 "Item[]",以引發事件來指出物件上的索引子屬性已變更。

繫結來源可以視為單一物件,其屬性包含資料,或做為物件的集合。 在 C# 程式碼中,您可以一次性繫結到實作 List<T> \(部分機器翻譯\) 的物件,以顯示不會在執行階段變更的集合。 對於可觀察的集合 (在集合中新增和移除專案時觀察),請改為單向繫結至 ObservableCollection<T>。 若要繫結至您自己的集合類別,請使用下表中的指引。

案例 C# (CLR) C++/WinRT
繫結至物件。 可為任何物件。 可為任何物件。
取得繫結物件的屬性變更通知。 物件必須實作 INotifyPropertyChanged \(部分機器翻譯\)。 物件必須實作 INotifyPropertyChanged \(部分機器翻譯\)。
繫結至集合。 List<T> IInspectableIVector,或 IBindableObservableVector。 請參閱 XAML 項目控制項;繫結至 C++/WinRT 集合使用 C++/WinRT 的集合
取得繫結集合的集合變更通知。 ObservableCollection<T> IInspectableIObservableVector。 例如,winrt::single_threaded_observable_vector<T> \(部分機器翻譯\)。
實作支援繫結的集合。 擴充 List<T> 或實作 IListIList<Object>、IEnumerableIEnumerable<Object>。 不支援繫結至泛型 IList<T>IEnumerable<T> Implement IInspectableIVector。 請參閱 XAML 項目控制項;繫結至 C++/WinRT 集合使用 C++/WinRT 的集合
實作可支援集合變更通知的集合。 擴充 ObservableCollection<T> 或實作 (非泛型) IListINotifyCollectionChanged 實作 IInspectableIObservableVector,或 IBindableObservableVector
實作支援增量載入的集合。 擴充 ObservableCollection<T> 或實作 (非泛型) IListINotifyCollectionChanged。 此外,也實作 ISupportIncrementalLoading 實作 IInspectableIObservableVector,或 IBindableObservableVector。 此外,也實作 ISupportIncrementalLoading \(英文\)

您可以使用增量載入,將清單控制項繫結至任意大型資料來源,但仍能達到高效能。 舉例來說,您可以將清單控制項繫結至 Bing 影像查詢結果,但不需要一次載入所有結果, 相反地,您只會立即載入某些結果,並視需要載入其他結果。 若要支援增量載入,您必須在支援集合變更通知的資料來源上實作 ISupportIncrementalLoading \(英文\)。 當資料繫結引擎要求更多資料時,您的資料來源必須提出適當的要求、整合結果,然後傳送適當的通知以更新 UI。

繫結目標

在下列兩個範例中,Button.Content 屬性是繫結目標,而其值設定為宣告繫結物件的標記延伸模組。 首先會顯示 {x:Bind},接著是 {Binding}。 在標記中宣告繫結是常見案例 (便利、可讀取且可使用工具), 但是,如有需要,您可以避免標記和以命令方式 (程式設計方式) 建立 Binding 類別的執行個體。

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

使用 {x:Bind} 宣告的繫結物件

在撰寫 {x:Bind} (機器翻譯) 標記之前,還需要執行一個步驟。 我們需要從代表標記頁面的類別中公開繫結來源類別。 作法是在 MainWindow 視窗類別中新增屬性 (在此案例中為 HostViewModel 類型)。

namespace DataBindingInDepth
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}

如此一來,我們現在可以進一步查看宣告繫結物件的標記。 下列範例會使用稍早在「繫結目標」一節中使用的相同 Button.Content 繫結目標,並顯示其繫結至 HostViewModel.NextButtonText 屬性。

<!-- MainWindow.xaml -->
<Window x:Class="DataBindingInDepth.MainWindow" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Window>

請注意,我們針對 Path 指定的值。 這個值是從視窗本身的角度來解譯,在此案例中,路徑最初會參考我們剛才加入至 MainWindow 頁面的 ViewModel 屬性。 該屬性會傳回 HostViewModel 執行個體,因此我們可以進入該物件,以存取 HostViewModel.NextButtonText 屬性。 我們會指定 Mode,以覆寫預設一次性的 {x:Bind}

Path 屬性支援各種語法選項,以繫結至巢狀屬性、附加屬性,以及整數和字串索引子。 如需詳細資訊,請參閱 Property-path 語法 (機器翻譯)。 繫結至字串索引子可製造繫結至動態屬性的效果,但無須實作 ICustomPropertyProvider。 如需其他設定,請參閱 {x:Bind} 標記延伸

為了說明 HostViewModel.NextButtonText 屬性確實可觀察,請將 Click 事件處理常式新增至按鈕,並更新 HostViewModel.NextButtonText 的值。 建置、執行,然後按一下按鈕以查看按鈕 Content 更新的值。

// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    ViewModel.NextButtonText = "Updated Next button text";
}

注意

TextBox 失去焦點時 (不是在使用者每次按下按鍵之後),即會將對於 TextBox.Text 的變更傳送到雙向繫結來源。

DataTemplate 與 x:DataType

DataTemplate 內 (無論是做為專案範本、內容範本或標頭範本),Path 的值不會在視窗的內容中解譯,而是在範本化的資料物件內容中解譯。 在資料範本中使用 {x:Bind} 時,以便在編譯階段驗證其繫結 (並為其產生有效率的程式碼),DataTemplate 需要使用 x:DataType 宣告其資料物件的類型。 下列範例可用來做為繫結至 SampleDataGroup 物件集合之項目控制項的 ItemTemplate

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

Path 中的弱式類型物件

例如,假設您有名為 SampleDataGroup 的類型,其會實作名為 Title 的字串屬性。 而且您有屬性 MainWindow.SampleDataGroupAsObject,其類型為 object,但實際上會傳回 SampleDataGroup的實例。 繫結的 <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> 會導致編譯錯誤,因為在 object 類型上找不到 Title 屬性。 解決此問題的方法是將轉換新增至您的 Path 語法,如下所示:<TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>。 以下是另一個範例,其中 Element 宣告為 object ,但實際上是 TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>。 而轉換可解決此問題:<TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>

如果您的資料會以非同步方式載入

支援 {x:Bind} 的程式碼會在視窗的部分類別中於編譯階段產生。 您可以在 obj 資料夾中找到這些檔案,名稱如 (適用於 C#) <view name>.g.cs。 產生的程式碼包含視窗的 載入事件的處理常式,而該處理常式會在代表視窗繫結的已產生類別上呼叫 Initialize 方法。 Initialize 接著會呼叫 Update,開始在繫結來源與目標之間移動資料。 Loading 會在視窗或使用者控制項的第一個量值階段之前引發。 因此,如果您的資料是以非同步方式載入,在呼叫 Initialize 時可能無法就緒。 因此,載入資料之後,您可以呼叫 this.Bindings.Update(); 來強制初始化單次繫結。 如果您只需要針對以非同步方式載入的資料使用一次性繫結,則將其初始化的方法會比讓其擁有單向繫結並接聽變更來得經濟實惠。 如果您的資料未進行精細的變更,且如果可能會更新為特定動作的一部分,則您可以進行一次性繫結,並隨時以呼叫 Update 強制手動更新。

注意

{x:Bind} 不適合用於晚期繫結案例,例如瀏覽 JSON 物件或鴨子型別的字典結構。 「鴨子型別」是以屬性名稱的詞法相符項目為基礎的弱式形式類型 (「如果其走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那其就是鴨子」)。 使用鴨子型別,Age 屬性的繫結同樣可滿足 PersonWine 物件 (假設那些類型都有 Age 屬性)。 在這些情況下,請使用 {Binding} 標記延伸模組。

使用 {Binding} 宣告的繫結物件

根據預設,{Binding} 假設您繫結至標記視窗的 DataCoNtext。 因此,我們將視窗的 DataContext 設定為繫結來源類別的執行個體 (在此案例中為 HostViewModel 類型)。 下列範例顯示宣告繫結物件的標記。 我們使用稍早在「繫結目標」一節中使用的相同 Button.Content 繫結目標,並繫結至 HostViewModel.NextButtonText 屬性。

<Window xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Window.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Window.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    viewModelInDataContext.NextButtonText = "Updated Next button text";
}

請注意,我們針對 Path 指定的值。 這個值會在視窗 DataCoNtext 的內容中解譯,在此範例中,這個範例會設定為 HostViewModel 的執行個體。 路徑會參考 HostViewModel.NextButtonText 屬性。 我們可以省略 Mode,因為此處預設 {Binding} 為單向運作。

UI 元素 DataCoNtext 的預設值是其父系的繼承值。 您當然可以藉由明確設定 DataContext 來覆寫該預設值,依預設會繼承子系。 當您想要有多個使用相同來源的繫結時,在元素上明確設定 DataContext 很實用。

繫結物件具有 Source 屬性,預設為宣告繫結之 UI 元素的 DataCoNtext。 您可以藉由在繫結上明確設定 SourceRelativeSourceElementName 來覆寫此預設值 (如需詳細資訊,請參閱 {Binding})。

DataTemplate \(英文\) 內,DataContext \(英文\) 設定為樣板化的資料物件。 下列範例可用作繫結至具有名為 TitleDescription 字串屬性的任何類型集合的項目控制項的 ItemTemplate

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

注意

根據預設,當 TextBox 失去焦點時,TextBox.Text 的變更會傳送到雙向繫結來源。 若要在每個使用者按鍵輸入之後傳送變更,請將 UpdateSourceTrigger 設定為標記中繫結上的 PropertyChanged。 您也可以將 UpdateSourceTrigger 設定為 Explicit,完全控制何時將變更傳送至來源。 接著,您可以在文字方塊中處理事件 (通常是 TextBox.TextChanged),呼叫目標上的 GetBindingExpression ,以取得 BindingExpression 物件,最後呼叫 BindingExpression.UpdateSource,以程式設計方式更新資料來源。

Path (機器翻譯) 屬性支援使用多種語法選項繫結至巢狀屬性、附加屬性,以及整數和字串索引子。 如需詳細資訊,請參閱 Property-path 語法 (機器翻譯)。 繫結至字串索引子可製造繫結至動態屬性的效果,但無須實作 ICustomPropertyProviderElementName (機器翻譯) 屬性對元素之間的繫結很實用。 RelativeSource (機器翻譯) 屬性有數種用途,其中一個是作為 ControlTemplate (機器翻譯) 內範本繫結的更強大替代屬性。 如需瞭解其他設定,請參閱 {Binding} 標記延伸 (機器翻譯) 與 Binding (機器翻譯) 類別。

如果來源和目標的類型不相同,該怎麼辦?

如果您想要根據布林值屬性的值控制 UI 元素的可見度,或以數值範圍或趨勢函式指定的色彩呈現 UI 元素,抑或是在預期字串的 UI 元素屬性中顯示日期和/或時間值,就必須轉換值的類型。 在某些情況下,正確的解決方案是從繫結來源類別公開正確型別的另一個屬性,並確認轉換邏輯在該處已完成封裝且可進行測試。 但是當您擁有大量來源和目標屬性或其組合時,這種做法既不夠彈性,也不可調整。 在此情況下,您有幾個選項:

  • 如果使用 {x:Bind},您可以直接繫結至函式以執行該轉換
  • 或者,您可以指定值轉換器,這是設計用來執行轉換的物件

值轉換器

以下是適合單次或單向系結的值轉換器,可將 DateTime 值轉換成包含月份的 string 值。 這個類別會實作 IValueConverter (機器翻譯)。

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisDate = (DateTime)value;
        int monthNum = thisDate.Month;
        string month;
        switch (monthNum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

以下方法可在繫結物件標記中取用該值轉換器。

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

如果繫結定義 Converter 參數,繫結引擎會呼叫 ConvertConvertBack 方法。 從來源傳遞資料時,繫結引擎會呼叫 Convert,並將傳回的資料傳遞至目標。 從目標傳遞資料時 (針對雙向繫結),繫結引擎會呼叫 ConvertBack 並將傳回的資料傳遞至來源。

轉換器也有選用的參數:ConverterLanguage \(英文\) 可以指定轉換中要使用的語言,而 ConverterParameter \(英文\) 可允許傳遞轉換邏輯的參數。 如需使用轉換器參數的範例,請參閱 IValueConverter (機器翻譯)。

注意

如果轉換中出現錯誤,請不要擲回例外狀況。 相反地,傳回 DependencyProperty.UnsetValue,這會停止資料傳輸。

若要在無法解析繫結來源時顯示要使用的預設值,請在標記中設定繫結物件的 FallbackValue 屬性。 這很適合用來處理轉換和格式化錯誤。 繫結至可能不存在於異質類型繫結集合中所有物件上的來源屬性,也可以派上用場。

如果您將文字控制項繫結至不是字串的值,資料繫結引擎會將該值轉換成字串。 如果值為參考類型,資料繫結引擎會呼叫 ICustomPropertyProvider.GetStringRepresentationIStringable.ToString (如果有的話),否則會呼叫 Object.ToString 來擷取字串值。 不過請注意,繫結引擎會忽略隱藏基底類型實作的任何 ToString 實作。 子類別實作應該改為覆寫基底類型 ToString 方法。 同樣地,在原生語言中,所有受控物件似乎都會實作 ICustomPropertyProviderIStringable。 不過,所有對 GetStringRepresentationIStringable.ToString 的呼叫都會路由傳送至 Object.ToString 或該方法的覆寫,而且永遠不會路由傳送至隱藏基底類型實作的新 ToString 實作。

注意

Windows Community Toolkit 提供 BoolToVisibilityConverter。 轉換器會將 true 對應至 Visible 列舉值,並將 false 對應至 Collapsed,讓您可以繫結 Visibility 屬性至布林值,而不需要建立轉換器。 若要使用轉換器,您的專案必須新增 CommunityToolkit.WinUI.Converters NuGet 套件。

{x:Bind} 中的函式繫結

{x:Bind} 可讓繫結路徑中的最後一個步驟成為函式。 這可用來執行轉換,以及執行相依於多個屬性的繫結。 請參閱 x:Bind 中的函式

元素繫結

您可以將一個 XAML 元素的屬性繫結至另一個 XAML 元素的屬性。 以下是其在標記中的作法範例。

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

具有 {x:Bind} 的資源字典

{x:Bind} 標記延伸相依於程式碼產生,因此需要程式碼後置檔案,其中包含呼叫 InitializeComponent 的建構函式 (以初始化產生的程式碼)。 您可以藉由具現化其類型來重複使用資源字典 (以便呼叫 InitializeComponent),而不是參考其檔名。 以下是如果您有現有資源字典,且想要在其中使用 {x:Bind},該怎麼做的範例。

<!-- TemplatesResourceDictionary.xaml -->
<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>
// TemplatesResourceDictionary.xaml.cs
using Microsoft.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}
<!-- MainWindow.xaml -->
<Window x:Class="ExampleNamespace.MainWindow"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Window.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
</Window>

事件繫結與 ICommand

{x:Bind} (機器翻譯) 支援稱為「事件繫結」的功能。 透過這項功能,您可以使用繫結來指定事件的處理常式,這是除了使用程式碼後置檔案上方法處理事件以外的選項。 假設您在 MainWindow 類別上有 ListViewDoubleTapped 事件處理常式。

public sealed partial class MainWindow : Window
{
    ...
    public void ListViewDoubleTapped()
    {
        // Handle double-tapped logic
    }
}

然後,您可以將 ListView 的 DoubleTapped 事件繫結至 MainWindow 上的方法。

<ListView DoubleTapped="{x:Bind ListViewDoubleTapped}" />

超載方法無法使用這項技術來處理事件。 此外,如果處理事件的方法具有參數,則它們必須分別從事件的所有參數類型指派。 在此情況下,ListViewDoubleTapped 未超載,且沒有參數 (但即使它取用了兩個 object 參數,仍然有效)。

事件繫結技術類似於實作和取用命令 (命令是傳回實作 ICommand 介面的物件的屬性)。 {x:Bind}{Binding} 使用命令。 因此,您不需要多次實作命令模式,您可以使用 DelegateCommand 協助程式類別,您可以在 QuizGame UWP 範例中 (位於「Common」資料夾中)。

繫結至資料夾或檔案集合

您可以使用 Windows.Storage (機器翻譯) 命名空間中的 API 擷取封裝的 Windows 應用程式 SDK 應用程式中的資料夾和檔案資料。 不過,各種 GetFilesAsyncGetFoldersAsyncGetItemsAsync 方法不會傳回適合繫結至清單控制項的值。 相反地,您必須繫結至 FileInformationFactory 類別的 GetVirtualizedFilesVectorGetVirtualizedFoldersVector,和 GetVirtualizedItemsVector 方法的傳回值。 下列來自 StorageDataSource 和 GetVirtualizedFilesVector UWP 範例的程式碼範例顯示一般使用模式。 請記得在應用程式套件資訊清單中宣告 picturesLibrary 功能,並確認圖片媒體櫃資料夾中有圖片。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

您通常會使用此方法來建立檔案和資料夾資訊的唯讀檢視。 您可以建立檔案和資料夾屬性的雙向繫結,例如讓使用者在音樂檢視中評分歌曲。 不過,在您呼叫適當的 SavePropertiesAsync 方法之前,不會保存任何變更 (例如,MusicProperties.SavePropertiesAsync)。 當項目失去焦點時,您應該認可變更,因為這會觸發選取重設。

請注意,使用這項技術的雙向繫結僅適用於索引位置,例如 Music。 您可以呼叫 folderInformation.GetIndexedStateAsync 方法來判斷位置是否已編制索引。

另請注意,虛擬化向量可以針對某些項目傳回 null,然後再填入其值。 例如,您應該先檢查 null,再使用繫結至虛擬化向量的清單控制項 SelectedItem 值,或改用 SelectedIndex

繫結至依索引鍵分組的資料

如果您取得一般項目集合 (例如書籍,以 BookSku 類別表示) 且使用常見的屬性作為索引鍵將項目分組 (例如 BookSku.AuthorName 屬性),則結果稱為分組資料。 當您將資料分組時,它不再是一般集合。 群組資料是群組物件的集合,其中每個群組物件都有:

  • 索引鍵,以及
  • 項目的集合,其屬性符合該索引鍵。

若要再次接受書籍範例,依作者姓名對書籍進行分組的結果將得出一個作者姓名群組的集合,其中每個群組都有:

  • 索引鍵 (作者名稱),以及
  • BookSku 的集合,其 AuthorName 屬性符合群組的索引鍵。

一般而言,若要顯示集合,您可以將專案控制項的 ItemsSource 直接繫結至傳回集合的屬性 (例如 ListViewGridView)。 如果該集合為一般項目集合,就不需要執行任何特殊動作, 但是,如果它是群組物件的集合 (如同繫結至群組資料時),則您需要中繼物件的服務,稱為 CollectionViewSource,其位於專案控制項與繫結來源之間。 您可以將 CollectionViewSource 繫結至傳回群組資料的屬性,並將專案控制項繫結至 CollectionViewSourceCollectionViewSource 的額外加值是它會追蹤目前的項目,因此您可以將多個項目控制項繫結至相同的 CollectionViewSource,以保持同步。 您也可以透過 CollectionViewSource.View 屬性所傳回之物件的 ICollectionView.CurrentItem 屬性,以程式設計方式存取目前的項目。

若要啟動 CollectionViewSource 的群組功能,請將 IsSourceGrouped 設為 true。 您是否也需要設定 ItemsPath 屬性,取決於您撰寫群組物件的方式。 有兩種方式可以撰寫群組物件:「is-a-group」模式,以及「has-a-group」模式。 在「is-a-group」模式中,群組物件衍生自集合類型 (例如,List<T>),因此群組物件實際上是項目群組本身。 使用此模式時,您不需要設定 ItemsPath。 在「has-a-group」模式中,群組物件具有集合類型的一或多個屬性 (例如,List<T>),因此群組會以屬性形式「具有」項目群組 (或數個屬性形式的多個項目群組)。 使用此模式時,您必須將 ItemsPath 設定為包含項目群組的屬性名稱。

下列範例說明「has-a-group」模式。 視窗類別具有名為 DataCoNtext 的屬性,它會傳回檢視模型的執行個體。 CollectionViewSource 繫結至檢視模型的 Authors 屬性 (Authors 是群組物件的集合),同時指定它是包含群組專案的 Author.BookSkus 屬性。 最後,GridView 繫結至 CollectionViewSource,並已定義其群組樣式,使其可以在群組中轉譯項目。

<Window.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Window.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

您可以使用兩種方式之一來實作「is-a-group」模式。 其中一種方式是撰寫您自己的群組類別。 從 List<T> 衍生類別 (其中 T 是專案的類型)。 例如: public class Author : List<BookSku> 。 第二種方式是使用 LINQ 運算式,從 BookSku 項目的屬性值動態建立群組物件 (和群組類別)。 這種方法只會維護一般項目清單,並即時將項目分組,通常是從雲端服務存取資料的應用程式會採用的方法, 就好像可以彈性地依作者或內容類型將書籍分組,而不需要 AuthorGenre 等特殊群組類別。

下列範例說明使用 LINQ 的「is-a-group」模式。 這次我們依內容類型將書籍分組,並在群組標頭顯示內容類型名稱, 這是以群組 Key 值參考的「索引鍵」屬性路徑來表示。

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (genres == null)
        {
            genres = from book in bookSkus
                     group book by book.genre into grp
                     orderby grp.Key
                     select grp;
        }
        return genres;
    }
}

請記住,將 {x:Bind} 與資料範本一起使用時,我們需要藉由設定 x:DataType 值來指出繫結的類型。 如果類型是泛型,則無法在標記中表示,因此我們需要在群組樣式標頭範本中使用 {Binding}

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

SemanticZoom 控制項是讓使用者檢視及瀏覽群組資料的絕佳方式。 Bookstore2 UWP 範例應用程式會說明如何使用 SemanticZoom。 在該應用程式中,您可以檢視依作者分組的書籍清單 (放大檢視),也可以縮小以查看作者的跳躍清單 (縮小檢視)。 跳躍清單的瀏覽方式比捲動書籍清單更快。 放大和縮小檢視實際上是 ListViewGridView 繫結至相同 CollectionViewSource 的控制項。

SemanticZoom 的圖例

當您繫結至類別內的階層式資料時 (例如類別內的子類別),您可以選擇使用一系列項目控制項在 UI 中顯示階層式層級。 一個項目控制項中的選取項目會決定後續項目控制項的內容。 您可以將每個清單繫結至自己的 CollectionViewSource,並將 CollectionViewSource 執行個體繫結在鏈結中,藉此讓清單保持同步。 這稱為主要/詳細資料 (或清單/詳細資料) 檢視。 如需詳細資訊,請參閱如何繫結至階層式資料並建立主要/詳細資料檢視

診斷和偵錯資料繫結問題

您的繫結標記包含屬性的名稱 (在 C# 中有時還包含欄位和方法), 因此,若您重新命名屬性,也必須變更參考該屬性的所有繫結。 忘記這麼做會造成典型的資料繫結錯誤範例,且您的應用程式將無法編譯或無法正確執行。

{x:Bind} 所建立的繫結物件和 {Binding} 基本上功能相等。 但是 {x:Bind} 具有繫結來源的類型資訊,而且會在編譯階段產生原始程式碼。 使用 {x:Bind},您可得到與其餘程式碼相同的問題偵測。 這包括繫結運算式的編譯階段驗證,以及透過在作為頁面部分類別產生的原始程式碼中設定中斷點進行偵錯。 這些類別位於您 obj 資料夾中的檔案,名稱格式為 <view name>.g.cs (就 C# 而言)。 如果繫結發生問題,請在 Microsoft Visual Studio 偵錯工具中開啟 [發生未處理的例外狀況時中斷]。 偵錯工具會在該時間點中斷執行,然後您可以偵錯發生錯誤的原因。 {x:Bind} 所產生的程式碼會遵循繫結來源節點圖形中每個部分的相同模式,而且您可以使用呼叫堆疊視窗中的資訊,以協助判斷導致問題的呼叫順序。

{Binding} 沒有繫結來源的類型資訊。 但在附加偵錯工具的情況下執行您的應用程式時,所有繫結錯誤都會顯示在 Visual Studio 的 [輸出] 和 [XAML 繫結失敗] 視窗中。 如需在 Visual Studio 中偵錯繫結錯誤的詳細資訊,請參閱 XAML 資料繫結診斷

在程式碼中建立繫結

注意

此節只適用於 {Binding} \(部分機器翻譯\),因為您不能在程式碼中建立 {x:Bind} \(部分機器翻譯\) 繫結。 不過,您可以使用 DependencyObject.RegisterPropertyChangedCallback 達成 {x:Bind} 的一些相同優點,這可讓您在任何相依性屬性上註冊變更通知。

您也可以使用程序性程式碼而非 XAML,將 UI 元素連線到資料。 若要這樣做,請建立新的 Binding (機器翻譯) 物件,設定適當的屬性,然後呼叫 FrameworkElement.SetBinding (機器翻譯) 或 BindingOperations.SetBinding (機器翻譯)。 當您想要在執行階段選擇繫結屬性值,或在多個控制項之間共用單一繫結時,以程式設計方式建立繫結會很有用。 不過請注意,呼叫 SetBinding 之後,您無法變更繫結屬性值。

以下範例示範如何在程式碼中實作繫結。

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
var textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
var binding = new Binding { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);

{x:Bind} 與 {Binding} 的功能比較

功能 {x:Bind} 與 {Binding} 備註
Path 為預設屬性 {x:Bind a.b.c}
-
{Binding a.b.c}
Path 屬性 {x:Bind Path=a.b.c}
-
{Binding Path=a.b.c}
x:Bind 中,Path 預設根目錄在 Window,而不是 DataCoNtext。
索引編製程式 {x:Bind Groups[2].Title}
-
{Binding Groups[2].Title}
繫結至傳回集合中的指定項目。 僅支援以整數為基礎的索引。
附加屬性 {x:Bind Button22.(Grid.Row)}
-
{Binding Button22.(Grid.Row)}
附加屬性會使用括弧加以指定。 如果屬性未在 XAML 命名空間中宣告,請在它前面加上 xml 命名空間,該命名空間應該對應至文件前端的程式碼命名空間。
轉型 {x:Bind groups[0].(data:SampleDataGroup.Title)}
-
{Binding} 不需要。
轉換是使用括弧來指定。 如果屬性未在 XAML 命名空間中宣告,請在它前面加上 xml 命名空間,該命名空間應該對應至文件前端的程式碼命名空間。
轉換器 {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}}
轉換器必須於 Window/Control/ResourceDictionary 的根目錄或 App.xaml 中宣告。
ConverterParameter、ConverterLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
轉換器必須於 Window/Control/ResourceDictionary 的根目錄或 App.xaml 中宣告。
TargetNullValue {x:Bind Name, TargetNullValue=0}
-
{Binding Name, TargetNullValue=0}
當繫結運算式的分葉為 Null 時使用。 針對字串值使用單引號。
FallbackValue {x:Bind Name, FallbackValue='empty'}
-
{Binding Name, FallbackValue='empty'}
當繫結路徑的任何部分 (分葉除外) 為 Null 時使用。
ElementName {x:Bind slider1.Value}
-
{Binding Value, ElementName=slider1}
使用 {x:Bind},將繫結至欄位;根據預設,Path 根目錄在 Window,因此可以透過其欄位存取任何具名元素。
RelativeSource: Self <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />
-
<Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... />
使用 {x:Bind},為元素命名,並在 Path 中使用其名稱。
RelativeSource: TemplatedParent {x:Bind} 不需要
-
{Binding <path>, RelativeSource={RelativeSource TemplatedParent}}
使用 {x:Bind} 時,ControlTemplate 上的 TargetType 表示繫結至範本父系。 針對 {Binding},一般範本繫結可以用在控制項範本中滿足大部分的用途。 但需要使用轉換器或雙向系結時請使用 TemplatedParent
Source {x:Bind} 不需要
-
<ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/>
針對 {x:Bind},您可以直接使用已命名的元素,使用屬性或靜態路徑。
[模式] {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
Mode 可以是 OneTimeOneWayTwoWay{x:Bind} 預設為 OneTime{Binding} 預設為 OneWay
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger 可以是 DefaultLostFocusPropertyChanged{x:Bind} 不支援 UpdateSourceTrigger=Explicit{x:Bind} 針對所有案例使用 PropertyChanged 行為,只有 TextBox.Text 使用 LostFocus 行為。

另請參閱