前面章節一直都在討論如何添加鏈接兩個各元素的綁定。但在數據驅動的應用程序中,更常見的情況是創建從不可見對象中提取數據的綁定表達式。唯一的要求是希望顯示的信息必須存儲在公有屬性中。WPF數據綁定數據結構不能獲取私有信息或公有字段。
當綁定到非元素對象時,需要放棄Binding.ElementName屬性,並使用以下屬性中的一個:
- Source:該屬性是指向源對象的引用——換句話說,是提供數據的對象。
- RelativeSource:這是引用,使用RelateSource對象指向源對象。有了這個附加層,可在當前元素(包含綁定表達式的元素)的基礎上構建引用。這似乎無謂地增加了復雜程度。但實際上,RelativeSource屬性是一種特殊工具,當編寫控件模板以及數據模板時很方便的。
- DataContext:如果沒有使用Source或RelativeSource屬性指定源,WPF就從當前元素開始在元素樹中向上查找。檢查每個元素的DataContext屬性,並使用第一個非空的DataContext屬性。當我要將同一個對象的多個屬性綁定到不同的元素時,DataContext屬性是非常有用的,因為可在更高層次的容器對象上(而不是直接在目標元素上)設置DataContext屬性。
一、Source屬性
Source屬性非常簡單。唯一的問題是為了進行綁定,需要具有數據對象。在稍后將看到可使用集中方法獲取數據對象。可從資源中提取數據對象,可通過編寫代碼生成數據對象,也可在數據提供的幫助下獲取數據對象。
最簡單的選擇是將Source屬性指向一些已經准備好了的靜態對象。例如,可在代碼中創建一個靜態對象並使用該對象。或者,可使用來自.NET類庫的組件。如下所示:
<TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"></TextBlock>
這個綁定表達式獲取由靜態屬性SystemFonts.IconFontFamily提供的FontFamily對象(注意,為了設置Binding.Source屬性,需要借助靜態標記擴展)。然后將Binding.Path屬性設置為FontFamily.Source屬性,給屬性給出了字體家族的名稱。結果是一行文本。在Windows Vista或Windows 7中,顯示的字體名稱segoe UI。
另一種選擇是綁定到先前作為資源創建的對象。例如,下面的標記創建指向Calibri字體的FontFamily對象:
<Window.Resources> <FontFamily x:Key="CustomFont">Calibri</FontFamily> </Window.Resources>
並且下面的TextBlock元素會被綁定到該資源:
<TextBlock Margin="5" Text="{Binding Source={StaticResource CustomFont}, Path=Source}"></TextBlock>
現在將會看到文本Calibri。
二、RelativeSource屬性
通過RelativeSource屬性可根據相對於目標對象的關系指向源對象。例如,可使用RelativeSource屬性將元素綁定到自身或其父元素(不知道在元素樹中從當前元素到綁定的父元素到綁定的父元素之間有多少代)。
為設置Binding.RelativeSource屬性,需要使用RelativeSource對象,這會使語法變得更加復雜,因為出了需要創建Binding對象外,還需要在其中創建嵌套的RelativeSource對象。一種選擇是使用屬性設置語法而不是使用Binding標識擴展。例如,下面的代碼為TextBlock.Text屬性創建了一個Binding對象,這個Binding對象時候用查找父窗口並顯示窗口標題的RelativeSource對象:
<TextBlock> <TextBlock.Text> <Binding Path="Title"> <Binding.RelativeSource> <RelativeSource Mode="Findncestor" AncestorType="{x:Type Window}" /> </Binding.RelativeSource> </Binding> </TextBlock.Text> </TextBlock>
RelativeSource對象使用FindAncestor模式,該模式告知查找元素樹直到發現AncestorType屬性定義的元素類型。
編寫綁定更常用的方法是使用Binding和RelativeSource標記擴展,將其合並到一個字符串中,如下所示:
<TextBlock Text="{Binding Path=Title,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}} }"> </TextBlock>
當創建RelativeSource對象時,FindAncestor模式有4中,下表列出了所有4中模式。
表 RelativeSourceMode枚舉值
RelativeSource屬性看似多余,並且會標記變得復雜。畢竟,為什么不使用Source或Element屬性直接綁定到希望使用的源呢?然而,並不總是可以使用Source或ElementName屬性,這通常是因為源對象和目標對象在不同的標記塊中。當創建控件模板和數據模板時會出現這種情況。例如,如果正在構建改變列表項顯示方式的數據模塊,可能需要訪問頂級ListBox對象以讀取屬性。
三、DataContext屬性
在某些情況下,會將大量元素綁定到同一個對象。例如,分析下面的一組TextBlock元素,每個TextBlock元素都使用類似的綁定表達式提取與默認圖標字體相關的不同細節,包括行間距,以及第一個字體的樣式和粗細(這兩個都是簡單的正則表達式)。可為每個TextBlock元素使用Source屬性,但這會使標記變得非常長:
<TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=LineSpacing}"></TextBlock> <TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Style}"></TextBlock> <TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Weight}"></TextBlock>
對於這種情況,使用FrameworkElement.DataContext屬性一次性定義綁定源會更清晰,也更靈活。在這個示例中,為包含所有TextBlock元素的StackPanel面板設置DataContext屬性是合理的(甚至還可在更高層次上設置DataContext屬性——例如整個窗口——但是為了使意圖更清晰,在盡可能小的范圍內進行定義效果更好)。
可使用和設置Binding.Source屬性相同的方法設置元素的DataContext屬性。換句話說,可提供內聯對象,從靜態屬性中提取,或從資源中提取,如下所示:
<StackPanel Margin="10" DataContext="{x:Static SystemFonts.IconFontFamily}">
現在可通過省略源信息來精簡綁定表達式:
<TextBlock Margin="5" Text="{Binding Path=Source}"></TextBlock>
當在綁定表達式中省略源信息時,WPF會檢查元素的DataContext屬性。如果屬性值為null,WPF會繼續向上在元素樹中查找第一個不為null的數據上下文(最初,所有元素的DataContext屬性都是null)。如果找到了一個數據上下文,就為綁定使用找到的數據上下文。如果沒有找到,綁定表達式不會為目標屬性應用任何值。