Xamarin.Forms 基本系結
Xamarin.Forms數據系結會連結兩個對象之間的一組屬性,其中至少一個通常是使用者介面物件。 這兩個物件稱為「目標」和「來源」:
- 「目標」是資料繫結設定所在的物件 (和屬性)。
- 「來源」是資料繫結參考的物件 (和屬性)。
這項區別有時可能有點令人困惑:在最簡單的情況下,資料會從來源流向目標,這表示目標屬性值會從來源屬性值設定。 不過,在某些情況下,資料可能會從目標流向來源,或是雙向流動。 為了避免混淆,請記住,目標一律是資料繫結設定所在的物件,即使它是提供資料而不是接收資料。
具有繫結內容的繫結
雖然資料繫結通常會完全以 XAML 指定,但在程式碼中看到資料繫結也很有意義。 [基本程式碼繫結] 頁面包含具有 Label
和 Slider
的 XAML 檔案:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicCodeBindingPage"
Title="Basic Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="48"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Slider
設定為 0 到 360 的範圍。 此程式的目的是藉由操作 Slider
而旋轉 Label
。
如果沒有資料繫結,您會將 Slider
的 ValueChanged
事件設定為事件處理常式,以存取 Slider
的 Value
屬性,並將該值設定為 Label
的 Rotation
屬性。 資料繫結會自動執行該作業;已不再需要事件處理常式和其內的程式碼。
您可以在衍生自 BindableObject
的任何類別執行個體上設定繫結,其中包括 Element
、VisualElement
、View
和 View
衍生項目。 繫結一律會在目標物件上設定。 繫結會參考來源物件。 若要設定資料繫結,請使用目標類別的下列兩個成員:
BindingContext
屬性指定來源物件。SetBinding
方法指定目標屬性和來源屬性。
在此範例中,Label
是繫結目標,而 Slider
是繫結來源。 Slider
來源中的變更會影響 Label
目標的旋轉。 資料會從來源流向目標。
BindableObject
所定義的 SetBinding
方法具有 BindingBase
類型的引數,而 Binding
類別衍生自該處,但有 BindableObjectExtensions
類別定義的其他 SetBinding
方法。 基本程式碼繫結範例中的程式碼後置檔案,使用來自此類別的較簡單 SetBinding
延伸模組方法。
public partial class BasicCodeBindingPage : ContentPage
{
public BasicCodeBindingPage()
{
InitializeComponent();
label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
}
}
Label
物件是繫結目標,因此它是這個屬性設定所在的物件,也是對其呼叫方法的物件。 BindingContext
屬性指出繫結來源,也就是 Slider
。
SetBinding
方法在繫結目標上呼叫,但它同時指定目標屬性和來源屬性。 目標屬性指定為 BindableProperty
物件:Label.RotationProperty
。 來源屬性指定為字串,並指出 Slider
的 Value
屬性。
SetBinding
方法顯示其中一個最重要的資料繫結規則:
目標屬性都必須受到可繫結屬性的支援。
此規則暗示,目標物件必須是衍生自 BindableObject
的類別執行個體。 請參閱可繫結屬性一文,以了解可繫結物件和可繫結屬性的概觀。
指定為字串的來源屬性沒有這類規則。 就內部而言,會使用反映來存取實際的屬性。 不過在這個特定案例中,Value
屬性也受到可繫結屬性支援。
程式碼可稍微簡化:RotationProperty
可繫結屬性由 VisualElement
定義,且由 Label
和 ContentPage
繼承,因此在 SetBinding
呼叫中不需要類別名稱:
label.SetBinding(RotationProperty, "Value");
不過,包含類別名稱對於目標物件是很好的提醒。
在您操作 Slider
時,Label
會跟著旋轉:
[基本 XAML 繫結] 頁面與 [基本程式碼繫結] 相同,不同之處在於它會在 XAML 中定義整個資料繫結:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicXamlBindingPage"
Title="Basic XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
如同程式碼,資料繫結是在目標物件上設定,也就是 Label
。 牽涉到兩個 XAML 標記延伸。 這些可藉由大括弧分隔符號立即辨識出來:
- 需要
x:Reference
標記延伸才能參考來源物件,也就是名為slider
的Slider
。 Binding
標記延伸會將Label
的Rotation
屬性連結至Slider
的Value
屬性。
如需 XAML 標記延伸的詳細資訊,請參閱 XAML 標記延伸。 x:Reference
標記延伸受到 ReferenceExtension
類別支援;Binding
受到 BindingExtension
類別支援。 如 XML 命名空間前置詞所指出, x:Reference
是 XAML 2009 規格的一部分,而 Binding
是的一 Xamarin.Forms部分。 請注意,在大括弧內沒有出現引號。
在設定 BindingContext
時,很容易就會忘記 x:Reference
標記延伸。 通常會錯誤地將屬性直接設定為如下的繫結來源名稱:
BindingContext="slider"
但那是不對的。 該標記會將 BindingContext
屬性設定為 string
物件,其字元拼起來是 "slider"!
請注意,來源屬性是使用 BindingExtension
的 Path
屬性所指定,其對應於 Binding
類別的 Path
屬性。
[基本 XAML 繫結] 頁面上所顯示的標記可以簡化:XAML 標記延伸,例如 x:Reference
和 Binding
可以定義「內容屬性」屬性,這對於 XAML 標記延伸表示屬性名稱不需要出現。 Name
屬性是 x:Reference
的內容屬性,而 Path
屬性是 Binding
的內容屬性,這表示它們可以從運算式排除:
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference slider}"
Rotation="{Binding Value}" />
沒有繫結內容的繫結
BindingContext
屬性是資料繫結的重要元件,但是它不一定必要。 來源物件可以改為指定在 SetBinding
呼叫或 Binding
標記延伸中。
這示範於替代程式碼繫結範例中。 XAML 檔案類似於繫結基本程式碼範例,不同之處在於定義了 Slider
來控制 Label
的 Scale
屬性。 因此, Slider
會針對 –2 到 2 的範圍設定 :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeCodeBindingPage"
Title="Alternative Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
程式碼後置檔案會使用 BindableObject
定義的 SetBinding
方法來設定繫結。 引數是 Binding
類別的建構函式:
public partial class AlternativeCodeBindingPage : ContentPage
{
public AlternativeCodeBindingPage()
{
InitializeComponent();
label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
}
}
Binding
建構函式有 6 個參數,因此會使用具名引數來指定 source
參數。 引數是 slider
物件。
執行此程式可能會有點令人驚訝:
左側的 iOS 畫面會顯示頁面第一次出現時的螢幕外觀。 Label
在哪裡?
問題在於 Slider
初始值為 0。 這會導致 Label
的 Scale
屬性也設定為 0,覆寫其預設值 1。 這導致一開始看不到 Label
。 如 Android 螢幕擷取畫面所示,您可以操作 Slider
,讓 Label
再次出現,但其一開始消失是令人不安。
您將在下一篇文章中發現如何藉由從 Scale
屬性的預設值初始化 Slider
來避免這個問題。
注意
VisualElement
類別也會定義 ScaleX
和 ScaleY
屬性,它們可以在水平和垂直方向以不同的方式調整 VisualElement
。
[替代 XAML 繫結] 頁面會顯示完全使用 XAML 的相等繫結:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeXamlBindingPage"
Title="Alternative XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value}" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
現在 Binding
標記延伸已設定兩個屬性,分別是 Source
和 Path
,並以逗號分隔。 如果您偏好,它們可以出現在同一行:
Scale="{Binding Source={x:Reference slider}, Path=Value}" />
Source
屬性設定為內嵌的 x:Reference
標記延伸,這在其他方面具有與設定 BindingContext
相同的語法。 請注意,大括弧中未出現引號,而且必須以逗號分隔兩個屬性。
Binding
標記延伸的內容屬性是 Path
,但只有在它是運算式中的第一個屬性時,才能去除標記延伸的 Path=
部分。 若要去除 Path=
部分,您需要交換兩個屬性:
Scale="{Binding Value, Source={x:Reference slider}}" />
雖然 XAML 標記延伸通常以大括弧分隔,它們也可以用物件項目來表示:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Source="{x:Reference slider}"
Path="Value" />
</Label.Scale>
</Label>
現在 Source
和 Path
屬性是一般的 XAML 屬性:值出現在引號內,且屬性不以逗號分隔。 x:Reference
標記延伸也可以成為物件項目:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Path="Value">
<Binding.Source>
<x:Reference Name="slider" />
</Binding.Source>
</Binding>
</Label.Scale>
</Label>
此語法不常見,但有時候涉及複雜物件時會需要。
到目前為止所顯示的範例,會將 BindingContext
屬性和 Binding
的 Source
屬性設定為 x:Reference
標記延伸,以參考頁面上的另一個檢視。 這兩個屬性都屬於類型 Object
,並可以設定為任何物件,物件內包含適合繫結來源的屬性。
在接下來的文章中,您將發現可以將 BindingContext
或 Source
屬性設定為 x:Static
標記延伸,以便參考靜態屬性或欄位的值;或是設定為 StaticResource
標記延伸,以便參考儲存在資源字典中的物件,或是直接設定為物件,這個物件通常 (但不一定) 是 ViewModel 的執行個體。
BindingContext
屬性也可以設定為 Binding
物件,以便 Binding
的 Source
和 Path
屬性能定義繫結內容。
繫結內容繼承
在本文中,您已了解,您可以使用 BindingContext
屬性或 Binding
物件的 Source
屬性來指定來源物件。 如果同時設定,Binding
的 Source
屬性會優先於 BindingContext
。
BindingContext
屬性有個極重要的特性:
BindingContext
屬性的設定會透過視覺化樹狀結構繼承。
如您所見,這非常方便簡化系結運算式,而且在某些情況下,特別是在Model-View-ViewModel (MVVM) 案例中,這是不可或缺的。
繫結內容繼承範例是繫結內容繼承的簡單示範:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BindingContextInheritancePage"
Title="BindingContext Inheritance">
<StackLayout Padding="10">
<StackLayout VerticalOptions="FillAndExpand"
BindingContext="{x:Reference slider}">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="EndAndExpand"
Rotation="{Binding Value}" />
<BoxView Color="#800000FF"
WidthRequest="180"
HeightRequest="40"
HorizontalOptions="Center"
VerticalOptions="StartAndExpand"
Rotation="{Binding Value}" />
</StackLayout>
<Slider x:Name="slider"
Maximum="360" />
</StackLayout>
</ContentPage>
StackLayout
的 BindingContext
屬性設定為 slider
物件。 這個繫結內容同時由 Label
和 BoxView
繼承,這兩者的 Rotation
屬性都設定為 Slider
的 Value
屬性:
在下一篇文章中,您會看到「繫結模式」如何變更目標和來源物件之間的資料流程。