本章共计27个示例,全都在VS2008下.NET3.5测试通过,点击这里下载:ConnectedData.rar
1.ShowDataWithoutBinding
注:
数据绑定,就是要保持数据对象和UI界面的同步。
.NET事件绑定是基于Observer模式的。在.NET2.0中,对Observer进行了一次包装,可以引用System.Component命名空间,实现INotifyPropertyChanged接口,可以获得事件PropertyChanged,以及PropertyChangedEventArgs。于是在这套体系下,事件机制事先搭建好了。接口如下:
{
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正是这种绑定方式的展现。现在的代码量还可以容忍,当对象数量的增加或者对象属性的增加时,我们就要额外添加更多的代码。这就造成了代码爆炸。所以,我们需要一种更灵活的绑定方式。
2.ShowDataWithBinding
绑定有两种情形,一种是绑定一个对象到一个简单控件,另一种是绑定一组数据到一个数据列表控件。
这个例子讲的是前者。
WPF的数据绑定机制既保证了数据的同步性,又使得数据类型的相应转换正常进行。如下所示:
左边是XMAL的UI元素,右边是C#程序中的Object,中间是数据绑定层,将左右两层连接起来。
那么, 两个层次的语法如下:
UI层,有3种表示方式:
方法1:
方法2:
方式3:
<TextBox.Text>
<Binding Source="{StaticResource myDataSource}" Path="Age"/>
</TextBox.Text>
</TextBox>
注:绑定后从底层向上开始找数据源,直到发现位置为止,最上层是<Window>
Object层,要设置数据源:
相应前面的XAML中的TextBox控件:<TextBox Text="{Binding Age}" />,于是可以看到显示的是person对象的Age属性
结合Resource技术,可以全都写在XAML中而不用编写后台程序,这样做的前提是这个Object,在这里是Person,有一个用来初始化的构造函数。这时,DataContext绑定的是静态资源{StaticResource Tom},Tom是资源的Key:
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后,后台还是有代码的:
void birthdayButton_Click(object sender, RoutedEventArgs e) {
Person person = (Person)this.FindResource("Tom"));
++person.Age;
MessageBox.Show();
}
}
注意这个FindResource()方法,找到key为Tom的资源后,强制类型转换为Person
以上介绍的都是隐式的数据源,因为只有一个DataSource,所以可以不指定Source属性;当数据源多于1个的时候,这时要指定具体绑定那一个数据源了——称之为显示数据源,关键的是Source属性
<TextBox Text="{Binding Path=Name, Source={StaticResource Tom}}" />
绑定其他类型数据
以上介绍的都只是文本。接下来说的是如何绑定ForeColor这样的类型数据。
现在考虑的是如果 Age〉25,则名字显示为红色。ForeColor是Brush类型,Age为整型。
WPF提供了接口IValueConverter,只要实现了该接口的两个方法,就可以完成这件工作:一个是Convert,另一个是ConvertBack,分别控制正反两个方向的转换。对于当前情况,新建一个类AgeToForegroundConverter,实现如下:
{
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属性即可:
<local:AgeToForegroundConverter x:Key="ATFC" />
</Window.Resources>
<TextBox…Text="{Binding Age}"
Foreground="{Binding Path=Age,Converter={StaticResource ATFC}}" />
总结,以上介绍的技术,只限于单独一个对象的绑定,可以取代前面介绍的INotifyPropertyChanged实现模式。
3.ShowDataWithMultiBinding
这个例子讲的是多笔数据绑定。为此,将上节的Person聚集为泛型People类:
class People : List<Person> { }
Window1展示的是如何显示当前项:
通过在XAML资源中添加数据源:
<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>
于是,可以直接绑定:
<TextBox Name="nameTextBox" Text="{Binding Path=Name}" />
注意,这次Grid绑定的是Famliy这个集合对象,因为只有一个TextBox,所以默认显示第一项"Melissa"。
为了遍历这个数据集合,我们在其上建立view,重写按钮的Click方法:
{
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方法:
{
People people = (People)this.FindResource("Family");
return CollectionViewSource.GetDefaultView(people);
}
观察新添加的Back按钮事件方法:
{
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的当前选中项也会随着一起改变。
遗憾的是,显示的并不是我们需要的数据,而是直接把每个Object的Type输出了。
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模板。
Window5把Window4使用的数据模板放置在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,可以对其进行排序,每种排序规则都是一个