样式提供了基本的格式化能力,但它们不能消除到目前为止看到的列表的最重要的局限性:不管如何修改ListBoxItem,它都只是ListBoxItem,而不是功能更强大的元素组合。并且因为每个ListBoxItem只支持单个绑定字段,所以不可能实现包含多个字段或图像的富列表。
然而WPF另有一个工具可突破这个相当大的限制,并允许组合使用来自绑定对象的多个属性,并以特定的方式排列它们或显示比简单字符串更高级的可视化表示。这个工具就是数据模板。
数据模板是一块定义如何显示绑定的数据对象的XAML标记。有两种类型的控件支持数据模板:
内容控件 通过ContentTemplate属性支持数据模板。内容模板用于显示任何放置在Content属性中的内容。
列表控件(继承自ItemsControl类的控件) 通过ItemsTemplate属性支持数据模板。这个模板用于显示作为ItemsSource提供的集合中的每个项(或来自DataTable的每一行)。
基于列表的模板特性实际上以内容控件模板为基础,这是因为列表中的每个项均由内容控件封装。数据模板是一块普通的XAML标记。与其他XAML标记一样,数据模板可以包含任意元素的组合,还应当包含一个或多个数据绑定表达式,从而提取希望显示的信息。
分离和重用模板
数据模板可以写在应用模板控件标签下,也可以放在资源内作为公共资源,以便复用。放在资源标签内时,可以指定模板的Key并在应用控件里面通过Key关联模板,与常规的资源没什么两样;也可以指定DataType属性,会自动应用于绑定与DataType指定的数据类型相同的控件。
*** Great for vacations ***
改变模板
可以通过几种方式为同一个列表使用不同的模板
数据触发器 可根据绑定的数据对象中的属性值使用触发器修改模板中的属性。
值转换器 实现了IValueConverter接口的类,能够将值从绑定的对象转换为可用于设置模板中与格式化相关的属性的值。
模板选择器 模板选择器检查绑定的数据对象,并在几个不同模板之间进行选择。
数据触发器提供了最简单的方法。基本技术是根据数据项中的某个属性,设置模板中某个元素的某个属性。这种方法非常有用,但是不能改变与模板相关的复杂细节,只能修改模板或容器元素中的单个属性。此外触发器只能测试是否相等,不支持更复杂的比较条件。
值转换器在数据转换里面介绍过了,通过Converter属性设置,可以根据绑定的对象调整自身。
模板选择器是一种更强大的工具,可以根据不同的条件应用完全不同的模板。模板选择器继承自DataTemplateSelector,需要实现 SelectTemplate() 函数,返回应用的DataTemplate。
public class SingleCriteriaHighlightTemplateSelector : DataTemplateSelector
{
public System.Windows.DataTemplate DefaultTemplate { get; set; }
public System.Windows.DataTemplate HighlightTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container)
{
Order order = (Order)item;
if (order.Price >= 10000)
{
return HighlightTemplate;
}
else
{
return DefaultTemplate;
}
}
}
*** Great for vacations ***
完整代码如下:
MainWindow.xaml
*** Great for vacations ***
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace DataTemplate;
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty(ref T member, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer.Default.Equals(member, value))
{
return false;
}
member = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class Order : ViewModelBase
{
public decimal price = 0;
public decimal Price { get => price; set => SetProperty(ref price, value); }
public int volume = 0;
public int Volume { get => volume; set => SetProperty(ref volume, value); }
public DateTime orderDate = DateTime.Now;
public DateTime OrderDate { get => orderDate; set => SetProperty(ref orderDate, value); }
public string image = string.Empty;
public string Image { get => image; set => SetProperty(ref image, value); }
}
public class ImagePathConverter : IValueConverter
{
private string imageDirectory = Directory.GetCurrentDirectory();
public string ImageDirectory
{
get { return imageDirectory; }
set { imageDirectory = value; }
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string imagePath = Path.Combine(ImageDirectory, (string)value);
return new BitmapImage(new Uri(imagePath));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
}
public class SingleCriteriaHighlightTemplateSelector : DataTemplateSelector
{
public System.Windows.DataTemplate DefaultTemplate { get; set; }
public System.Windows.DataTemplate HighlightTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container)
{
Order order = (Order)item;
if (order.Price >= 10000)
{
return HighlightTemplate;
}
else
{
return DefaultTemplate;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myGrid.DataContext = this;
Order order1 = new Order();
Order order2 = new Order();
Order order3 = new Order();
Order order4 = new Order();
order1.Price = 100;
order1.Volume = 10;
order1.Image = "image1.gif";
order2.Price = 1000;
order2.Volume = 100;
order2.Image = "image2.gif";
order3.Price = 10000;
order3.Volume = 1000;
order3.Image = "image3.gif";
order4.Price = 100000;
order4.Volume = 10000;
order4.Image = "image4.gif";
Orders.Add(order1);
Orders.Add(order2);
Orders.Add(order3);
Orders.Add(order4);
}
public ObservableCollection Orders { get; set; } = new();
}