前言 貌似最近來問我XAML這塊的東西的人挺多的。有時候看他們寫XAML這塊覺著也挺吃力的,所謂基礎不牢,地動山搖。XAML這塊雖說和HTML一樣屬於標記語言,但是世界觀相對更加龐大一點。 今天講講XAML中的Binding。沒啥技術含量,全當是快速閱讀。 Binding作為MVVM模式的一個...
前言
貌似最近來問我XAML這塊的東西的人挺多的。有時候看他們寫XAML這塊覺著也挺吃力的,所謂基礎不牢,地動山搖。XAML這塊雖說和HTML一樣屬於標記語言,但是世界觀相對更加龐大一點。
今天講講XAML中的Binding。沒啥技術含量,全當是快速閱讀。
Binding作為MVVM模式的一個相對核心的功能,一直是有爭議的。使用數據綁定可以將我們的View和Model解耦,但是如果一旦出現Bug,我們將很難調試,還有一個問題就是數據綁定會帶來過大的記憶體開銷。
跟多數的技術一樣,都有自己的兩面性,具體運用場景如何抉擇,應該充分考慮。
作為XAML的一部分,Binding的功能也隨著XAML版本的變更著。目前為止,XAML一共有以下幾個版本:
- WPF Version
- Silverlight 3 Version
- Silverlight 4 Version
- Windows 8 XAML/Jupiter(Windows Runtime XAML Framework) Version
其中WPF版本的Binding功能最強大,但也開銷最大。本文中,我們主要講述最新版本,即__Windows 8 XAML/Jupiter__這個版本的XAML中的Binding。
開始之前
要想講明白Binding這個東西,我們先要從Binding類的繼承層次開始講。
public class Binding : BindingBase, IBinding, IBinding2
{
public Binding();
public IValueConverter Converter { get; set; }
public System.String ConverterLanguage { get; set; }
public System.Object ConverterParameter { get; set; }
public System.String ElementName { get; set; }
public BindingMode Mode { get; set; }
public PropertyPath Path { get; set; }
public RelativeSource RelativeSource { get; set; }
public System.Object Source { get; set; }
public System.Object FallbackValue { get; set; }
public System.Object TargetNullValue { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
}
可以看到Binding繼承了BindingBase類,還繼承了IBinding,IBinding2的介面。我們再分別看下這三個類和介面。首先看下我們的BindingBase。
public class BindingBase : DependencyObject, IBindingBase
{
public BindingBase();
}
BindingBase又繼承了DependencyObject和IBindingBase。DependencyObject這個我們就不多講了,IBindingBase只是一個空介面。看來BindingBase沒有看到太多信息,我們再來看下IBinding和IBinding2。
internal interface IBinding
{
IValueConverter Converter { get; set; }
System.String ConverterLanguage { get; set; }
System.Object ConverterParameter { get; set; }
System.String ElementName { get; set; }
BindingMode Mode { get; set; }
PropertyPath Path { get; set; }
RelativeSource RelativeSource { get; set; }
System.Object Source { get; set; }
}
internal interface IBinding2
{
System.Object FallbackValue { get; set; }
System.Object TargetNullValue { get; set; }
UpdateSourceTrigger UpdateSourceTrigger { get; set; }
}
微軟設計了一個Binding的基礎模型,蘊含了介面分離原則(ISP)的思想,又提供了一個IBindingBase的空介面,如果你想實現自己的Binding模型,可以繼承這個介面,這樣可以和.NET類庫風格統一。
講講IBinding
既然我們已經瞭解了Binding的大概的層次結構,那我們開始一個個講講這些都是怎麼用的。
IBinding中的Path
Path是我們相對用的比較多的,多數情況下,我們可以這樣寫
Text="{Binding}"
XAML會取綁定源的ToString的值,所以我們可以重寫Override方法來實現我們的需要的綁定。
我們也可以綁定具體的屬性,比如:
Text="{Binding Name}"
如果我們綁定了一個集合,那我們也可以嘗試這樣寫:
Text="{Binding MyList[1].Name}"
除了上述比較常用的,我們還有一個叫做ICustomPropertyProvider的介面,當你的類實現了這個介面中的
string GetStringRepresentation()
XAML就會去取這個函數返回的值。
所以總結下我們的Path的綁定方式:
預設情況:
target Text="{Binding}"
source ToString()
//你可以重寫ToString方法來改變值
屬性綁定:
target Text="{Binding Name}"
source public String Name { get;}
索引器綁定:
target Text="{Binding MyList[1].Name}"
實現ICustomPropertyProvider的綁定:
target Text="{Binding}"
source String GetStringRepresentation()
//實現方法獲取值
IBinding中的Mode
Mode一共有三種,OneTime,OneWay,TwoWay。看字面的意思就很容易理解。
//OneTime
Text="{Binding, Mode=OneTime}"
//OneWay
Text="{Binding, Mode=OneWay}"
//TwoWay
Text="{Binding, Mode=TwoWay}"
在OneWay和TwoWay中,如果想要對象的值變更時讓綁定目標也變化,需要註意一下兩點
- 對於普通的屬性,需要類實現INotifyPropertyChanged,並且對象值變化時手動通知變更。
- 對於依賴屬性,當觸發SetValue方法後,PropertyChangedCallBack會通知變更,所以無需我們手動操作。
在UWP系統中,Mode的預設值為__OneWay__。
IBinding中的RelativeSource
RelativeSource是一種相對關係找數據源的綁定。目前有兩種:Self和TemplatedParent
//Self
<TextBlock Text="{Binding Foreground.Color.R, RelativeSource={RelativeSource Mode=Self}}" Foreground="Red"/>
//TemplatedParent
<DataTemplate>
<Rectangle Fill="Red" Height="30" Width="{Binding Width, RelativeSource={RelativeSource Mode=TemplatedParent}}">
</DataTemplate>
RelativeSource綁定的方式我們常用於控制項模板。預設值一般為null。
IBinding中的ElementName
ElementName也是我們最常用的一種綁定方式,使用這個我們需要註意兩點:
- 指定的ElementName必須在當前XAML名稱範圍里。
- 如果綁定目標位於數據模板或控制項模板中,則為模板化父級的XAML名稱範圍。
舉個例子:
<UserControl x:Name="Instance" Background="Red">
<Grid Background="{Binding Background, ElementName=Instance}"/>
</UserControl>
//or
<Page x:Name="Instance">
<Grid>
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command=" {Binding DataContext.TestCommand, ElementName=Instance}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
IBinding中的Source
Source也是我們常用的一種方式。
<Page>
<Page.Resources>
<SolidColorBrush x:Key="MainBrush" Color="Orange"/>
</Page.Resources>
<Grid Background="{Binding Source={StaticResource MainBrush}}"/>
</Page>
//當然我們也可以簡寫為
<Grid Background="{StaticResource MainBrush}"/>
一般情況下,Source,ElementName和RelativeSource三者是互斥的,指定多餘一種的綁定方式會引發異常。
IBinding中的Converter
我們很難保證我們的對象值和我們綁定目標的類型一直,所以轉換器可以將類型就行轉換。
使用轉換器我們要實現IValueConverter介面:
public class BoolVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool? result = value as Nullable<bool>;
if(result == true)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
如果你的值需要轉換回去,你也可以繼續實現ConvertBack方法。
<Page>
<Page.Resources>
<local:BoolVisibilityConverter x:Key="BoolVisibilityConverter"/>
</Page.Resources>
<Grid>
<Border Visibility="{Binding Busy, Converter={StaticResource BoolVisibilityConverter}}"/>
</Grid>
</Page>
IBinding中的ConverterParameter和ConverterLanguage
這兩個參數不能綁定,只能指定常量值。
<Border Visibility="{Binding Busy, Converter={StaticResource BoolVisibilityConverter},
ConverterParameter=One, ConverterLanguage=en-US}"/>
IBinding中的參數基本上覆蓋了我們多數的需求。儘管相對於WPF缺少了多值綁定等等,但我們也能夠通過自定義一些附加屬性來實現這些功能。
IBinding2
IBinding2中的參數就相對使用的比較少了。
IBinding2中的FallbackValue
FallbackValue的用途是:當綁定對象不存在時,我們就使用FallbackValue的值:
<Page>
<Page.Resources>
<x:String x:Key="ErrorString">Not Found</x:String>
</Page.Resources>
<Grid>
<TextBlock Text="{Binding Busy, FallbackValue={StaticResource ErrorString}}"/>
</Grid>
</Page>
IBinding2中的TargetNullValue
TargetNullValue的用途是:當綁定對象為空時,我們就使用TargetNullValue的值:
<Page>
<Page.Resources>
<x:String x:Key="ErrorString">Not Found</x:String>
</Page.Resources>
<Grid>
<TextBlock Text="{Binding Busy, TargetNullValue={StaticResource ErrorString}}"/>
</Grid>
</Page>
IBinding2中的UpdateSourceTrigger
UpdateSourceTrigger的值有三種:Default,PropertyChanged,Explicit。
多數情況下大多數依賴項屬性的預設值都為 PropertyChanged。但是Text屬性不是。
PropertyChanged的意思是當綁定目標屬性更改時,立即更新綁定源。而Explicit是只有UpdateSource方法時才更新綁定源。
舉個例子:
<Grid>
<TextBox x:Name="TitleTextBox" Text="{Binding Title, ElementName=Instance, UpdateSourceTrigger=Explicit, Mode=TwoWay}" />
<Button Click="Button_Click"/>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
var current = this.Title;
TitleTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
current = this.Title;
}
有關uwp的Binding就說到這裡。謝謝~
參考資料
被誤解的MVC和被神化的MVVM
Extensible Application Markup Language
RelativeSource 標記擴展
UpdateSourceTrigger enumeration