WPF 基础 - DataTemplate
阅读原文时间:2023年07月08日阅读:3

如果把控件的功能视为内容,则可以使用控件模板 ControlTemplate 来控制它的展现;

如果把数据视为内容,则可以使用数据模板 DataTemplate 把数据展示出来;

ControlTemplate 是算法内容的表现形式,一个控件怎样组织其内部结构才让它更符合业务逻辑、让用户操作起来更舒服就是由它来控制的;

DataTemplate 是数据内容的表现形式,一条数据是简单的文本还是图形动画还是其他,由它决定。

1. DataTemplate

1.1 一条柱状图的 DataTemplate 举例

<DataTemplate x:Key="dt">
    <Grid>
        <StackPanel Orientation="Horizontal">
            <Grid>
                <Rectangle Width="{Binding Price}" Fill="LightSalmon"/>
                <TextBlock Text="{Binding Year}"></TextBlock>
            </Grid>
            <TextBlock Text="{Binding Price}"></TextBlock>
        </StackPanel>
    </Grid>
</DataTemplate>

1.2 常用 DataTemplate 的 3 处地方

ContentControl 的 ContentTemplate 属性,给 ContentControl 的内容穿衣服;

ItemsControl 的 ItemTemplate 属性,给 ItemsControl 的数据条目穿衣服;

GridViewColumn 的 CellTemplate 属性,给 GridViewColumn 单元格里的数据穿衣服。

1.3 使用 DataTemplate 实现一个汽车列表和详情

<Window x:Class="WpfAppTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="623">
    <Window.Resources>
        <local:NameToPhotoPathConverter x:Key="nameToPhotoPathConverter"/>

        <!--集合条目的内容模板-->
        <DataTemplate x:Key="carListItemViewTemplate">
            <Grid Margin="2">
                <StackPanel Orientation="Horizontal">
                    <Image Height="60" Width="64"
                           Source="{Binding Name, Converter={StaticResource nameToPhotoPathConverter}}" />
                    <StackPanel Margin="5 12">
                        <TextBlock Text="Name" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Name}"/>
                    </StackPanel>
                </StackPanel>
            </Grid>
        </DataTemplate>

        <!--详情的内容模板-->
        <DataTemplate x:Key="carDetailViewTemplate">
            <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6" Margin="2">
                <StackPanel Margin="5">
                    <Image Height="250" Width="400"
                           Source="{Binding Name, Converter={StaticResource nameToPhotoPathConverter}}" />
                    <StackPanel Orientation="Horizontal" Margin="5,0">
                        <TextBlock Text="Name:" FontWeight="Bold" FontSize="19"/>
                        <TextBlock Text="{Binding Name}" FontSize="19"/>
                    </StackPanel>
                    <StackPanel Orientation="Horizontal" Margin="5 0">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="Year:" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Year}"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="5 0">
                            <TextBlock Text="TopSpeed:" FontWeight="Bold"/>
                            <TextBlock Text="{Binding TopSpeed}"/>
                        </StackPanel>
                    </StackPanel>
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>

        <UserControl x:Name="carDetailControl"
                     ContentTemplate="{StaticResource carDetailViewTemplate}"
                     Content="{Binding ElementName=listBoxCar, Path=SelectedItem}" />

        <ListBox Grid.Column="1" x:Name="listBoxCar" Margin="2"
                 ItemTemplate="{StaticResource carListItemViewTemplate}" />
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitializeList();
    }

    /// <summary>
    /// 添加汽车种类
    /// </summary>
    void InitializeList()
    {
        List<Car> carList = new List<Car>();
        carList.Add(new Car() { Name = "BMW", TopSpeed = "260", Year = "1997" });
        carList.Add(new Car() { Name = "Audi", TopSpeed = "240", Year = "1999" });
        carList.Add(new Car() { Name = "Toyota", TopSpeed = "120", Year = "2016" });
        carList.Add(new Car() { Name = "Volkswagen", TopSpeed = "150", Year = "2000" });

        this.listBoxCar.ItemsSource = carList;
    }
 }

1.4 而如果不用 DataTemplate ,就会显得比较繁琐且冗长

使用 winform 的 Usercontrol 实现一个汽车列表和详情

<!--汽车详情-->
<UserControl x:Class="WpfAppTemplate.UserControls.CarDetailUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:WpfAppTemplate.UserControls"
             mc:Ignorable="d" >
    <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6" Margin="2">
        <StackPanel Margin="5">
            <Image x:Name="img" Height="250" Width="400"/>

            <StackPanel Orientation="Horizontal" Margin="5,0">
                <TextBlock Text="Name:" FontWeight="Bold" FontSize="19"/>
                <TextBlock x:Name="textBlockName" FontSize="19"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="5 0">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Year:" FontWeight="Bold"/>
                    <TextBlock x:Name="textBlockYear"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="5 0">
                    <TextBlock Text="TopSpeed:" FontWeight="Bold"/>
                    <TextBlock x:Name="textBlockTopSpeed"/>
                </StackPanel>
            </StackPanel>

        </StackPanel>
    </Border>
</UserControl>

public partial class CarDetailUserControl : UserControl
{
    public CarDetailUserControl()
    {
        InitializeComponent();
    }

    Car _currentCar;
    public Car CurrentCar
    {
        get { return _currentCar; }
        set
        {
            if (value == null) return;
            _currentCar = value;
            this.textBlockName.Text = value.Name;
            this.textBlockTopSpeed.Text = value.TopSpeed;
            this.textBlockYear.Text = value.Year;
            string uriStr = string.Format(@"/Resources/Images/Cars/{0}.jpg", value.Name);
            this.img.Source = new BitmapImage(new Uri(uriStr, UriKind.Relative));
        }
    }
}

<!--汽车条目-->
<UserControl x:Class="WpfAppTemplate.UserControls.CarListItemUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:WpfAppTemplate.UserControls"
             mc:Ignorable="d">
    <Grid Margin="2">
        <StackPanel Orientation="Horizontal">
            <Image x:Name="img" Height="60" Width="64"/>
            <StackPanel Margin="5 12">
                <TextBlock Text="Name" FontWeight="Bold"/>
                <TextBlock x:Name="textBlockName" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

public partial class CarListItemUserControl : UserControl
{
    public CarListItemUserControl()
    {
        InitializeComponent();
    }

    Car _currentCar;
    public Car CurrentCar
    {
        get { return _currentCar; }
        set
        {
            if (value == null) return;

            _currentCar = value;
            this.textBlockName.Text = value.Name;
            string uriStr = string.Format(@"/Resources/Images/Cars/{0}.jpg", value.Name);
            this.img.Source = new BitmapImage(new Uri(uriStr, UriKind.Relative));
        }
    }
}

<!--主窗体-->
<Window x:Class="WpfAppTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTemplate"
        xmlns:localUserControls="clr-namespace:WpfAppTemplate.UserControls"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="623">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>

        <localUserControls:CarDetailUserControl x:Name="carDetailControl"/>

        <ListBox Grid.Column="1" x:Name="listBoxCar" SelectionChanged="listBoxCar_SelectionChanged" Margin="2"/>
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitializeList();
    }

    /// <summary>
    /// 添加汽车种类
    /// </summary>
    void InitializeList()
    {
        List<Car> carList = new List<Car>();
        carList.Add(new Car() { Name = "BMW", TopSpeed = "260", Year = "1997" });
        carList.Add(new Car() { Name = "Audi", TopSpeed = "240", Year = "1999" });
        carList.Add(new Car() { Name = "Toyota", TopSpeed = "120", Year = "2016" });
        carList.Add(new Car() { Name = "Volkswagen", TopSpeed = "150", Year = "2000" });

        // 给每种汽车生成一个 汽车条目控件,作为一条内容添加到汽车列表控件的内容集合
        foreach (Car car in carList)
        {
            CarListItemUserControl control = new CarListItemUserControl();
            control.CurrentCar = car;
            this.listBoxCar.Items.Add(control);
        }
    }

    /// <summary>
    /// 当切换汽车时
    /// </summary>
    /// <param name="sender">listbox</param>
    /// <param name="e">SelectionChangedEventArgs, 可以通过 AddedItems 参数取到新选择的项</param>
    private void listBoxCar_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        CarListItemUserControl control = e.AddedItems[0] as CarListItemUserControl;
        if (control != null)
        {
            this.carDetailControl.CurrentCar = control.CurrentCar;
        }
    }
}