【萬里征程——Windows App開發】資料繫結——簡單示例、更改通知、資料轉換

nomasp發表於2015-04-10

簡單的資料繫結示例

相比於理論,我更傾向於從實踐中開始部落格,尤其是對於資料繫結。那麼,我們先來看看幾個簡單的例子。

1.資料繫結到TextBox

我們依舊使用前面的鬧鐘類來開始。在下面的程式碼中,我們有屬性、建構函式,還有一個ToString()方法的過載。之所以過載這個方法是因為我們想在最後繫結的時候,這三個屬效能夠在TextBox上顯示得更加工整。

    public class Alarm
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public DateTime AlarmTime { get; set; }
        public Alarm() { }
        public Alarm(string title, string description,DateTime alarmTime)
        {
            Title = title;                  
            Description = description;
            AlarmTime = alarmTime;
        }
        public override string ToString()
        {
            return "Title: " + Title +"\n"+ "Time: "+ AlarmTime.ToString("d") + "\n"+ "Description: " + Description;
        }
    }

接下來再在XAML中新增TextBox控制元件如下,因為TextBox此時是用作顯示而非輸入,所以建議設定其的只讀屬性。資料繫結的核心就是Text屬性中的那麼一個Binding關鍵字。

<TextBox x:Name="textBox1" FontSize="28" Height="150" Width="400"
                    TextWrapping="Wrap" Text="{Binding}" IsReadOnly="True"/>

但是光這樣還不夠,我們還需要在後臺程式碼中將資料繫結到textBox1的DataContext(資料上下文)中。

textBox1.DataContext = new Alarm(
                "First Alarm", "I need to study!", new DateTime(2015, 4, 11));

相信大家並不為覺得這個很難,相反我在學資料繫結的時候一上來就是一大堆理論,以至於我對資料一詞有了陰影——所以我學資料結構非常痛苦。

這裡寫圖片描述

2.資料繫結到ComboBox

才儲存一個鬧鐘沒太大意思,我們多來幾個。

        public ObservableCollection<Alarm> UsefulAlarm = new ObservableCollection<Alarm>();
        public MainPage()
        {
            this.InitializeComponent();

            UsefulAlarm.Add(new Alarm("First Alarm", "I need to study!", new DateTime(2015, 4, 11)));
            UsefulAlarm.Add(new Alarm("First Alarm", "Read a magzine!", new DateTime(2015, 4, 12)));
            UsefulAlarm.Add(new Alarm("First Alarm", "Write a blog!", new DateTime(2015, 4, 15)));
            UsefulAlarm.Add(new Alarm("First Alarm", "Travel", new DateTime(2015, 5, 15)));

            textBox1.DataContext = UsefulAlarm;
        }

但是……

這裡寫圖片描述

很顯然我們用了ObservableCollection< T >類,它為資料繫結提供了一個集合,這是因為它實現了INotifyPropertyChanged和INotifyCollectionChanged介面。顧名思義,當屬性改變時,它可以通知它所繫結的控制元件,並且如果你希望該空間能夠同步更新,則將用於繫結的物件也實現INotifyPropertyChanged介面。這個類好歸好,但相對於TextBox而言算有些高階了,以至於它無法顯示出來。但是我們可以用ComboBox來代替它,我們的類並不需要修改,前面的UsefulAlarm例項化也都不用改,只需要將textBox1改成comboBox1即可。以下是新的ComboBox程式碼。

       <ComboBox Name="comboBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400">
            <ComboBox.ItemTemplate>
                <DataTemplate>               
                    <StackPanel Orientation="Vertical" Margin="8">
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
                    </StackPanel>                    
                </DataTemplate>
            </ComboBox.ItemTemplate>     
        </ComboBox>

在圖示中我們也容易發現TextBox和ComboBox兩個控制元件的Width屬性的應用區別。在TextBox中,我們將資料繫結到Text中;而在ComboBox中,我們則是將資料繫結到ItemsSource中,簡單的說就是ComboBox拿來所有的資料,再將它們分成小的細節發給它的子物件,這些子物件都在ComboBox的DataTemplate(資料容器)中。

這裡寫圖片描述

在這裡我們並沒有用到前面所過載的ToString()函式,因為我們已經分別將Title、Description、AlarmTime繫結到相應的TextBox控制元件了。那圖示中又為什麼這些資料都是一行一行的表示呢,這都是佈局控制元件StackPanel的功勞,全靠它的Orientation屬性。如果將這個屬性設定成Horizontal呢,那標題、描述已經時間就是全排在一行了。

這裡寫圖片描述

3.資料繫結到ListBox

聽說ListBox和ComboBox很類似哦,它們都是Box……XBox呀。博主我有點懶,那可不可以直接將ComboBox的名字改成ListBox就直接執行呢,答案是可以哦!那麼區別到底在哪裡呢?看看這張圖就知道啦。

這裡寫圖片描述

咦?怎麼只有一條鬧鐘了?別驚慌……拖動右邊的滾動條就可以檢視到全部的鬧鐘咯。我真的只把ComboBox改成ListBox還有相應的Name屬性(包括後臺程式碼中的名字哦),以下就是完整的程式碼啦,我會騙你?

        <ListBox Name="listBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="8">
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

4.資料繫結到ListView

看了前面的程式碼相信我沒有騙你吧,童鞋們看到ListBox有沒有想到ListView呢?我要是想說還是和前面一樣只用改名字等就可以用ListView,你還是不信麼?

        <ListView Name="listView1" ItemsSource="{Binding}"  FontSize="28" Height="150" Width="400">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="8">
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
                        <TextBox Width="350"  TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

這裡寫圖片描述

當然了,還是用右邊的滾動條來下拉以檢視所有的資料。不過ListView君的最佳姿勢不是這樣哦,將Height改為600才是呢。看下圖——這才是高大上的ListView君嘛!

這裡寫圖片描述

好了不玩了,GridView也是可以這樣弄得,不信你試試。

再談資料繫結

1.我們為什麼要用資料繫結

很顯然,我們不可能把所有的資料全部固定在特定的控制元件上。比如,遊戲的積分、設定的鬧鐘、天氣預報甚至的通訊類的訊息,它們都並非是一成不變的。但是也並非所有的控制元件都需要繫結,比如你的App的名字、傳送訊息時所用的傳送按鈕上面的文字等。

2.那資料和UI之間又有哪些關係呢

首先我們得明確,資料的顯示和其後臺的管理是不一樣的。資料與UI繫結之後,我們的資料就可以在這兩者之間進行溝通,如果資料發生變化時,繫結到資料的UI則會自動將相應的屬性進行調整,不僅僅是前面用到的Text屬性,還有FontSize、Width、Foreground、Image屬性都可以。

3.資料繫結到底是繫結什麼

首先,我們得有繫結源,這些就是我們需要繫結的資料,沒有資料,即使你繫結了,它也顯示不出來。
其次,我們還需要繫結目標,也就是Framework類的DependencyProperty屬性,說得白話文點就是將資料繫結到UI的相應屬性上。
最後,我們還需要一個Binding物件,它就像是搬運工,沒有它,資料也是無法動彈的。它能夠幫助我們將資料從資料來源移動到繫結目標,並且將繫結目標的相應訊息通知給繫結源。它還有一些巧妙的工具,能夠將繫結源的資料加工成特定的格式。

4.繫結源有哪些

所有的公共語言執行時物件,我們前面用的Alarm類就是這種物件,另外UI元素也是哦。

5.聽說有的搬運工只能將資料來源的資料一次性搬到繫結目標後就不再搬了,而有的搬運工則會在資料修改後再搬一次,甚至還有的能夠在繫結目標更改後再將資料搬回到資料來源

OneTime繫結:這個搬運工的工作就是第一種,它只負責在建立時將源資料更新到繫結目標。
OneWay繫結:這是系統預設的搬運工,它是第二種,負責在建立時以及源資料發生更改時更新繫結目標。
TwoWay繫結:這個搬運工則是第三種,它能夠在繫結源和繫結目標的一邊發生更改時同時更新繫結源和繫結目標。但它在一種時候卻會偷懶,那就是對於TextBox.Text每次點選之後,它就不會將這個Text屬性的更改更新到繫結源。不過如果碰到Boss,它也只能繼續搬了。那就是將Binding.UpdateSourceTrigger設定成PropertyChanged。而預設情況下,只有TextBox失去焦點時才會去更新。

以下分別是OneWay和TwoWay的例子:

        <StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Slider Name="slider1" Minimum="0" Maximum="100"/>
            <TextBox FontSize="30" 
                     Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" />
        </StackPanel>

拖動滑動條,就可以看到在TextBox中顯示它的值的變化了。如果希望它只變化一次,那就將程式碼中的OneWay改成OneTime即可。

這裡寫圖片描述

        <StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBox FontSize="30"  Name="textBox" Height="60"                
                     Text ="{Binding ElementName=listBox1, Path=SelectedItem.Content,  Mode=TwoWay}">
            </TextBox>   
            <ListBox FontSize="30" Name="listBox1">
                <ListBoxItem Content="Item 1"/>
                <ListBoxItem Content="Item 2"/>
                <ListBoxItem Content="Item 3"/>
                <ListBoxItem Content="Item 4"/>
            </ListBox>
        </StackPanel>

如下圖所示,點選Item 1後TextBox則會顯示相應的Item 1,將TextBox中的Item 1修改為Item 5後再ListBox中也自動修改成了Item5。

這裡寫圖片描述

這裡寫圖片描述

簡單示例:Foreground的資料繫結

前面已經說到了Foreground也可以繫結,想不想試試呢。我們現在TextBox中寫一個TextBox,然後在後臺程式碼中新增一個繫結就可以了。這個和前面的比較簡單,這裡只是用來引出後面的東東哦

 <TextBox Name="textBox" Width="200" Height="100" IsReadOnly="True"
                 FontSize="32" Text="Text" Foreground="{Binding ForeBrush}"/>
textBox.Foreground = new SolidColorBrush(Colors.BlueViolet);

更改通知

1.Silder繫結到TextBlock,不使用更改通知

首先定義一個簡單的類BindingSlider,同時在XAML中作如下定義。

    public class BindingSlider
    {
        private int sliderValue;
        public int SliderValue
        {
            get
            {
                return sliderValue;
            }
            set
            {
                sliderValue = value;
            }
        }
    }
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Slider Name="slider1" Minimum="0" Maximum="100" Width="200" Value="{Binding SliderValue,Mode=TwoWay}"/>
    <Button x:Name="button" Content="Button" Width="200" Click="button_Click"/>
    <TextBlock Name="textBlock" FontSize="30"/>
</StackPanel>

雖然這裡只是用到了OneWay傳遞,但還是需要使用TwoWay。因為在這裡OneWay是指從BindingSlider類的SliderValue屬性單向傳遞到Slider控制元件的Value屬性。但我們需要的則是Slider控制元件的Value屬性單向傳遞到BindingSlider類的SliderValue屬性,所以才得使用TwoWay方式。

        BindingSlider bindingSlider = new BindingSlider();
        public MainPage()
        {
            this.InitializeComponent();
            slider1.DataContext = bindingSlider;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            textBlock.Text = bindingSlider.SliderValue.ToString();
        }

首先例項化BindingSlider類,再在後臺程式碼中獎bindingSlider物件繫結到slider1的資料上下文。最後通過Click事件來將bindingSlider物件的SliderValue屬性傳遞給textBlock控制元件的Text屬性。

這裡的效果就是,拖動Slider但是TextBlock不會有變化,而需要Button來不斷的更改TextBlock的Text。如果想要TextBlock的Text能夠根據Slider實時的更改,這就需要”更改通知“了。

2.Silder繫結到TextBlock,使用更改通知

既然要使用通知更改的技術,那就可以在XAML程式碼中將Button控制元件刪除掉了,包括後臺程式碼中的Click事件。

緊接著來修改BindingSlider類,首先得使用INotifyPropertyChanged介面。這個介面有PropertyChanged事件,而這個事件則會告知繫結目標繫結源已經發生修改,這樣繫結目標也會實時的進行更改。在新的set中,我們將SliderValue值傳遞到NotifyPropertyChanged中。

    public class BindingSlider :INotifyPropertyChanged
    {
        private int sliderValue;
        public int SliderValue
        {
            get
            {
                return sliderValue;
            }
            set
            {
                sliderValue = value;
                NotifyPropertyChanged("SliderValue");     
            }
        }                                                                           
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }

最後我們還需要將bindingSlider物件繫結到textBlock的資料上下文。

        BindingSlider bindingSlider = new BindingSlider();
        public MainPage()
        {
            this.InitializeComponent();
            slider1.DataContext = bindingSlider;
            textBlock.DataContext = bindingSlider;           
        }

這樣一來就全部更改完成了,試試就會發現TextBlock的Text會根據Slider的拖動而實時修改了。

資料轉換

有時候預設的輸出方式不能滿足我們的需要,比如前面的OneWay示例,可能我們需要的是在TextBox中顯示“開始載入“、”載入一半了“、”很快就載入完了“以及”已經載入好“等,甚至還可以讓其能夠轉換成英文哦。

那麼首先新建一個類SliderValueConverter.cs,然後實現IValueConverter介面。然後按自己的需要寫它的Converter方法即可。

 public class SliderNotifyAndConverter : IValueConverter      
 {
       public object Convert(object value, Type targetType, object parameter, string language)
        {
            string valueTextBlock;
            string parameterValue = parameter.ToString();
            double valueSlider = (double)value;
            if (valueSlider > 0&&valueSlider<=5)
            {
                if (parameterValue == "zh-cn")
                    valueTextBlock = "開始載入";
                else
                    valueTextBlock = "Starts to load";
            }
            else if (valueSlider >= 45 && valueSlider <= 55)
            {
                if (parameterValue == "zh-cn")
                    valueTextBlock = "載入一半了";
                else
                    valueTextBlock = "loaded half";
            }
            else if (valueSlider >= 90&&valueSlider<100)
            {
                if (parameterValue == "zh-cn")
                    valueTextBlock = " 很快就載入完了";
                else
                    valueTextBlock = "finished loading very quickly";
            }
            else if (valueSlider == 100)
            {
                if (parameterValue == "zh-cn")
                    valueTextBlock = " 已經載入好";
                else
                    valueTextBlock = "loaded";
            }
            else
            {
                if (parameterValue == "zh-cn")
                    valueTextBlock = "載入中";
                else
                    valueTextBlock = "Loading";
            }
            return valueTextBlock;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
 }           

最後還需要在XAML中新增如下程式碼哦,值轉換器Converter所使用的靜態資源已經在

    <Page.Resources>
        <local:SliderNotifyAndConverter x:Key="SliderNotifyAndConverterResources"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Name="stackPanel" Width="450" Orientation="Vertical"
                    HorizontalAlignment="Center" VerticalAlignment="Center">
            <Slider Name="slider1" Minimum="0" Maximum="100"
                    Value="95"/>
            <TextBlock FontSize="30" 
                      Text="{Binding ElementName=slider1, Path=Value,  
                Converter={StaticResource SliderNotifyAndConverterResources}, 
                ConverterParameter='zh-cn'}"/>
            <TextBlock FontSize="30" 
                        Text="{Binding ElementName=slider1, Path=Value,  
                Converter={StaticResource SliderNotifyAndConverterResources}, 
                ConverterParameter='en-us'}"/>
        </StackPanel>
    </Grid>

以下是Slider的Value取不同值時TextBlock的不同顯示。

這裡寫圖片描述

這裡寫圖片描述

終於一口氣把自我感覺最難的資料繫結部分給寫完了,但願寫的還算清晰,歡迎指正。


歡迎大家點選左上角的“關注”或右上角的“收藏”方便以後閱讀。


為使本文得到斧正和提問,轉載請註明出處:
http://blog.csdn.net/nomasp

相關文章