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

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

繫結轉換器

當你執行SliderBindings程式時(或者當你看到該截圖的時候會感到驚訝),開始的時候你可能看到TextBlock顯示Slider的值有時是整數,有時是包含一或兩個小數位的浮點數,但更多的時候看到的是15位的雙精度浮點數。

有辦法解決這個問題嗎?

有,Binding類的其中一個叫做Converter的屬性,這個屬性的作用是引用一個可以在從源到目標的過程中進行資料轉換的類,(如果有需要)也可以從目標轉換回源。顯然,我們已經使用了一些隱式的資料轉換,例如數字轉換為字串,或者字串轉換為數字。但是,我們可以提供一些更明確的手段來協助轉換的過程。 Binding類Converter屬性是IValueConverter型別,IValueConverter是一個介面,它包含了兩個方法:Convert和ConvertBack。Convert方法處理從源到目標的資料轉換,而ConvertBack方法處理TwoWay(雙向)繫結時另外一個方向的轉換。

如果你的轉換類從來不使用在雙向繫結上,那麼在ConvertBack方法中簡單地返回null就可以了。 為了給SliderBindings程式新增一個簡單的轉換器,在專案中增加一個名為Truncation- Converter的類。其實這個類已經在專案中,如下所示:

Silverlight專案:SliderBindings  檔案:TruncationConverter.cs
using System;
using System.Globalization;
using System.Windows.Data;

namespace SliderBindings
{
    public class TruncationConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
                              object parameter, CultureInfo culture)
        {
            if (value is double)
                return Math.Round((double)value);

            return value;
        }

        public object ConvertBack(object value, Type targetType, 
                                  object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

Convert方法的value引數是從源傳遞到目標的物件。這個方法檢查value是否為double型別。如果是double,那麼顯式地呼叫Math.Round方法把value轉換成double。

如果需要在MainPage.xaml引用這個類,你需要使用XML名稱空間宣告:

xmlns:local="clr-namespace:SliderBindings"

然後把TruncationConverter類作為資源:

<phone:PhoneApplicationPage.Resources>
    <local:TruncationConverter x:Key="truncate" />
    ...
</phone:PhoneApplicationPage.Resources>

你會發現這些已經存在於SliderBindings專案的MainPage.xaml檔案中了。

然後Binding擴充套件標記就可以引用這個資源:

<TextBlock Name="txtblk" 
           Text="{Binding ElementName=slider, 
                          Path=Value, 

我把擴充套件標記分成三行使各個部件更加清晰可見。注意StaticResource也是一個擴充套件標記,它巢狀在第一個標記擴充套件中,因此整個表示式包含了兩個花括號。

現在TextBlock顯示的數字被截斷了,如圖12-2所示。

enter image description here

記住,把轉換器定義為StaticResource。很多時候,我可能不由自主地將Binding的Converter屬性設定為靜態資源的鍵名:

<!— 這是錯誤的 !--> 
<TextBlock Name="txtblk"  
           Text="{Binding ElementName=slider,  
                          Path=Value,  
                          Converter=truncate}" ... />

我自己經常這樣做,但是這種問題卻難以追蹤。

使用轉換器最常用的方法是把轉換器定義為資源,但是這不是唯一的方法。如果你使用Binding的元素語法,可以直接把TrunctionConverter類嵌入到標記中:

<TextBlock ... > 
    <TextBlock.Text> 
        <Binding ElementName="slider" 
                 Path="Value"> 
            <Binding.Converter> 
                <local:TruncationConverter /> 
            </Binding.Converter> 
        </Binding> 
    </TextBlock.Text> 
</TextBlock>

不過,如果你在一個XAML檔案中多次使用相同的轉換器,最好還是把它定義為資源,這樣能共享唯一例項。 TrucationConverter實際上是一個糟糕的資料轉換器。當然它能完成它應該做的工作,但完成的方式並不靈活。如果你要在轉換器類中呼叫Math.Round方法,那麼提供一個小數位舍入功能不是更好嗎?試想一下,如果有一個不僅支援數字,而且還支援不同的格式和各種資料型別的功能,那豈不是更好?

這種魔法般的功能由Petzold.Phone.Silverlight庫裡面的StringFormatConverter類所提供:

Silverlight專案:Petzold.Phone.Silverlight  檔案:StringFormatConverter.cs
using System;
using System.Globalization;
using System.Windows.Data;

namespace Petzold.Phone.Silverlight
{
    public class StringFormatConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            if (targetType == typeof(string) && parameter is string)
                return String.Format(parameter as string, value);

            return value;
        }

        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

除了Converter屬性以外,Binding類包含一個叫做ConverterParameter的屬性。該屬性的值是呼叫Convert時傳遞給parameter的引數。Convert方法假設該parameter是.NET標準的格式化字串,該字串可用來呼叫String.Format。

為了在SliderBindings程式中使用這個轉換器,你需要引用Petzold.Phone.Silverlight庫。(這裡已經引用了。)並在檔案中新增XML名稱空間宣告:

xmlns:petzold="clr-namespace:Petzold.Phone.Silverlight;assembly=Petzold.Phone.Silv-erlight" 下面的程式碼用於例項化頁面中Resources集合裡的StringFormatConverter:

<phone:PhoneApplicationPage.Resources> 
    ...     
    <petzold:StringFormatConverter x:Key="stringFormat" /> 
</phone:PhoneApplicationPage.Resources>

現在你可以在Binding標記表示式中引用這個轉換器了。將ConverterParameter設定為.NET格式化字串的一個佔位符(Placeholder)的:

Text="{Binding ElementName=slider,  
               Path=Value,  
               Converter={StaticResource stringFormat}, 
               ConverterParameter=...}"

當你輸入.NET格式化字串時,你會發現一個問題。標準.NET的格式化字串需要使用大括號,但你也知道當XAML解析器(XAML parser)解碼Binding標記表示式時,並不會喜歡非法嵌入的大括號。 簡單的解決方法是把ConverterParameter的值用單引號括起來:

Text="{Binding ElementName=slider,  
               Path=Value,  
               Converter={StaticResource stringFormat}, 
               ConverterParameter='{0:F2}'}"

Visual Studio中的XAML解析器和視覺化設計器(visual designer)不喜歡這種特定的語法,但是執行時卻沒有問題。如果你想讓設計器接受這種語法,在第一個單引號後插入一個空格(或者其他字元)就可以了。 因為ConverterParameter是String.Format呼叫的第一個引數,你可以把它完善一下:

Text="{Binding ElementName=slider,  
           Path=Value,  
           Converter={StaticResource stringFormat}, 
           ConverterParameter='The slider is {0:F2}'}" 

結果如圖12-3所示。

enter image description here

相對繫結源

根據繫結資料來源來分類,Silverlight for Windows Phone支援三種不同的繫結型別。到目前為止,這一章已經介紹了ElementName繫結,這種繫結指向一個命名元素。在本章後面的部分,主要使用Source屬性來代替ElementName屬性指向資料來源。

第3類繫結稱為RelativeSource(相對繫結源)。在Windows Presentation Foundation中的RelativeSource比Silverlight中的RelativeSource靈活很多,所以你可能對這個選項沒有深刻的印象。使用RelativeSource的一個目的是與模板相關聯,你將在第16章看到這樣的應用。最後一個選項叫做Self,使用Self可以定義指向自身元素屬性的繫結。下面的程式演示了這種語法:

Silverlight專案:BindToSelf  檔案:MainPage.xaml(節選)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Center"
                VerticalAlignment="Center">

        <TextBlock Text="{Binding RelativeSource={RelativeSource Self}, 
                                  Path=FontFamily}" />

        <TextBlock Text=" - " />

        <TextBlock Text="{Binding RelativeSource={RelativeSource Self}, 
                                  Path=FontSize}" />

        <TextBlock Text=" pixels" />
    </StackPanel>
</Grid>

RelativeSource屬性是另一個擴充套件標記,其中包含RelativeSource和Self。Path指向同一個元素的另一個屬性。在這個例子中,這兩個TextBlock元素分別顯示自身的TextBlock的FontFamily和FontSize屬性。

this繫結源

也許你的應用程式有這樣一個需求:需要顯示很多簡短的文字字串,這些字串都由邊界所包圍。你決定建立一個派生自UserControl類的控制元件,並將其命名為BorderedText,如下所示:

<petzold:BorderedText Text="Ta Da!" 
                      FontFamily="Times New Roman" 
                      FontSize="96" 
                      FontStyle="Italic" 
                      FontWeight="Bold" 
                      TextDecorations="Underline" 
                      Foreground="Red" 
                      Background="Lime" 
                      BorderBrush="Blue" 
                      BorderThickness="8" 
                      CornerRadius="36" 
                      Padding="16 4" 
                      HorizontalAlignment="Center" 
                      VerticalAlignment="Center" />

從XML名稱空間的字首可以判斷出,這個類也在Petzold.Phone.Silverlight庫中。

BorderedText派生自UserControl,而UserControl繼承自Control,所以我們知道通過類繼承BorderedText具備Control類的一些屬性。BorderedText需要自己定義Text、TextDecorations、CornerRadius等屬性,以及一些使其變得更加靈活的其他屬性。

BorderedText.xaml檔案中的視覺化樹很可能包含了一個Border,並且Border裡包含了一個TextBlock。TextBlock和Border的屬性都是通過BorderedText的屬性來設定。

在前面的章節中,你已經見過實現這種功能的方法:ColorColumn類定義了Label和Value屬性,然後在程式碼中使用屬性變更處理程式為視覺化樹中元素的屬性設定新值。使用資料繫結能簡化這項工作。

BorderedText的程式碼隱藏檔案定義了一些屬性,其父類Control類並沒有提供這些虛屬性供其子類使用:

Silverlight專案:Petzold.Phone.Silverlight  檔案:BorderedText.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace Petzold.Phone.Silverlight
{
    public partial class BorderedText : UserControl
    {
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text",
                typeof(string),
                typeof(BorderedText),
                new PropertyMetadata(null));

        public static readonly DependencyProperty TextAlignmentProperty =
            DependencyProperty.Register("TextAlignment",
                typeof(TextAlignment),
                typeof(BorderedText),
                new PropertyMetadata(TextAlignment.Left));

        public static readonly DependencyProperty TextDecorationsProperty =
            DependencyProperty.Register("TextDecorations",
                typeof(TextDecorationCollection),
                typeof(BorderedText),
                new PropertyMetadata(null));


        public static readonly DependencyProperty TextWrappingProperty =
            DependencyProperty.Register("TextWrapping",
                typeof(TextWrapping),
                typeof(BorderedText),
                new PropertyMetadata(TextWrapping.NoWrap));

        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius",
                typeof(CornerRadius),
                typeof(BorderedText),
                new PropertyMetadata(new CornerRadius()));

        public BorderedText()
        {
            InitializeComponent();
        }

        public string Text
        {
            set { SetValue(TextProperty, value); }
            get { return (string)GetValue(TextProperty); }
        }

        public TextAlignment TextAlignment
        {
            set { SetValue(TextAlignmentProperty, value); }
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
        }

        public TextDecorationCollection TextDecorations
        {
            set { SetValue(TextDecorationsProperty, value); }
            get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
        }

        public TextWrapping TextWrapping
        {
            set { SetValue(TextWrappingProperty, value); }
            get { return (TextWrapping)GetValue(TextWrappingProperty); }
        }

        public CornerRadius CornerRadius
        {
            set { SetValue(CornerRadiusProperty, value); }
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
        }
    }
}

這些程式碼有點長但其實很簡單,因為都是一些屬性的定義。並沒有使用任何屬性變更處理程式。下面是包含了Border和TextBlock的XAML檔案:

S

ilverlight專案:Petzold.Phone.Silverlight  檔案:BorderedText.xaml
<UserControl x:Class="Petzold.Phone.Silverlight.BorderedText"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             Name="this">

    <Border Background="{Binding ElementName=this, Path=Background}"
            BorderBrush="{Binding ElementName=this, Path=BorderBrush}"
            BorderThickness="{Binding ElementName=this, Path=BorderThickness}"
            CornerRadius="{Binding ElementName=this, Path=CornerRadius}"
            Padding="{Binding ElementName=this, Path=Padding}">

        <TextBlock Text="{Binding ElementName=this, Path=Text}"
                   TextAlignment="{Binding ElementName=this, Path=TextAlignment}"
                   TextDecorations="{Binding ElementName=this, Path=TextDecorations}"
                   TextWrapping="{Binding ElementName=this, Path=TextWrapping}" />
    </Border>
</UserControl>

請注意根元素的命名:

Name="this"

你可以把這個根元素設定成你想要的名稱,但使用C#的關鍵字this是最常見的做法,因為在該XAML檔案中,this是指BorderedText類當前的例項,因此可以使用this這一熟悉的概念。你可以通過this這一名字把BorderedText的屬性與視覺化樹中元素的屬性建立繫結關係。

這個檔案不需要為Foreground屬性或者一些其他字型相關的屬性進行資料繫結,因為這些屬性都可以從視覺化樹中繼承過來。TextBlock的Inlines屬性使我感覺不爽。這是因為TextBlock把Inlines屬性定義為只讀(get-only)屬性,所以沒辦法為它定義資料繫結。

BorderedTextDemo程式測試這個新控制元件:

Silverlight專案:BorderedTextDemo  檔案:MainPage.xaml(節選)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <petzold:BorderedText Text="Ta Da!"
                          FontFamily="Times New Roman"
                          FontSize="96"
                          FontStyle="Italic"
                          FontWeight="Bold"
                          TextDecorations="Underline"
                          Foreground="Red"
                          Background="Lime"
                          BorderBrush="Blue"
                          BorderThickness="8"
                          CornerRadius="36"
                          Padding="16 4"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center" />
</Grid>

通知機制

為了使資料繫結能正常運作,繫結源必須實現某種通知機制(notification mechanism)。當屬性的值發生改變的時候,這種通知機制會傳送通知訊號,使得新的值可以從源傳遞到目標。當你繫結Slider的Value屬性到TextBlock的Text屬性時,同時使用到兩個依賴屬性。雖然你在公共的程式設計介面中看不出來,但是就是這些依賴屬性提供了通知機制。

使用資料繫結可以非常方便地關聯兩個視覺化元素,但以視覺化元素作為繫結目標,使用業務物件(business object)來代替視覺化元素作為繫結源的資料繫結功能最強大。

這裡需要提醒一下:

有時候,當程式設計師學習了作業系統的一個全新且重要的功能時(例如我在前面討論過的依賴屬性),他們就覺得需要在各個地方使用該功能,也許這樣做只是為了多練習一下。對於依賴特性來說,這種做法不太可取。當然,如果你的父類的父類已經是DependencyObject的派生類,那麼可以使用依賴屬性,否則不應該單純地為了使用依賴屬性而繼承DependencyObject類。

換句話說:不要為了使用依賴屬性而重寫業務物件!

資料繫結的目標必須是依賴屬性,但是對於繫結的源,並沒有嚴格要求。繫結源可以是普通類的普通屬性。但是如果你希望繫結源在變化的時候,繫結目標也隨之自動更新,那麼繫結源必須實現某種通知機制。 通常用作繫結源的業務物件需要實現的通知機制稱為INotifyPropertyChanged介面。

INotifyPropertyChanged定義在System.ComponentModel名稱空間。(這清楚地表明該介面不僅僅在Silverlight中,而且在.NET中扮演著非常重要的角色。)這就是業務物件提供資料變更通知的方法。 INotifyPropertyChanged的定義非常簡單,如下所示:

public interface INotifyPropertyChanged 
{ 
    event PropertyChangedEventHandler PropertyChanged; 
}

實現INotifyPropertyChanged介面的類只需要簡單地定義一個公共事件Property- Changed。理論上,這個派生類並不需要為這個事件做任何特殊的處理,但是當它的某個屬性發生變化時,可以通過這個事件來觸發變更事件。

PropertyChangedEventHandler委託與PropertyChangedEventArgs類相關聯,Pro- pertyChangedEventArgs類只有一個string型別的只讀屬性PropertyName,你要將發生變化的屬性名傳遞給PropertyChangedEventArgs的建構函式。

有時候實現INotifyPropertyChanged介面的類需要定義一個受保護的虛方法OnPropertyChanged,該方法具有一個PropertyChangedEventArgs型別的引數。這個方法不是必須的,但能給派生類帶來便利。我在這個例子中使用了該方法,因為在該方法中可以方便觸發事件。

由於實現了INotifyPropertyChanged介面的業務物件並沒有繼承FrameworkElement,它們並不是XAML檔案中視覺化樹的組成部分,因此它們通常會被例項化為XAML的資源或者位於程式碼隱藏檔案中。

簡單的繫結服務

有時我覺得使用業務物件的本意是在XAML檔案中進行繫結,這種繫結稱為繫結服務(binding server)。繫結服務公開了一些公共屬性,當這些屬性發生變化時,繫結服務會觸發相應的PropertyChanged事件。

例如,假設你想在Windows Phone 7應用程式中顯示當前時間,並且提供靈活的呈現方式。有時你只想顯示秒,並完全通過XAML來完成這個功能。例如,你想通過XAML顯示:“目前是X秒”,中間放一個每秒鐘更新一次的數字。當然這裡講述的技巧可以擴充套件到許多其他的應用中,而不僅僅是一個鐘錶應用。

雖然可以完全通過XAML實現整體的可視元素,但是你還是需要一些輔助的程式碼(可能是一個命名為Clock的類,該類包含了Year、Month、Day、DayOfWeek、Hour、Minute和Second等屬性。)我們將在XAML檔案中例項化這個Clock類並通過資料繫結來訪問它的屬性。

如你所知,在.NET中已經存在一個包含Year、Month和Day等相關屬性的結構體:DateTime。儘管DateTime對於編寫Clock類是必不可少的,但是它並不大符合我們的需求,因為DateTime的屬性不可以動態更改。相反,我要演示的Clock類所包含的屬性會實時地反映當前的時間,而且會通過PropertyChanged事件通知外部世界相應的變化。

Clock類包含在Petzold.Phone.Silverlight庫中,如下所示:

Silverlight專案:Petzold.Phone.Silverlight  檔案:Clock.cs
using System;
using System.ComponentModel;
using System.Windows.Threading; 

namespace Petzold.Phone.Silverlight
{
    public class Clock : INotifyPropertyChanged
    {
        int hour, min, sec;
        DateTime date;

        public event PropertyChangedEventHandler PropertyChanged;

        public Clock()
        {
            OnTimerTick(null, null);

            DispatcherTimer tmr = new DispatcherTimer();
            tmr.Interval = TimeSpan.FromSeconds(0.1);
            tmr.Tick += OnTimerTick;
            tmr.Start();


        }

        public int Hour
        {
            protected set
            {
                if (value != hour)
                {
                    hour = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Hour"));
                }
            }
            get 
            { 
                return hour; 
            }
        }
        public int Minute
        {
            protected set
            {
                if (value != min)
                {
                    min = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Minute"));
                }
            }
            get 
            { 
                return min; 
            }
        }

        public int Second
        {
            protected set
            {
                if (value != sec)
                {
                    sec = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Second"));
                }
            }
            get
            {
                return sec;
            }
        }

        public DateTime Date
        {
            protected set
            {
                if (value != date)
                {
                    date = value;


                    OnPropertyChanged(new PropertyChangedEventArgs("Date"));
                }
            }
            get
            {
                return date;
            }
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, args);
        }
      void OnTimerTick(object sender, EventArgs args)
        {
            DateTime dt = DateTime.Now;
            Hour = dt.Hour;
            Minute = dt.Minute;
            Second = dt.Second;
            Date = DateTime.Today;
        }
    }
}

Clock類實現了INotifyPropertyChanged介面,因此包含PropertyChanged公共事件。靠近結尾的地方,有一個受保護的OnPropertyChanged方法,負責觸發實際的事件。在Clock類的建構函式中建立DispatcherTimer的例項併為它的Tick事件註冊了一個事件處理程式,這個處理程式的呼叫間隔(Interval)為1/10秒。在類最底部的OnTimerTick處理程式負責給這個類的Hour、Minute、Second和Data屬性設定新值,這些屬性的結構都非常相似。

例如,看一下Hour屬性:

public int Hour 
{ 
    protected set 
    { 
        if (value != hour) 
        { 
            hour = value; 
            OnPropertyChanged(new PropertyChangedEventArgs("Hour")); 
        } 
    } 
    get  
    {  
        return hour;  
    } 
}

set儲存器是受保護的。因此該值只能在內部設定,我們並不希望外部的類設定該屬性。set存取器檢查正在設定的值與儲存在欄位中的值是否相同,如果不相同,就把新值設定到hour欄位中,同時呼叫OnPropertyChanged來觸發更新事件。

有些程式設計師不使用if語句來檢查屬性是否發生變化,其結果是一旦屬性被設定就馬上觸發PropertyChanged事件,即使該屬性的值並沒有發生變化也是如此。這並不是一個好主意,特別是像這樣的類。我們不希望每隔1/10秒PropertyChanged事件就報告Hour屬性發生了變化,其實該屬性每一小時才改變一次。

要在XAML檔案中使用Clock類,你需要宣告一個XML名稱空間來引用Petzold.Phone.Silverlight庫: xmlns:petzold="clr-namespace:Petzold.Phone.Silverlight;assembly=Petzold.Phone.Silverlight" 當繫結源不是DependencyObject的子類時,你會在Binding中使用Source屬性來代替ElementName屬性。我們要建立的繫結的Source屬性設定為Petzold.Phone.Silverlight庫中的Clock物件。

你可以直接在Binding中插入Clock類的引用:

<TextBlock> 
    <TextBlock.Text> 
        <Binding Path="Second"> 
            <Binding.Source> 
                <petzold:Clock /> 
            </Binding.Source> 
        </Binding> 
    </TextBlock.Text> 
</TextBlock>

Binding的Source屬性是一個屬性元素,並設定為Clock類的例項。Path屬性指向Clock類的Second屬性。 或者按照慣例把Clock類定義為XAML的資源:

<phone:PhoneApplicationPage.Resources> 
    <petzold:Clock x:Key="clock" /> 
    ... 
</phone:PhoneApplicationPage.Resources> 

然後就可以在Binding擴充套件標記中引用該資源了:

TextBlock Text="{Binding Source={StaticResource clock}, Path=Second}" /> 

注意StaticResource的嵌入標記表示式(embedded markup expression)。

TimeDisplay專案演示了這種方法,該專案使用了水平StackPanel來串聯文字:

Silverlight專案:TimeDisplay  檔案:MainPage.xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Center"
                VerticalAlignment="Center">
        <TextBlock Text="The current seconds are " />
        <TextBlock Text="{Binding Source={StaticResource clock}, 
                                  Path=Second}" />
    </StackPanel>
</Grid>

執行效果如圖12-4所示。

enter image description here

再次強調:繫結目標(例如TextBlock的Text屬性)必須是依賴屬性。為了使繫結目標隨著繫結源(例如Clock的Second屬性)的變化而更新,繫結源必須實現某種通知機制。

當然,我並不想使用承載多個TextBlock元素的StackPanel。可以使用StringFormat- Converter(我已在TimeDisplay專案中把它定義為資源,並指定了它的鍵為StringFormat,因此你現在可以使用它了),我可以簡單地在一個TextBlock中包括整段文字,如下所示:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 
    <TextBlock HorizontalAlignment="Center" 
               VerticalAlignment="Center" 
               Text="{Binding Source={StaticResource clock},  
                              Path=Second, 
                              Converter={StaticResource stringFormat}, 
                              ConverterParameter='The current seconds are {0}'}" /> 
</Grid>

現在Binding標記表示式包含了兩個嵌入標記表示式。

如果想顯示Clock類的多個屬性,你可以回去使用多個TextBlock元素。例如,可以通過冒號來分割時、分和秒,同時在分和秒前補0:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 
    <StackPanel Orientation="Horizontal" 
                HorizontalAlignment="Center" 
                VerticalAlignment="Center"> 
        <TextBlock Text="{Binding Source={StaticResource clock},  
                                  Path=Hour}" /> 
        <TextBlock Text="{Binding Source={StaticResource clock},  
                                  Path=Minute, 
                                  Converter={StaticResource stringFormat}, 
                                  ConverterParameter=':{0:D2}'}" /> 
        <TextBlock Text="{Binding Source={StaticResource clock},  
                                  Path=Second, 
                                  Converter={StaticResource stringFormat}, 
                                  ConverterParameter=':{0:D2}'}" /> 
    </StackPanel> 
</Grid>

如你所見,3個繫結都使用了相同的Source設定。是否有方法讓我們避免這種重複呢?有,並且這種技巧是一個非常重要的概念。

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

相關文章