现在的位置: 首页 > 综合 > 正文

WPF SDK研究 之 数据绑定

2011年09月21日 ⁄ 综合 ⁄ 共 6830字 ⁄ 字号 评论关闭
这一章介绍数据绑定。
本章共计27个示例,全都在VS2008下.NET3.5测试通过,点击这里下载:ConnectedData.rar

1ShowDataWithoutBinding
注:

<?Mapping XmlNamespace="local" ClrNamespace="TestBinding" ?>

语法已经升级为:

xmlns:local="clr-namespace:TestBinding"

这个例子讲的是在WPF中使用传统的绑定方式

数据绑定,就是要保持数据对象和UI界面的同步。

.NET事件绑定是基于Observer模式的。在.NET2.0中,对Observer进行了一次包装,可以引用System.Component命名空间,实现INotifyPropertyChanged接口,可以获得事件PropertyChanged,以及PropertyChangedEventArgs。于是在这套体系下,事件机制事先搭建好了。接口如下:

namespace System.ComponentModel
{
    
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

    
public interface INotifyPropertyChanged
    
{
        
event PropertyChangedEventHandler PropertyChanged;
    }


    
public class PropertyChangedEventArgs : EventArgs
    
{
        
public PropertyChangedEventArgs(string propertyName);
        
public virtual string PropertyName get; }
    }

}

从数据对象到UI界面:

当实现了INotifyPropertyChanged接口的对象有所改变时,会激发OnPropertyChanged这个接口方法该方法保证了UI界面的数据同步。

    从UI界面到数据对象:

    在控件的事件方法中,改变数据对象。原理同上,由于使用了控件内嵌的事件机制,从而更加简单了。

    示例1正是这种绑定方式的展现。现在的代码量还可以容忍,当对象数量的增加或者对象属性的增加时,我们就要额外添加更多的代码。这就造成了代码爆炸。所以,我们需要一种更灵活的绑定方式。

2ShowDataWithBinding

绑定有两种情形,一种是绑定一个对象到一个简单控件,另一种是绑定一组数据到一个数据列表控件。

这个例子讲的是前者。

WPF的数据绑定机制既保证了数据的同步性,又使得数据类型的相应转换正常进行。如下所示:

左边是XMALUI元素,右边是C#程序中的Object,中间是数据绑定层,将左右两层连接起来。

那么, 两个层次的语法如下:

UI层,有3种表示方式:

方法1

<TextBox Text="{Binding Path=Age}" />

方法2

<TextBox Text="{BindingAge}" />

方式3

<TextBox Width="100" Height="25">
    
<TextBox.Text>
      
<Binding Source="{StaticResource myDataSource}" Path="Age"/>
    
</TextBox.Text>
</TextBox>

注:绑定后从底层向上开始找数据源,直到发现位置为止,最上层是<Window>

Object层,要设置数据源:

grid1.DataContext = person; // person为对象,可写在XAML的资源中而省略这句,见下

相应前面的XAML中的TextBox控件:<TextBox Text="{Binding Age}" />,于是可以看到显示的是person对象的Age属性

结合Resource技术,可以全都写在XAML中而不用编写后台程序,这样做的前提是这个Object,在这里是Person,有一个用来初始化的构造函数。这时,DataContext绑定的是静态资源{StaticResource Tom}Tom是资源的Key

<Window
 
xmlns:local ="clr-namespace:ShowDataWithBinding">

 
<Window.Resources>
    
<local:Person x:Key="Tom" Name="Tom" Age="9" />
 
</Window.Resources>
 
<Grid DataContext="{StaticResource Tom}">
    
<TextBlock >Name:</TextBlock>
    
<TextBox Text="{Binding Path=Name}" />
    
<TextBlock >Age:</TextBlock>
    
<TextBox Text="{Binding Age}" />
    
<Button x:Name="birthdayButton">Birthday</Button>
 
</Grid>
</Window>

当然,按下Button后,后台还是有代码的:

public partial class Window1 : Window {
 
void birthdayButton_Click(object sender, RoutedEventArgs e) {
    Person person 
= (Person)this.FindResource("Tom"));

    
++person.Age;

    MessageBox.Show();
 }

}

注意这个FindResource()方法,找到keyTom的资源后,强制类型转换为Person

以上介绍的都是隐式的数据源,因为只有一个DataSource,所以可以不指定Source属性;当数据源多于1个的时候,这时要指定具体绑定那一个数据源了——称之为显示数据源,关键的是Source属性

    <TextBox Text="{Binding Path=Name, Source={StaticResource Tom}}" />

绑定其他类型数据

    以上介绍的都只是文本。接下来说的是如何绑定ForeColor这样的类型数据。

    现在考虑的是如果 Age25,则名字显示为红色。ForeColorBrush类型,Age为整型。

    WPF提供了接口IValueConverter,只要实现了该接口的两个方法,就可以完成这件工作:一个是Convert,另一个是ConvertBack,分别控制正反两个方向的转换。对于当前情况,新建一个类AgeToForegroundConverter,实现如下:

    public class AgeToForegroundConverter : IValueConverter
    
{
        
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
{
            Debug.Assert(targetType 
== typeof(Brush));
            
// DANGER! After 25, it's all down hill
            int age = int.Parse(value.ToString());
            
return (age > 25 ? Brushes.Red : Brushes.Black);
        }


        
public object ConvertBack…
    }

于是,在XAML中添加这个类AgeToForegroundConverter的资源,并设置相应的Convert属性即可:

    <Window.Resources>
        
<local:AgeToForegroundConverter x:Key="ATFC" />
    
</Window.Resources>
<TextBox…Text="{Binding Age}"
            Foreground
="{Binding Path=Age,Converter={StaticResource ATFC}}" />

总结,以上介绍的技术,只限于单独一个对象的绑定,可以取代前面介绍的INotifyPropertyChanged实现模式。

3ShowDataWithMultiBinding
这个例子讲的是多笔数据绑定。为此,将上节的Person聚集为泛型People类:
    class People : List<Person> { }
Window1展示的是如何显示当前项:

通过在XAML资源中添加数据源:

    <Window.Resources>
        
<local:People x:Key="Family">
            
<local:Person Name="Melissa" Age="36" />
            
<local:Person Name="Tom" Age="9" />
            
<local:Person Name="John" Age="11" />
        
</local:People>
    
</Window.Resources>

于是,可以直接绑定:

    <Grid DataContext="{StaticResource Family}" Name="grid1">
        
<TextBox Name="nameTextBox" Text="{Binding Path=Name}" />

注意,这次Grid绑定的是Famliy这个集合对象,因为只有一个TextBox,所以默认显示第一项"Melissa"
为了遍历这个数据集合,我们在其上建立view,重写按钮的Click方法:

        private void birthdayButton_Click(Object sender, RoutedEventArgs e)
        
{
            People people 
= (People)this.FindResource("Family");

            ICollectionView view 
= CollectionViewSource.GetDefaultView(people);

            Person person 
= (Person)view.CurrentItem;

            
++person.Age;

            MessageBox.Show(
string.Format("Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday");
        }

也就是通过CollectionViewSource类的静态GetDefaultView方法访问这个数据上的视图,获取到当前项,显示在UI上。

Window2展示的是向前和向后移动当前项。
Window1的一些公用语句抽象成GetFamilyView方法:

        ICollectionView GetFamilyView()
        
{
            People people 
= (People)this.FindResource("Family");
            
return CollectionViewSource.GetDefaultView(people);
        }

观察新添加的Back按钮事件方法:

        private void backButton_Click(object sender, RoutedEventArgs e)
        
{
            ICollectionView view 
= GetFamilyView();
            view.MoveCurrentToPrevious();
            
if (view.IsCurrentBeforeFirst)
            
{
                view.MoveCurrentToFirst();
            }

        }

为了方式越界,在移动前要判断是否到了尽头。
Prev的方法与Back方法大同小异。

Window3展示了如何把多笔数据绑定到列表控件,如ListBox。
        <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
这里没有Path属性,意为绑定到当前的所有对象。
ItemsSource属性设为"{Binding}",不用设置具体是哪一个Source,默认为找到的第一个数据源。
IsSynchronizedWithCurrentItem属性设置为True,保证了自身选项变化,其他绑定控件也相应跟着改变。这里,当点击Back按钮,ListBox的当前选中项也会随着一起改变。
遗憾的是,显示的并不是我们需要的数据,而是直接把每个ObjectType输出了。

Window4使用了数据模板,解决了Window3窗体遗留的问题,

        <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <StackPanel Orientation="Horizontal">

                        <TextBlock Text="{Binding Path=Name}" />

                        <TextBlock Text=" (age: " />

                        <TextBlock Text="{Binding Path=Age}"

                          Foreground="{Binding Path=Age, Converter={StaticResource ATFC}}" />

                        <TextBlock Text=")" />

                      </StackPanel>

                </DataTemplate>

            </ListBox.ItemTemplate>

        </ListBox>

通过设置ItemTemplate属性,可以显示任何形式的数据。使用方法很像ASP.NET中的ListBox模板。

Window5Window4使用的数据模板放置在Window资源中,从而

    <Window.Resources>

        <DataTemplate DataType="{x:Type local:Person}">

            <StackPanel Orientation="Horizontal">

           

        </DataTemplate>

    </Window.Resources>

使用标签的DataType属性标志这个数据模板是类型化的。现在,除非另外通知,每当WPF看到Person对象的一个实例:

        <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />

就会应用相应的数据模板。注意这个ItemTemplate属性。

Window6展示了排序技术

我们在前面使用到了ICollectionView接口view,可以对其进行排序,每种排序规则都是一个

抱歉!评论已关闭.