生命周期
在 Android 上,若主活動的 [Activity()]
屬性缺少 ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation
,旋轉時及首次啟動應用程序時,將調用 OnStart
方法。
數據綁定
數據綁定在用戶界面和應用程序之間建立連接。
官網:https://docs.microsoft.com/zh-cn/xamarin/xamarin-forms/app-fundamentals/data-binding/
原理參考:Xamarin.From中的Data binding(數據綁定)(一)
基本綁定
數據綁定連接兩個對象,即源和目標。 源對象提供數據, 目標對象使用(並經常顯示)來自源對象的數據。 例如, Editor
(目標對象) 通常會將Text
屬性綁定到源對象中string
的屬性。 下圖說明了這種綁定關系:
數據綁定的主要優點是讓你無需再擔心視圖和數據源之間的數據同步。 底層的綁定框架源會將源對象中的更改自動推送到目標對象,且目標對象中的更改可選擇性地推送回源對象。
建立數據綁定的過程分為兩個步驟:
- 目標對象的
BindingContext
屬性必須設置為源。 - 必須在目標和源之間建立綁定。
實現綁定有兩種方式:僅在xaml文件中或者僅在cs中,每種又分使用BindingContext與否。
1、在 XAML 中設置【常用方式】
不使用BingingContext:通過使用 Binding
標記擴展實現。

<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>
使用BingingContext:

<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>
XAML標記擴展(例如x:Reference和Binding)可以定義內容屬性特性,對於XAML標記擴展,這意味着不需要顯示屬性名稱。
Name屬性是x:Reference的content屬性,Path屬性是Binding的content屬性,這意味着可以從表達式中消除它們:

<Label Text="TEXT" FontSize="80" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" BindingContext="{x:Reference slider}" Rotation="{Binding Value}" />
同時,源屬性是由 BindingExtension
的 Path
屬性指定的,它對應於 Binding
類的 Path
屬性(Binding
標記擴展的內容屬性是 Path
,但是標記擴展的 Path=
部分只有在它是表達式中的第一個屬性時才能被刪除。)
總結最精簡做法:

<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 Value, Source={x:Reference slider}}" /> <Slider x:Name="slider" Minimum="-2" Maximum="2" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>
小結:
將Binding的Source屬性或者BindingContext屬性設置為x:Reference標記擴展,以引用頁面上的另一個視圖(源對象)。 這兩個屬性的類型為Object,可以將它們設置為任何包含適合於綁定源的屬性的對象。
如果兩者都已設置,則 Binding
的 Source
屬性優先於 BindingContext
。
2、只在cs中設定
需要設置以下
BindingContext
屬性指定源對象。SetBinding
方法指定目標屬性和源屬性。
public BasicCodeBindingPage()
{
InitializeComponent();
label.BindingContext = slider; label.SetBinding(Label.RotationProperty, "Value"); }
綁定上下文繼承
父布局中定義了源對象,子控件中都可以使用。

<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>
綁定模式
默認綁定模式
使用 BindingMode
枚舉的成員指定綁定模式:
- Default
TwoWay
– 數據在源和目標之間雙向傳輸OneWay
– 數據從源到目標單向傳輸OneWayToSource
– 數據從目標到源單向傳輸OneTime
– 只有在BindingContext
更改時,數據才從源到目標單向傳輸(Xamarin.Forms 3.0 新增功能)
每個可綁定屬性都有一個默認綁定模式,該模式在創建可綁定屬性時進行設置,並且可從 BindableProperty
對象的 DefaultBindingMode
屬性中獲得。 此默認綁定模式指示該屬性是數據綁定目標時有效的模式。
大多數屬性(如 Rotation
、Scale
和 Opacity
)的默認綁定模式都是 OneWay
。 如果這些屬性是數據綁定目標時,則從源設置目標屬性。
但是,Slider
的 Value
屬性的默認綁定模式為 TwoWay
。 這意味着,如果 Value
屬性是數據綁定目標時,則通常從源設置目標,但源也可從目標設置。 這就是允許從初始 Opacity
值設置 Slider
的原因。
【<!--Slider.Value是twoWay模式,它的Value 是從Label的opacity處來,默認是1。 同時Label的opacity也是1,改變value時,Label的opacity也會改變-->】
這種雙向綁定似乎會創建一個無限循環,但這種情況不會發生。 除非屬性實際發生變化,否則可綁定屬性不會發出屬性更改的信號,這樣可以避免無限循環。
1、雙向綁定
大多數可綁定屬性的默認綁定模式都是 OneWay
,但以下屬性的默認綁定模式為 TwoWay
:
DatePicker
的Date
屬性Editor
、Entry
、SearchBar
和EntryCell
的Text
屬性ListView
的IsRefreshing
屬性MultiPage
的SelectedItem
屬性Picker
的SelectedIndex
和SelectedItem
屬性Slider
和Stepper
的Value
屬性Switch
的IsToggled
屬性SwitchCell
的On
屬性TimePicker
的Time
屬性
這些特定屬性被定義為 TwoWay
,理由非常充分:
當數據綁定與模型-視圖-視圖模型 (MVVM) 應用程序體系結構一起使用時,ViewModel 類是數據綁定源,而由 Slider
等視圖組成的 View 則是數據綁定目標。 MVVM 綁定更類似於“ReverseBindingPage”示例,而不是之前示例中的綁定 。 你很可能會想要使用 ViewModel 中相應屬性的值來初始化頁面上的每個視圖,但是視圖中的更改也將影響ViewModel屬性。
默認綁定模式為 TwoWay
的屬性 是最有可能在 MVVM 方案中使用的屬性。
2、OneWayToSource綁定
只讀可綁定屬性的默認綁定模式為 OneWayToSource【目標到源】
。
只有一個讀/寫可綁定屬性的默認綁定模式為 OneWayToSource
:
ListView
的SelectedItem
屬性
其基本原理是,對 SelectedItem
屬性的綁定(改變)應該導致 重新設置綁定源。
3、一次性綁定
許多屬性(包括 Entry
的 IsTextPredictionEnabled
屬性)都具有 OneTime
的默認綁定模式。
只有在綁定上下文更改時,才會更新綁定模式為 OneTime
的目標屬性。 對於這些目標屬性的綁定,該模式簡化了綁定基礎結構,因為不必監視源屬性中的更改。
ViewModels and 屬性更改通知
ViewModel的使用,參考:C#使用Xamarin開發可移植移動應用(4.進階篇MVVM雙向綁定和命令綁定)附源碼
Data binding的核心思想:Data binding總是有一個源(source)和一個目標(target);源是某個對象的一個屬性,通常會在運行期間發生變化;當這個屬性變化時,Data binding將自動將這一變化更新到目標上,即另一個對象的一個屬性上。

有一點特別重要:Data binding中的目標必須是一個BindableProperty對象;
在Xamarin.Form中,大多數的View、Layout和Page都是BindableObject對象。
對源則沒有這樣的要求,因此源可以是一個普通的C#屬性;ViewModel 類是數據綁定源,在ViewModel中定義C#屬性。。
但在一般的數據綁定中,源的變化應當引起目標的變化,這種變化需要某種通知機制進行傳遞。這里有一個現成的機制—— INotifyPropertyChanged接口。
INotifyPropertyChanged接口的內容非常簡單,僅僅定義了個PropertyChanged事件,這個事件在屬性變化時會被觸發;
public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; }
因此,Data binding中的源必須實現INotifyPropertyChanged接口;
而BindableObject正好就實現了INotifyPropertyChanged接口,因此,只需將源定義成BindableObject對象即可,這樣它既可以成為源,也可以成為目標。
而只實現INotifyPropertyChanged就只能作為源,不過,這么做也有好處,就是在實現上更簡單。
覆蓋默認的綁定模式
如果目標屬性的默認綁定模式不適合特定的數據綁定,則可以通過將 Binding
的 Mode
屬性(或 Binding
標記擴展的 Mode
屬性)設置為 BindingMode
枚舉的其中一個成員來替代它。
但是,將 Mode
屬性設置為 TwoWay
並不總是像你預期的那樣有效。
替換掉默認的綁定模式的例子:SampleSettingsViewModel
一種非常有用的應用程序涉及 ListView 的 SelectedItem 屬性。,其默認綁定模式為 OneWayToSource(目標到源)
我們將其改為TwoWay。
字符串格式設置
在代碼中顯示字符串時,最強大的工具是靜態 String.Format
方法。 格式設置字符串包括特定於各種類型的對象的格式代碼,也可以包括其他文本以及正在進行格式設置的值。 有關字符串格式的詳細信息,請參閱設置 .NET 中類型的格式一文。
<Slider x:Name="slider" /> <Label Text="{Binding Source={x:Reference slider}, Path=Value, StringFormat='The slider value is {0:F2}'}" />
Path的值會去替換占位符{0}。
綁定路徑
前面將Binding
類的 Path
屬性(或 Binding
標記擴展的 Path
屬性)已設置為單個屬性。 實際上,可以將 Path
設置為“子屬性”(屬性的屬性),也可以設置為集合的成員 。
示例中有一些很有用的說明。
如果綁定路徑中的屬性沒有實現 INotifyPropertyChanged
,那么對該屬性的任何更改都將被忽略。 一些更改可能會使綁定路徑完全無效,所以應只在屬性和子屬性字符串永遠不會失效的情況下才使用這種技術。
綁定值轉換器
- 說明
當源和目標屬性都是同一類型,或當一個類型可以隱式轉換為另一種類型時,這類傳遞都是非常簡單的。 如果不是這種情況,則必須執行類型轉換。
字符串格式設置一文已介紹如何使用數據綁定的 StringFormat
屬性將任意類型轉換為字符串 。 對於其他類型的轉換,需要在類中編寫一些專門的代碼以實現 IValueConverter
接口。 (通用 Windows 平台在 Windows.UI.Xaml.Data
命名空間中包含一個名為 IValueConverter
的類似的類,但此 IValueConverter
在 Xamarin.Forms
命名空間中。)實現 IValueConverter
的類被稱為“值轉換器”,但它們通常也被稱為“綁定轉換器”或“綁定值轉換器” 。
- 實例
想要定義源屬性為類型 int
但目標屬性為 bool
的數據綁定。 想要此數據綁定在整數源等於 0 時生成 false
值,在其他情況則生成 true
。

public class IntToBoolConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (int)value != 0; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return (bool)value ? 1 : 0; } }
將此類的實例設為 Binding
類的 Converter
屬性,此類即成為數據綁定的一部分。
當數據在 OneWay
或 TwoWay
綁定中由源移動到目標時,將調用 Convert
方法。 value
是來自數據綁定源的對象或值,該方法必須返回數據綁定目標類型的值。 此處所示的方法將 value
參數強制轉換為 int
,然后將其與 0 比較並得到 bool
返回值。
當數據在 TwoWay
或 OneWayToSource
綁定中由目標移動到源時,將調用 ConvertBack
方法。 ConvertBack
執行相反的轉換:它假定 value
參數是來自目標的 bool
,然后將其轉換為源的 int
返回值。
xaml中使用:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:DataBindingDemos" x:Class="DataBindingDemos.EnableButtonsPage" Title="Enable Buttons"> <ContentPage.Resources> <ResourceDictionary> <local:IntToBoolConverter x:Key="intToBool" /> </ResourceDictionary> </ContentPage.Resources> <StackLayout Padding="10, 0"> <Entry x:Name="entry1" Text="" Placeholder="enter search term" VerticalOptions="CenterAndExpand" /> <Button Text="Search" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" IsEnabled="{Binding Source={x:Reference entry1}, Path=Text.Length, Converter={StaticResource intToBool}}" /> <Entry x:Name="entry2" Text="" Placeholder="enter destination" VerticalOptions="CenterAndExpand" /> <Button Text="Submit" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" IsEnabled="{Binding Source={x:Reference entry2}, Path=Text.Length, Converter={StaticResource intToBool}}" /> </StackLayout> </ContentPage>
綁定回退
有時數據綁定會失敗,因為無法解析綁定源,或者因為綁定成功但返回 null
值。 雖然可以使用值轉換器或其他附加代碼處理這些情況,但是通過定義在綁定過程失敗時要使用的回退值,可以使數據綁定更加可靠。 這可以通過定義綁定表達式中的 FallbackValue
和 TargetNullValue
屬性來實現。 因為這些屬性位於 BindingBase
類中,它們可以與綁定、編譯綁定和 Binding
標記擴展一起使用。
命令接口
在“模型-視圖-視圖模型”(即 MVVM)體系結構中,數據綁定是在 ViewModel(通常是派生自 INotifyPropertyChanged
的類)中的屬性和視圖(通常為 XAML 文件)中的屬性之間定義的。
有時,應用程序的需求超越了屬性綁定層面,它要求用戶啟動影響 ViewModel 中某些內容的命令。 這些命令通常通過點擊按鈕或手指敲擊觸發信號,往往是以下兩個事件的處理程序中的代碼隱藏文件中處理它們:Button
的 Clicked
事件或 TapGestureRecognizer
的 Tapped
事件。
命令接口提供了另一種實現命令的方法,這種方法更適合 MVVM 體系結構。 ViewModel 本身可以包含命令,這些命令是針對視圖中的特定活動(例如單擊 Button
)而執行的方法。 在這些命令和 Button
之間定義了數據綁定。
為允許使用 Button
和 ViewModel 之間的綁定數據,Button
定義兩個屬性:
System.Windows.Input.ICommand
類型的Command
Object
類型的CommandParameter
要使用命令接口,您需要定義一個針對Button的Command屬性的數據綁定,其中源是ViewModel中ICommand類型的屬性。 ViewModel包含與單擊按鈕時執行的與ICommand屬性關聯的代碼。 您可以將CommandParameter設置為任意數據,以區分多個按鈕(如果它們都綁定到ViewModel中的同一ICommand屬性)。
下列類也定義了 Command
和 CommandParameter
屬性:
MenuItem
以及派生自MenuItem
的ToolbarItem
TextCell
以及派生自TextCell
的ImageCell
- TapGestureRecognizer
SearchBar
定義一個 ICommand
類型的 SearchCommand
屬性和一個 SearchCommandParameter
屬性。 ListView
的 RefreshCommand
屬性也是 ICommand
類型。
可以在 ViewModel 中以不依賴視圖中的特定用戶界面對象的方式處理上述所有命令。
使用:
若要使用命令接口,ViewModel 需包含 ICommand
類型的屬性(用於處理View(eg 按鈕)的所有邏輯)
ICommand
接口【System.Windows.Input.ICommand
】由兩個方法和一個事件組成:Execute、CanExecute、CanExecuteChanged。
而一般用位於Xamarin.Forms的的Command類來實現ICommand接口類型的屬性。
通過 Command
類的構造函數,你可以傳遞與 Execute
和 CanExecute
方法對應的 Action
和 Func<bool>
類型的參數。
參考實例:PersonEntryPage 【主要是按鈕的Command屬性的綁定】
它的工作原理如下:用戶首先按“新建”按鈕 。 這將啟用輸入窗體,但禁用“新建”按鈕 。 然后用戶輸入姓名、年齡和技能。 在編輯過程中,用戶隨時都可以按下“取消”按鈕重新開始 。 只有在輸入了姓名和有效年齡后,才啟用“提交”按鈕 。 按“提交”按鈕可將人員轉移到 ListView
顯示的集合中 。 按“取消”或“提交”按鈕后,會清除輸入窗體中的內容並再次啟用“新建”按鈕 。
注:如果使用命令接口,請勿使用 Button
的 IsEnabled
屬性。
1、Excute
用戶按下 Button
時,Button
調用綁定到 Button的Command
屬性的 ICommand
對象中的 Execute
方法,即上面的Action execute 無返回值的委托。
2、CanExcute
首次在 Button
的 Command
屬性上定義綁定時【即初始加載時】,以及數據綁定以某種方式更改時,Button
調用 ICommand
對象中的 CanExecute
方法。
如果 CanExecute
返回 false
,則 Button
將禁用其自身。 這表示特定命令當前不可用或無效。
3、ChangeCanExecute
每當發生任何可能更改 CanExecute
方法返回值【eg 按鈕是否可用】的事情時,ViewModel 都應該為 ICommand
屬性調用 ChangeCanExecute
。【自定義的RefreshCanExecutes()方法中的設定】
調用 ChangeCanExecute方法
將導致 Command
類觸發 CanExecuteChanged
事件。 示例中Button
已為該事件附加了一個處理程序,並通過再次調用 CanExecute
進行響應,然后根據該方法的返回值啟用自身。
注:示例中的提交按鈕
因為最初單擊了New按鈕,執行了RefreshCanExecutes()方法,三個按鈕的CanExecuteChanged
事件均會附加處理程序。
每當編輯中的 PersonViewModel
對象中的屬性發生更改時,都會調用 SubmitCommand
的 canExecute
函數。即實時 判斷提交按鈕是否可用。。
已編譯的綁定
已編譯綁定的解析速度快於傳統綁定解析,因而可提升 Xamarin.Forms 應用程序中的數據綁定的性能 。
數據綁定存在兩個主要問題:
- 綁定表達式不具有編譯時驗證。 相反,綁定在運行時解析。 因此,在應用程序未按預期運行或出現錯誤消息時,直到運行時才會檢測到任何無效綁定。
- 它們不具有成本效益。 使用常規對象檢查(反射)在運行時解析綁定,並且執行此操作的開銷因平台而異。
已編譯的綁定通過在編譯時而不是運行時解析綁定表達式來提升 Xamarin.Forms 應用程序中的數據綁定性能。 此外,綁定表達式的這種編譯時驗證可以提供更好的開發人員故障排除體驗,因為無效綁定會被報告為生成錯誤。
使用已編譯的綁定的過程:
- 啟用 XAML 編譯。 有關 XAML 編譯的詳細信息,請參閱 XAML 編譯。
- 將
VisualElement
上的x:DataType
屬性設置為VisualElement
及其子元素將綁定到的對象類型。 請注意,可以在視圖層次結構中的任意位置重新定義此屬性。
在 XAML 編譯時,會將任何無效綁定表達式報告為生成錯誤。 但是,XAML 編譯器僅報告遇到的第一個無效綁定表達式的生成錯誤。
設置程序集級別的xaml編譯(AssemblyInfo.cs文件中):[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
無論是在 XAML 還是代碼中設置 BindingContext
,都將編譯在 VisualElement
或其子元素上定義的任何有效綁定表達式。 編譯綁定表達式會生成編譯代碼,該代碼將從源上的屬性獲取值,並將在標記中指定的目標的屬性上對其進行設置 。 此外,根據綁定表達式,生成的代碼可能會觀察到源屬性值的更改並刷新目標屬性,並可能會將更改從目標推送回源 。
注:目前,定義 Source
屬性的任何綁定表達式都禁用了編譯綁定。 這是因為 Source
屬性始終使用 x:Reference
標記擴展進行設置,該設置在編譯時無法解析。