6. 数据模板
数据模板为展示数据提供了极大的灵活性,我们继续以前面的例子来看看它的能力。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>, </TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
很 显然,利用 ListBox.ItemTemplate.DataTemplate 我们可以用更复杂和更丰富的手段显示数据源对象,不再仅仅是通过 Path 显示某单个属性。不过通常情况下,我们会像 HTML/CSS 那样将数据模板定义到资源中,以便在多个地方重复使用。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
<DataTemplate x:Key="myData">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}"
ItemTemplate="{StaticResource myData}">
</ListBox>
</StackPanel>
</Grid>
</Window>
当然,我们也可以使用 CSS 那样的选择器来指定模板的应用类型,而不是在 ListBox 上设置 ItemTemplate 属性。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
<DataTemplate DataType="{x:Type my:Personal}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}">
</ListBox>
</StackPanel>
</Grid>
</Window>
通过 DataType="{x:Type my:Personal}" 我们就可以让所有使用 Personal 对象的地方自动使用这个模板设置。
利用数据模板,我们还可以做出复杂的效果来,比如根据 Personal.Sex 来显示一个不同颜色的边框。类似的做法在 WinForm 似乎很麻烦,现在只需做些简单的设置即可。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
<DataTemplate DataType="{x:Type my:Personal}">
<Border x:Name="border1" BorderBrush="Red" BorderThickness="1" Padding="5" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Sex}">
<DataTrigger.Value>Male</DataTrigger.Value>
<Setter TargetName="border1" Property="BorderBrush" Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}">
</ListBox>
</StackPanel>
</Grid>
</Window>
首先,我们在数据模板中增加了一个 Border,默认颜色是红色。然后利用触发器,当 Personal.Sex == Male 时,将边框颜色设置为蓝色。
7. 值转换器
某些时候,我们需要对绑定的源值进行类型或者显示格式转换,那么可以采用值转换器达到这个目的。比如我们可以将上面的 Sex 转换成 "男"、"女" 来显示。
Window1.xaml.cs
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString() == "Male" ? "男" : "女";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
转换器很简单,只需实现 IValueConverter 接口即可。
Window1.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
<my:SexConverter x:Key="sexConverter" />
<DataTemplate DataType="{x:Type my:Personal}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex, Converter={StaticResource sexConverter}}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}">
</ListBox>
</StackPanel>
</Grid>
</Window>
首先在资源中创建一个转换器实例,然后在数据模板中使用 Binding.Converter 来指定转换器实例即可。看看最终效果。
Convert() 有个有趣的的返回结果 "Binding.DoNothing",它的意思是 "暂时取消该绑定"。
{
//return value.ToString() == "Male" ? "男" : "女";
return Binding.DoNothing;
}
注意,DoNothing 和 null 并不是一回事,null 是个有效返回值。
接下来,我们试着将 Sex 转换成 Brush 类型,以便显示不同的颜色。
Window1.xaml.cs
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString() == "Male" ? Brushes.Blue : Brushes.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Window1.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Learn.WPF"
Title="Window1" Height="276" Width="360" WindowStartupLocation="CenterScreen">
<Window.Resources>
<my:PersonalList x:Key="personals" >
<my:Personal Name="Tom" Age="10" Sex="Male" />
<my:Personal Name="Mary" Age="15" Sex="Female" />
<my:Personal Name="Jack" Age="12" Sex="Male" />
</my:PersonalList>
<my:SexToBrushConverter x:Key="sexToBrushConverter" />
<DataTemplate DataType="{x:Type my:Personal}">
<Border x:Name="border1"
BorderBrush="{Binding Path=Sex, Converter={StaticResource sexToBrushConverter}}"
BorderThickness="1" Padding="5" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Age}" />
<TextBlock>,</TextBlock>
<TextBlock Text="{Binding Path=Sex}" />
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel DataContext="{StaticResource personals}">
<ListBox x:Name="listbox1" ItemsSource="{Binding}">
</ListBox>
</StackPanel>
</Grid>
</Window>
注意 Border.BorderBrush 属性中的转换器用法,结果表明转换器达到了上面例子中数据模板触发器同样的效果。