使用DataConverter轉換繫結資料

by Vivid 14. 六月 2011 19:30

在WPF資料繫結應用程式中,若繫結來源與目標的資料型別相容,那麼WPF在資料繫結過程中會自動將資料轉換成適當型別。特別是在XAML中定義物件的屬性值時,不管屬性的型別為何,都是以字串方式表示,因此WPF提供預設轉換功能,利用TypeConverter類別自動將字串轉成適當的型別。本文將介紹WPF預設轉換子的行為,並說明如何進行客製化自行撰寫轉換子。

我們先看一下以下例子,使用一個MyIntDataClass自訂的資料類別做為繫結來源(Source),MyIntDataClass類別中包含一個int型別的MyIntData屬性。另外在WPF的視窗中有一個TextBox控制項做為繫結目標,請參考圖1所示:

clip_image002

圖 1:TextBox控制項Text屬性繫結到MyIntDataClass類別的MyIntData屬性。

MyIntDataClass自訂類別的程式如下所示:

MyIntDataClass自訂類別的程式如下所示:
  public class MyIntDataClass {
        public int MyIntData { get; set; }
    }

在WPF XAML檔案中,定義以下Window,使用Resources定義MyIntDataClass物件:

<Window
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "clr-namespace:wpfDataBinding2" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
    x:Class = "wpfDataBinding2.Window1 " x:Name = "Window"     Title = "__DefaultConvert"
    Width = "640" Height="480">

    <Window.Resources>
        <local:MyIntDataClass MyIntData = "100" x:Key = "MyIntDataClassDataSource" d:IsDataSource = "True" />
    </Window.Resources>
    <Grid x:Name = "LayoutRoot" DataContext = "{Binding Source={StaticResource MyIntDataClassDataSource}}" >
        <TextBox HorizontalAlignment = "Left" Height = "48" Margin = "176,80,0,0" TextWrapping = "Wrap" Text = "{Binding MyIntData, Mode=TwoWay}" VerticalAlignment = "Top" Width = "128" />       
        <Button Content = "Button" HorizontalAlignment = "Right" Height = "48" Margin = "0,96,152,0" VerticalAlignment = "Top" Width = "88" Click = "Button_Click" />
    </Grid>
</Window>

在Window.Resources區段中,定義一個MyIntDataClass物件,並設定MyIntData屬性值是「100」,在XAML中是以字串型式存在。但當你執行程式碼,WPF會自動將”100”字串轉換成數值,然後同步到MyIntDataClass物件MyIntData屬性中。因此,在XAML相關聯的cs檔案中,你可以利用以下程式碼來取得MyIntData屬性值,直接放到int型別的變數,型別轉換的動作會由WPF自動處理。

MyIntDataClass d = ( MyIntDataClass ) this.Resources [ "MyIntDataClassDataSource" ];
int data = d.MyIntData;
MessageBox.Show ( data.ToString());

若發生繫結來源與繫結目標的型別不相容時,WPF就無法自動進行轉換的動作,此時就必需自行轉寫轉換程式碼,利用資料轉換子來解決這個問題。

自訂資料轉換子

在WPF應用程式中經常需要使用者輸入資料,然後將資料進行格式化後呈現。例如在輸入貨幣資料時,使用者可能輸入一個數值,如1000,但你希望加上貨幣符號、千分位以及小數點資料,讓畫面看起來的結果類似是「NT$1,000.0000」,那麼你便需要撰寫資料轉換的程式碼,在WPF之中可以為自訂類別實作IValueConverter介面,撰寫自訂的資料轉換子(Data Converter)。

資料轉換子(Data Converter) 是一個類別,可以在來源(Source)與目標(Target)之間攔截資料,讓你自行操作這些資料。您可以利用System.Windows.Data命名空間下的ValueConversion Attribute來標示一個類別將被視為資料轉換子。此類別必需實作System.Windows.Data命名空間下的IValueConverter介面。這個介面包含兩個方法:Convert與ConvertBack方法,您需要自行撰寫這兩個方法來處理轉換邏輯。

以Expression Blend 4設計自訂資料轉換子的步驟如下,首先利用Expression Blend 4建立一個WPF Application應用程式專案。然後新增一個MyCurrencyConverter類別。從Expression Blend 4「Projects」視窗->選取專案->按滑鼠右鍵,再選取「Add New Item」,從「New Item」視窗中選取Class,並設定名稱為MyCurrencyConverter,請參考圖2所示。

clip_image004

圖 2:新增MyCurrencyConverter類別。

修改類別程式,在MyCurrencyConverter類別上方套用ValueConversion Attribute,這是要通知工具此為一個轉換子。ValueConversion Attribute建構函式傳入的第一個參數代表繫結來源的資料型別,本例為double型別。第二個參數代表繫結目標的資料型別,本例為string型別。另外MyCurrencyConverter類別實作了IValueConverter介面,這個介面包含Convert與ConvertBack兩個方法。

[ValueConversion( typeof(double), typeof(string)) ]
public class MyCurrencyConverter :IValueConverter
{
    public object Convert ( object value , Type targetType , object parameter , System.Globalization.CultureInfo culture ) {
        CultureInfo cinfo = CultureInfo.CurrentCulture;
        double d = ( double ) value;
        return d.ToString ( "c4" ,cinfo);
    }
    public object ConvertBack ( object value , Type targetType , object parameter , System.Globalization.CultureInfo culture ) {
        CultureInfo cinfo = CultureInfo.CurrentCulture;
        double d;
        double.TryParse ( ( string ) value , NumberStyles.Currency,cinfo , out d );
        return d;
    }
}

Convert方法傳入的第一個參數是要轉換的值(value),因此您可以直接讀取要轉換的資料,把轉換完的結果當做方法的回傳值傳回。例如本例將要轉換的數值格式化成貨幣格式,並加上小數點4位後回傳。ConvertBack方法則是將帶有貨幣符號、千分位及小數點的資料轉換回double型別回傳。

接著新增要用來當繫結來源的MyDataClass類別,從Expression Blend 4「Projects」視窗->選取專案->按滑鼠右鍵,再選取「Add New Item」,從「New Item」視窗中選取Class,並設定名稱為MyDataClass,修改程式如下,包含一個double 型別的MyProperty屬性:

public class MyDataClass {
        public double MyProperty { get; set; }
    }

在MainWindows.Xaml中加入兩個TextBox,第一個TextBox將會是繫結目標,第二個TextBox僅供測試用。

選取畫面上的TextBox,從Expression Blend 4屬性視窗點選Text屬性後方的「Advanced Options」->「Data Binding」->「+CLR Object」,,請參考圖3所示:

clip_image006

圖 3:新增一個物件資料來源。

在「Create Object Data Source」視窗中選取繫結來源類別為MyDataClass,請參考圖4所示:

clip_image008

圖 4:建立物件資料來源。

接著在「Data Field」頁設定TextBox的Text屬性繫結到MyDataClass的MyProperty,請參考圖5所示。

clip_image010

圖 5:設定TextBox的Text屬性繫結到MyDataClass的MyProperty。

點選「Create Data Binding」視窗下方「Show advanced properties」項目,選取「Value converter」為 「MyCurrencyConverter」,同時將「Binding direction」設定為「TwoWay」,做雙向繫結,請參考圖6所示。

clip_image012

圖 6:設定Value converter。

完成這些動作之後,Expression Blend 4產生出來的XAML如下,在 Window.Resources區段中定義了MyDataClass與MyCurrencyConverter轉換子,而TextBox的Text屬性的繫結語法中指明要使用此轉換字:

<Window
    Xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "clr-namespace:wpfDataBinding2" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
    x:Class="wpfDataBinding2.MainWindow " x:Name = "Window" Title = "__testDataConveter"
    Width = "640" Height = "480">

    <Window.Resources>
        <local:MyCurrencyConverter x:Key = "MyCurrencyConverter"/>
        <local:MyDataClass x:Key = "MyDataClassDataSource" d:IsDataSource = "True"/>
    </Window.Resources>

    <Grid x:Name = "LayoutRoot" DataContext = "{Binding Source={StaticResource MyDataClassDataSource}}">
        <TextBox Height = "40" Margin = "192,56,296,0" TextWrapping = "Wrap" Text = "{Binding MyProperty, Converter={StaticResource MyCurrencyConverter}, Mode=TwoWay}" VerticalAlignment = "Top"/>
        <TextBox Height = "40" Margin = "192,120,296,0" TextWrapping = "Wrap" Text = "TextBox" VerticalAlignment = "Top"/>
    </Grid>
</Window>

若執行程式測式,當你在第一個文字方塊輸入12345678後移動焦點,則文字方塊失去焦點時會顯示NT$1,234.5678,自動進行格式化。

轉換多個值

實作IValueConverter介面的轉換子類別只能夠轉換一個值。雖然在大部分情況下可以滿足需求,但有時卻略顯不足,特別是在使用多重繫結(MultiBinding)的情況。在WPF中可以使用MultiBinding將目標的一個屬性繫結到來源的多個屬性,但回傳一個值。例如有一個MyMultiDataClass資料類別定義如下:

public class MyMultiDataClass {
        public string MyYear { get; set; }
        public string MyMonth { get; set; }
        public string MyDay { get; set; }
    }

而你想把TextBox的Text屬性,繫結到MyMultiDataClass類別的MyYear、MyMonth、MyDay三個屬性,請參考圖7所示:

clip_image014

圖 7:繫結到多個屬性。

若要達到這樣的需求,你必需建立一個轉換子實作IMultiValueConverter,這個介面同樣會有兩個方法:Convert與ConvertBack。和IValueConverter介面不同的是,Convert方法會傳入一個object陣列,ConvertBack方法也會回傳一個object陣列。

以下這個範例類別的Convert方法第一個參數會存放年、月、日資料,在Convert方法中將其轉換成「年/月/日」格式,例如陣列包含「2011、3、30」三個值,就會被格式化為「2011/3/30」

public class MyMultiConverter : IMultiValueConverter {
  public object Convert ( object [ ] values , Type targetType , object parameter , CultureInfo culture ) {
            string year = values [ 0 ].ToString();
            string month = values [ 1 ].ToString ( );
            string day = values [ 2 ].ToString ( );
            return string.Format ( "{0}/{1}/{2}" , year , month , day );
        }

  public object [ ] ConvertBack ( object value , Type [ ] targetTypes , object parameter , CultureInfo culture ) {
            string [ ] o = value.ToString ( ).Split ( new char [ ] { ' ' } );
            return o;
        }
}

而ConvertBack方法則把傳入的「2011 3 30」字串根據空白切割後放到string陣列回傳。

針對多個值撰寫轉換子的方式和單一值的轉換子寫法差不多,但是在XAML中繫結的語法卻大不同,您要改用MultiBinding標籤來設定多個繫結。MultiBinding中可以包含多個Binding項目,必要時各別Binding相目可以有自己的轉換子,參考設定語法如下。

<Window
    Xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "clr-namespace:wpfDataBinding2" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
    x:Class = "wpfDataBinding2.MainWindow"     x:Name = "Window" Title="__MultiValue"
    Width = "640" Height = "480" Loaded = "Window_Loaded" >
    <Window.Resources>
        <local:MyMultiDataClass
        MyYear = "2011" MyMonth = "3" MyDay = "28"
        x:Key = "MyMultiDataClassDataSource" d:IsDataSource = "True" />
        <local:MyMultiConverter x:Key = "MyConverter" />
    </Window.Resources>
    <Grid x:Name = "LayoutRoot"
DataContext = "{Binding Source={StaticResource MyMultiDataClassDataSource}}" >
        <TextBox
        DataContext = "{Binding Source={StaticResource MyMultiDataClassDataSource}}"
        Height = "40" Margin = "240,32,216,0" TextWrapping = "Wrap"  VerticalAlignment = "Top" >
            <TextBox.Text>
                <MultiBinding   Converter = "{StaticResource MyConverter}" >
                    <Binding Path="MyYear" />
                    <Binding Path="MyMonth" />
                    <Binding Path="MyDay" />
                </MultiBinding>
            </TextBox.Text>
        </TextBox>
        <Button Content = "Button" Height = "23" HorizontalAlignment = "Left" Margin = "481,149,0,0" Name = "button1" VerticalAlignment = "Top" Width = "75" Click = "button1_Click_1" />
    </Grid>
</Window>

在MultiBinding 中Binding出現的順序會對應到Convert方法第一個參數陣列中項目的順序。範例在視窗中加入一個按扭,當你按下此按鈕,便可取得定義在<Windows.Resources>區段中的MyMultiDataClass物件,您可以取得他的屬性值,如MyMonth來加以比對是否為TextBox的Text屬性的最新值:

private void button1_Click_1 ( object sender , RoutedEventArgs e ) {
            MyMultiDataClass d = ( MyMultiDataClass ) this.Resources [ "MyMultiDataClassDataSource" ];
            MessageBox.Show ( d.MyMonth.ToString() );
        }

總結

在WPF資料繫結程式中,WPF預設轉換子提供相容型別的資料轉換動作。若要自訂一個值的轉換子需實作IValueConverter介面;若要轉換多個值,則需自訂一個類別實作IMultiValueConverter介面,然後搭配MultiBinding繫結到多個屬性。

 
   

Tags:

Expression Blend | WPF | 許薰尹Vivid Hsu | .NET Magazine國際中文電子雜誌

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List