資料繫結(一)——《Windows Phone 7程式設計》

出版圈郭志敏發表於2012-01-05

假設你想讓使用者與Slider控制元件進行互動,而且你也想與ColorScroll程式一樣,通過TextBlock顯示Slider當前的值。其實很簡單。只要為Slider控制元件的ValueChanged事件建立一個處理程式就可以了,當呼叫處理程式的時候,從Slider取出Value屬性的值並將其轉換成字串,然後把該字串設定給TextBlock的Text屬性。 像這樣的任務非常普遍,因此Silverlight提供了一種簡便的機制來實現這些任務。這種機制稱為資料繫結(data binding),或者就簡稱為繫結(binding)。資料繫結是一個物件的一個屬性與另外一個物件的一個屬性之間的一條連結(link),因此在繫結的情況下當一個屬性發生改變時,另一個屬性也隨之更新。繫結可以是雙向的(bidirectional),在這種情況下,其中一個屬性發生的變化會引起另一個屬性也隨之發生變化。

從本質上說,資料繫結可能像你所期望的那樣:由於註冊了一個事件處理程式,使得一個屬性從另外一個屬性中獲取更新,期間可能進行了一些資料轉換。通常你可以完全通過XAML來定義資料繫結,這意味著你不必編寫任何程式碼。從語法上看,這好像不需要移動任何部件就能傳輸資料了。

演示資料繫結最簡單的方法是使用兩個視覺化元素,例如Slider和TextBlock元素,我也從這兩個元素開始。但是,如果把視覺化元素和基礎資料來源進行繫結的話,更能體現出資料繫結的強大威力。

本章的目標是避免在程式碼隱藏檔案中顯式地使用事件處理程式,但是在本章的結束部分我不得不使用幾個事件處理程式。當然,我們還是需要一些其他程式碼來支援XAML中的資料繫結,但這些程式碼中的大部分可以恰當地歸類為業務物件(business object),而不是使用者介面元素。

繫結源與目標

在典型的資料繫結中,一個物件的屬性發生變化時,另一個物件的屬性也隨之自動更新。提供資料的物件,例如Slider,被認為是資料繫結的源(source);接收資料的物件(如TextBlock)是繫結的目標(target)。 通常給資料繫結源指定一個名字:

<Slider Name="slider"  .../>

你可以把目標屬性作為一個屬性元素 並賦值給型別為Binding的物件:

<TextBlock ...> 
    <TextBlock.Text> 
        <Binding ElementName="slider" Path="Value" /> 
    </TextBlock.Text> 
</TextBlock>

使用ElementName屬性指定源元素的名稱;使用Path屬性指定源屬性的名稱,在這個例子中Path是Slider的Value屬性。有時候把這種型別的繫結稱為元素名稱繫結,因為繫結源是一個視覺化元素,並通過名稱來引用。

為了使語法變得更加友好,Silverlight為Binding提供了一個標記擴充套件(markup extension),在此,所有的東西都定義在一對花括號裡面。(這是Silverlight for Windows Phone的幾個標記擴充套件中的一個。第7章介紹過StaticResource,第16章將介紹TemplateBinding。)這裡是更精簡的語法:

<TextBlock ... Text="{Binding ElementName=slider, Path=Value}" ... />

請注意,ElementName和Path的設定用一個逗號分隔,而slider和Value名稱的引號已經去掉了。引號永遠都不會出現在標記擴充套件的大括號中。 SliderBindings程式使用了這樣的繫結方式,你可以試驗一下,嘗試做一些修改。這一切都在XAML檔案裡面:

Silverlight專案:SliderBindings  檔案:MainPage.xaml(節選)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Slider Name="slider" 
            Value="90"
            Grid.Row="0"
            Maximum="180"
            Margin="24" />

    <TextBlock Name="txtblk" 
               Text="{Binding ElementName=slider, Path=Value}" 
               Grid.Row="1"
               FontSize="48"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />

    <Rectangle Grid.Row="2"


               Width="{Binding ElementName=slider, Path=Value}"
               RenderTransformOrigin="0.5 0.5"
               Fill="Blue">
        <Rectangle.RenderTransform>
            <RotateTransform x:Name="rotate" 
                             Angle="90" />
        </Rectangle.RenderTransform>
    </Rectangle>
</Grid>

這個頁面包含了一個範圍從0到180的Slider,以及一個TextBlock,TextBlock的Text屬性繫結到Slider的Value屬性上,另外還有一個Rectangle,它的Width屬性也繫結到Slider相同的Value屬性上。Rectangle還有一個RotateTransform屬性,這個屬性使得Rectangle元素旋轉了90°。

操作Slider時,TextBlock顯示Slider的值,而Rectangle的高度也隨之變大或者變小。(Binding的目標是Rectangle的Width屬性,而Rectangle的轉角是90°。)

在Binding擴充套件標記中,屬性的順序無關緊要。你可以把Path屬性放在前面:

<TextBlock ... Text="{Binding Path=Value, ElementName=slider}"

事實上,如果路徑出現第一個位置上,可以刪除“Path=”部分,只是使用屬性名:

<TextBlock ... Text="{Binding Value, ElementName=slider}"

在本章的後面以及隨後的章節中,我將使用這種縮略形式的語法,但對於元素名稱繫結,我卻不喜歡這樣做,因為這樣做我就無法知道繫結底層的工作原理。Binding類首先需要在視覺化樹中找到一個名為slider的元素,然後它需要使用反射來找到該元素的Value屬性。我偏向於下面這樣的語法,這種語法按照內部操作的過程來排列屬性的順序:

<TextBlock ... Text="{Binding ElementName=slider, Path=Value}"

為什麼Binding的這個屬性叫做Path而不是Property呢?畢竟,Style類就有一個叫做Property的屬性。為什麼Binding卻沒有呢?

簡單的答案是Path能把多個屬性名組合在一起使用。例如,假設Slider沒有名稱。如果你知道該Slider是ContentPanel元素的Children集合的第一個子元素,你可以間接地引用Slider:

Text="{Binding ElementName=ContentPanel, Path=Children[0].Value}"

或者,使用視覺化樹更上一層的元素:

Text="{Binding ElementName=LayoutRoot, Path=Children[1].Children[0].Value}"

Path的組成部件必須是通過點號連線的屬性或者索引器(indexer)。

Target和Mode

繫結包含一個源和一個目標。繫結目標被認為是繫結要設定的屬性,該屬性必須始終都是一個依賴屬性,永遠都是。當你在程式碼中建立繫結的時候,這一限制非常明顯。

修改一下SliderBindings程式,刪除TextBlock的Text屬性上的繫結。在MainPage.xaml.cs檔案中,你需要新增using指令來引用System.Windows.Data名稱空間,這個名稱空間包含了Binding類。在建構函式裡面,呼叫完InitializeComponent函式之後,生成一個型別為Binding的物件,並設定它的屬性:

  Binding binding = new Binding();   binding.ElementName = "slider";   binding.Path = new PropertyPath("Value");

ElementName和Path屬性是繫結源。下面看看將TextBlock的Text屬性作為繫結目標的程式碼:

txtblk.SetBinding(TextBlock.TextProperty, binding);

SetBinding方法定義在FrameworkElement裡面,第一個引數是依賴屬性,也就是目標屬性。該目標也是呼叫SetBinding方法的元素。你也可以使用其他替代方案,使用靜態方法BindingOperations.SetBinding來繫結目標:

BindingOperations.SetBinding(txtblk, TextBlock.TextProperty, binding);

但你仍然需要依賴屬性。因此,這就是視覺化物件的屬性應該是依賴屬性的另一個原因。你不僅可以為這些屬性定製樣式,而且可以把它們製作成動畫,但所有這些資料繫結的目標必須是依賴屬性。

就依賴屬性的優先順序而論,資料繫結與本地設定的級別相同。

使用BindingOperations.SetBinding方法意味著你可以在任何依賴屬性上設定繫結。對於Silverlight for Windows Phone來說,事實並非如此。在Windows Phone中繫結的目標必須是FrameworkElement的屬性。

例如,你會發現在MainPage.xaml中的Rectangle元素包含了RotateTransform屬性,該屬性設定為一個RotateTransform物件。嘗試把TextBlock的Text屬性和Rectangle的Width屬性上的繫結也應用到Angle屬性上:

<RotateTransform x:Name="rotate"  
                 Angle="{Binding ElementName=slider, Path=Value}" /> 

這看起來好像沒問題,但卻不能正常工作。你會在執行時得到一個XamlParseException異常。Angle本身是依賴屬性,不是條件充足了嗎?但是RotateTransform並不是派生自FrameworkElement,所以它不可以作為繫結的目標。(在Silverlight 4中,應用於RotateTransform的Angle屬性的繫結可以正常工作。但是Silverlight for Windows Phone大體上還是Silverlight 3 。)

如果想要這麼做,你需要刪除RotateTransform的Angle屬性上的繫結,以及已新增到MainPage.xaml.cs的所有程式碼。把Slider的Value屬性的值初始化為90:

<Slider Name="slider" 
        Value="90" ... />

繫結的目標是TextBlock的Text屬性:

<TextBlock Name="txtblk" 
           Text="{Binding ElementName=slider, Path=Value}" ... />

讓我們切換一下,把TextBlock的Text屬性初始化為90:

<TextBlock Name="txtblk"  
           Text="90"  .../>

然後把Slider的Value屬性作為繫結目標:

<Slider Name="slider"  
        Value="{Binding ElementName=txtblk, Path=Text}" .../>

乍一看這似乎能正常地工作。Slider的滾動塊最初放在中間,這表示Slider的值為90,該值從TextBlock獲取,而Rectangle的大小仍然與Slider繫結。然而,當你滑動Slider時,Rectangle的高度改變了,但TextBlock卻沒有發生任何變化。Slider上的Binding物件正等待著TextBlock的Text屬性發生變化,但Text屬性沒有任何的改變。

現在為Slider上的繫結新增Mode設定,下面的程式碼表示這個資料繫結是雙向(two-way)的。

<Slider Name="slider"  
        Value="{Binding ElementName=txtblk, Path=Text, Mode=TwoWay}" .../> 

現在能正常工作了!繫結的目標仍然是Slider的Value屬性。TextBlock的Text屬性變化時會影響Slider的Value屬性,同樣,現在Slider的Value屬性變化時也反過來影響到TextBlock。

Mode屬性的值為BindingMode列舉型別的成員。Mode屬性的預設值是BindingMode. OneWay,除此之外還有BindingMode.TwoWay和BindingMode.OneTime,BindingMode. OneTime表示源只傳輸一次資料到目標。

使用同樣的技巧,可以為RotateTransform的Angle屬性建立起繫結關係。首先,把TextBlock上的繫結還原到原始狀態:

<TextBlock Name="txtblk"  
           Text="{Binding ElementName=slider, Path=Value}" .../>

現在為Slider設定雙向繫結,指向RotateTransform的Angle屬性:

<Slider Name="slider"  
        Value="{Binding ElementName=rotate, Path=Angle, Mode=TwoWay}" .../>

執行得很好!當滑動Slider時,Rectangle元素也相應地旋轉,如圖12-1所示。

enter image description here

下一篇:資料繫結(二)——《Windows Phone 7程式設計》

相關文章