6. 數據模板數據模板為展示數據提供了極大的靈活性,我們繼續以前面的例子來看看它的能力。
<Window x:Class="Learn.WPF.Window1"
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 那樣將數據模板定義到資源中,以便在多個地方重復使用。
<Window x:Class="Learn.WPF.Window1"
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 屬性。
<Window x:Class="Learn.WPF.Window1"
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 似乎很麻煩,現在只需做些簡單的設置即可。
<Window x:Class="Learn.WPF.Window1"
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 class SexConverter : IValueConverter
{
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
<Window x:Class="Learn.WPF.Window1"
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",它的意思是 "暫時取消該綁定"。
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//return value.ToString() == "Male" ? "男" : "女";
return Binding.DoNothing;
}
注意,DoNothing 和 null 並不是一回事,null 是個有效返回值。
接下來,我們試著將 Sex 轉換成 Brush 類型,以便顯示不同的顏色。
Window1.xaml.cs
public class SexToBrushConverter : IValueConverter
{
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
<Window x:Class="Learn.WPF.Window1"
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 屬性中的轉換器用法,結果表明轉換器達到了上面例子中數據模板觸發器同樣的效果。