【万里征程——Windows App开发】数据绑定——简单示例、更改通知、数据转换

简单的数据绑定示例

相比于理论,我更倾向于从实践中开始博客,尤其是对于数据绑定。那么,我们先来看看几个简单的例子。

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

版权声明:本文为 NoMasp柯于旺 原创文章,如需转载请联系本人。

posted @ 2015-04-10 20:48  nomasp  阅读(197)  评论(0编辑  收藏  举报