WPF数据绑定格式化技术详解
WPF数据绑定格式化技术详解
数据绑定基础
数据绑定是WPF应用程序中的核心概念,它允许UI元素与数据源自动同步。通过绑定,开发者可以减少大量样板代码,实现更高效的应用程序开发。
数据转换技术
使用字符串格式化属性
字符串格式化属性主要用于将数值转换为特定格式的文本显示。在WPF中,Binding对象提供了StringFormat属性来实现这一功能:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1"
Text="{Binding Path=单价, StringFormat={}{0:C}}">
</TextBox>
StringFormat值开头的一对花括号是转义序列,表示这不是一个标记扩展。需要注意的是,只有当StringFormat值以花括号开头时,才需要使用{}转义序列。
WPF列表控件同样支持对列表项进行字符串格式化。要使用此功能,只需设置列表控件的ItemStringFormat属性(定义在ItemsControl类中)。以下是一个显示产品价格的示例:
<ListBox Name="产品列表" DisplayMemberPath="单价" ItemStringFormat="{}{0:C}">
</ListBox>
值转换器原理
值转换器负责在源数据和目标UI之间进行数据转换。在数据被显示之前,以及在进行双向绑定的情况下,在目标值被应用回源数据之前,都会进行转换。
创建值转换器需要四个步骤:
- 创建实现IValueConverter接口的类
- 在类声明上添加ValueConversion特性,指定目标数据类型
- 实现Convert()方法,将数据从原始格式转换为显示格式
- 实现ConvertBack()方法,将数据从显示格式转换回本地格式
以下是一个处理价格的完整值转换器类:
[ValueConversion(typeof(decimal), typeof(string))]
public class 价格转换器 : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var 价格 = (decimal) value;
return 价格.ToString("C", culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var 价格 = value.ToString();
decimal 结果;
if (Decimal.TryParse(价格, NumberStyles.Any, culture, out 结果))
{
return 结果;
}
return value;
}
}
应用转换器时,需要创建一个价格转换器实例,并将其赋值给绑定对象的转换器属性:
<TextBlock Margin="7" Grid.Row="2">单价:</TextBlock>
<TextBox Margin="5" Grid.Row="2" Grid.Column="1">
<TextBox.Text>
<Binding Path="单价">
<Binding.Converter>
<local:价格转换器></local:价格转换器>
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
当相同的转换器需要在多个绑定中重复使用时,为每个绑定创建转换器实例是不必要的。更好的方法是在Resources集合中创建转换器对象,如下所示:
<Window.Resources>
<local:价格转换器 x:Key="价格转换器"></local:价格转换器>
</Window.Resources>
然后在XAML中引用资源:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1"
Text="{Binding Path=单价, Converter={StaticResource 价格转换器}}">
</TextBox>
创建对象转换器
值转换器作为数据类和UI显示之间的桥梁,可以实现复杂的数据类型转换。以下是一个将文件路径转换为BitmapImage对象的转换器:
public class 图片路径转换器 : IValueConverter
{
private string 图片目录 = Directory.GetCurrentDirectory();
public string 图片目录属性
{
get { return 图片目录; }
set { 图片目录 = value; }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var 图片路径 = Path.Combine(图片目录, (string)value);
return new BitmapImage(new Uri(图片路径));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("此方法未实现。");
}
}
要使用这个转换器,首先将其添加到资源中:
<Window.Resources>
<local:图片路径转换器 x:Key="图片路径转换器"></local:图片路径转换器>
</Window.Resources>
然后创建使用此转换器的绑定表达式:
<Image Margin="5" Grid.Row="2" Grid.Column="1" Stretch="None"
水平对齐方式="左" 来源=
"{Binding Path=产品图片路径, Converter={StaticResource 图片路径转换器}}">
</Image>
注意,Image.Source属性期望接收一个ImageSource对象,而BitmapImage类派生自ImageSource类。
条件格式化应用
例如,您可能希望突出显示价格较高的项目,为它们设置不同的背景颜色:
public class 价格转背景转换器 : IValueConverter
{
public decimal 最低高亮价格 {get; set;}
public Brush 高亮画笔{get; set;}
public Brush 默认画笔{get; set;}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var 价格 = (decimal)value;
if (价格 >= 最低高亮价格)
return 高亮画笔;
else
return 默认画笔;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
定义资源对象:
<local:价格转背景转换器 x:Key="价格转背景转换器"
默认画笔="{x:Null}" 高亮画笔="橙色" 最低高亮价格="50">
</local:价格转背景转换器>
在元素上应用值转换器:
<Border 背景=
"{Binding Path=单价, Converter={StaticResource 价格转背景转换器}}"
... >
多属性值评估
这个过程是不可逆的,即只能将多个值合并为一个结果,而不能将结果分解为多个属性。
结合多个属性的第一种方法是使用MultiBinding。以下示例展示了如何将名字和姓氏组合显示:
<TextBlock>
<TextBlock.文本>
<多绑定 字符串格式="{1}, {0}">
<绑定 路径="名字"></绑定>
<绑定 路径="姓氏"></绑定>
</多绑定>
</TextBlock.文本>
</TextBlock>
第二种方法是使用值转换器,实现IMultiValueConverter接口。
以下示例使用MultiBinding结合源对象的单价和库存数量,并通过一个值转换器计算它们的乘积:
<TextBlock>库存总价值: </TextBlock>
<文本框>
<文本框.文本>
<多绑定 转换器="{静态资源 库存价值转换器}">
<绑定 路径="单价"></绑定>
<绑定 路径="库存数量"></绑定>
</多绑定>
</文本框.文本>
</文本框>
注意,Convert()方法的values是一个对象数组,按照它们在MultiBinding中出现的顺序排列。在上一个示例中,首先出现单价,然后是库存数量。
public class 库存价值转换器 : IMultiValueConverter
{
public object 转换(object[] 值, 类型 目标类型, object 参数, 文化信息 文化)
{
var 单价 = (decimal)值[0];
var 库存数量 = (int)值[1];
return 单价 * 库存数量;
}
public object[] 转换回(object 值, 类型[] 目标类型, object 参数, 文化信息 文化)
{
throw new NotSupportedException();
}
}
列表控件基础
所有列表控件的基类是ItemsControl。
ItemsControl类的格式化相关属性
| 名称 | 描述 |
|---|---|
| ItemsSource | 绑定数据源(希望显示在列表中的集合或DataView)。 |
| DisplayMemberPath | 希望为每个数据项显示的属性。对于更复杂的表示或使用多个属性,请使用ItemTemplate代替。 |
| ItemStringFormat | .NET格式字符串,如果设置,将用于格式化每个项的文本。通常,此技术用于将数字或日期值转换为合适的显示表示,类似于Binding.StringFormat属性。 |
| ItemContainerStyle | 允许设置每个项容器的样式的样式。容器类型取决于列表类型(例如,对于ListBox类是ListBoxItem,对于ComboBox类是ComboBoxItem)。当列表被填充时,这些包装器对象会自动创建。 |
| ItemContainerStyleSelector | StyleSelector。使用代码为每个列表项的包装器选择样式。这允许为不同的列表项提供不同的样式。必须创建自定义StyleSelector。 |
| AlternationCount | 数据中交替样式的数量。例如,2表示交替2行样式,3表示交替3行样式,等等。 |
| ItemTemplate | 一个模板,从绑定对象中提取合适的数据,并将其排列到合适的控件组合中。 |
| ItemTemplateSelector | DataTemplateSelector,使用代码为每个列表项选择模板。这允许为不同的项提供不同的模板。必须创建自定义DataTemplateSelector类。 |
| ItemsPanel | 定义用于保存列表项的面板。所有项包装器都添加到此容器中。通常使用具有垂直(从上到下)方向的VirtualizingStackPanel。 |
| GroupStyle | 如果使用分组,这是定义每个组如何格式化的样式。使用分组时,项包装器(ListBoxItem、ComboBoxItem等)被添加到表示每个组的GroupItem包装器中,然后将这些组添加到列表中。 |
| GroupStyleSelector | StyleSelector。使用代码为每个组选择样式。这允许为不同的组提供不同的样式。必须创建自定义StyleSelector。 |
ItemsControl类的下一级是Selector类,它添加了一组描述所选项的属性。
Selector类添加的属性包括:SelectedItem、SelectedIndex、SelectedValue、SelectedValuePath。
注意,Selector类不支持多选。ListBox通过SelectionMode和SelectedItems属性支持多选。
列表样式定制
项容器样式
如果设置了ItemContainerStyle,当项被创建时,样式将应用到列表控件的每个项上。对于列表框控件,每个项是一个ListBoxItem对象(在组合框中是ComboBoxItem,等等)。因此,使用ListBox.ItemContainerStyle属性应用的任何样式都将用于设置每个ListBoxItem对象的属性。
<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称">
<列表框.项容器样式>
<样式>
<设置器 属性="列表框项.背景" 值="淡钢蓝色" />
<设置器 属性="列表框项.边距" 值="5" />
<设置器 属性="列表框项.内边距" 值="5" />
</样式>
</列表框.项容器样式>
</列表框>
带有触发器的样式允许在满足特定条件时设置控件属性:
<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称">
<列表框.项容器样式>
<样式 目标类型="{x:Type 列表框项}">
<设置器 属性="背景" 值="淡钢蓝色" />
<设置器 属性="边距" 值="5" />
<设置器 属性="内边距" 值="5" />
<样式.触发器>
<触发器 属性="是否已选中" 值="真">
<设置器 属性="背景" 值="深红色" />
<设置器 属性="前景色" 值="白色" />
<设置器 属性="边框画笔" 值="黑色" />
<设置器 属性="边框粗细" 值="1" />
</触发器>
</样式.触发器>
</样式>
</列表框.项容器样式>
</列表框>
带复选框或单选按钮的列表框
基本技术是修改代表每个列表项的控件模板。不要修改ListBox.Template属性,因为那是列表框本身的模板。需要修改ListBoxItem.Template属性。以下是带有单选按钮的模板:
<窗口.资源>
<样式 x:键="单选按钮列表样式" 目标类型="{x:Type 列表框}">
<设置器 属性="项容器样式">
<设置器.值>
<样式 目标类型="{x:Type 列表框项}" >
<设置器 属性="边距" 值="2" />
<设置器 属性="模板">
<设置器.值>
<控制模板 目标类型="{x:Type 列表框项}">
<单选按钮 可聚焦="假"
是否选中="{绑定 路径=是否已选中, 模式=双向,
相对源={相对源 模板父级} }">
<内容展示器></内容展示器>
</单选按钮>
</控制模板>
</设置器.值>
</设置器>
</样式>
</设置器.值>
</设置器>
</样式>
</窗口.资源>
直接设置列表框的样式:
<列表框 样式="{静态资源 单选按钮列表样式}" 名称="产品列表"
显示成员路径="型号名称">
复选框样式的列表框建立方法与单选列表框基本相同,只是需要允许列表框多选:
<样式 x:键="复选框列表样式" 目标类型="{x:Type 列表框}">
<设置器 属性="选择模式" 值="多选"></设置器>
<设置器 属性="项容器样式">
<设置器.值>
<样式 目标类型="{x:Type 列表框项}" >
<设置器 属性="边距" 值="2" />
<设置器 属性="模板">
<设置器.值>
<控制模板 目标类型="{x:Type 列表框项}">
<复选框 可聚焦="假"
是否选中="{绑定 路径=是否已选中, 模式=双向,
相对源={相对源 模板父级} }">
<内容展示器></内容展示器>
</复选框>
</控制模板>
</设置器.值>
</设置器>
</样式>
</设置器.值>
</设置器>
</样式>
交替项样式
AlternationCount是形成交替序列的项目数,超过此数后样式会交替。默认情况下,AlternationCount设置为0,不使用交替格式化。如果将AlternationCount设置为1,列表将在每个项之后交替,这允许您应用偶奇格式化模式。
给每个ListBoxItem一个AlternationIndex属性,允许您决定它在交替项序列中的编号。假设您设置AlternationCount为2,第一个ListBoxItem获得AlternationIndex为0,第二个获得1,第三个获得0,第四个获得1,以此类推。
<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称"
交替计数="2">
<列表框.项容器样式>
<样式 目标类型="{x:Type 列表框项}">
<设置器 属性="背景" 值="淡钢蓝色" />
<设置器 属性="边距" 值="5" />
<设置器 属性="内边距" 值="5" />
<样式.触发器>
<触发器 属性="项目控件.交替索引" 值="1">
<设置器 属性="背景" 值="淡蓝色" />
</触发器>
<触发器 属性="是否已选中" 值="真">
<设置器 属性="背景" 值="深红色" />
<设置器 属性="前景色" 值="白色" />
<设置器 属性="边框画笔" 值="黑色" />
<设置器 属性="边框粗细" 值="1" />
</触发器>
</样式.触发器>
</样式>
</列表框.项容器样式>
</列表框>
样式选择器
必须通过代码实现,创建一个派生自StyleSelector的专用类,覆盖SelectStyle()方法,选择合适的样式。
以下是一个基本的双样式选择器:
public class 按类别产品样式选择器 : 样式选择器
{
public override 样式 选择样式(object 项, 依赖对象 容器)
{
var 产品 = (产品)项;
var 窗口 = 应用程序.当前.主窗口;
if (产品.类别名称 == "旅行")
{
return (样式)窗口.查找资源("旅行产品样式");
}
else
{
return (样式)窗口.查找资源("默认产品样式");
}
}
}
以下代码使用了反射,是一个更通用的样式选择器:
public class 单条件高亮样式选择器 : 样式选择器
{
public 样式 默认样式
{
get; set;
}
public 样式 高亮样式
{
get; set;
}
public string 要评估的属性
{
get; set;
}
public string 高亮值
{
get; set;
}
public override 样式 选择样式(object 项, 依赖对象 容器)
{
产品 产品 = (产品)项;
// 使用反射获取要检测的属性
类型 类型 = 产品.GetType();
属性信息 属性 = 类型.GetProperty(要评估的属性);
// 根据属性值决定是否应该高亮此产品
if (属性.获取值(产品, null).ToString() == 高亮值)
{
return 高亮样式;
}
else
{
return 默认样式;
}
}
}
定义代码中使用的两个样式:
<窗口.资源>
<样式 x:键="默认样式" 目标类型="{x:Type 列表框项}">
<设置器 属性="背景" 值="淡黄色" />
<设置器 属性="内边距" 值="2" />
</样式>
<样式 x:键="高亮样式" 目标类型="{x:Type 列表框项}">
<设置器 属性="背景" 值="淡钢蓝色" />
<设置器 属性="字体粗细" 值="粗体" />
<设置器 属性="内边距" 值="2" />
</样式>
</窗口.资源>
内联使用样式选择器:
<列表框 名称="产品列表" 水平内容对齐="拉伸">
<列表框.项容器样式选择器>
<local:单条件高亮样式选择器
默认样式="{静态资源 默认样式}"
高亮样式="{静态资源 高亮样式}"
要评估的属性="类别名称"
高亮值="旅行"
>
</local:单条件高亮样式选择器>
</列表框.项容器样式选择器>
</列表框>
样式选择过程在首次绑定列表时执行一次。如果由于修改了决定样式的数据,样式不会自动更新。只能通过强制样式更新的方法:
var 选择器 = 产品列表.项容器样式选择器;
产品列表.项容器样式选择器 = null;
产品列表.项容器样式选择器 = 选择器;
数据模板技术
每个ListBoxItem只能绑定一个字段,无法直接组合多个字段。要显示多个字段,需要使用数据模板。数据模板是一段XAML标记,它定义了应该如何显示绑定数据对象。两种类型的控件支持数据模板:
- 内容控件通过ContentTemplate属性支持数据模板,用于显示Content属性。
- 列表控件通过ItemTemplate属性支持数据模板,用于显示集合中的每个项。集合通过ItemsSource提供。
列表项是一个内容控件。
数据模板应包含数据绑定表达式。
以下示例使用带圆角的边框包裹每个项,显示两片信息,并使用粗体格式化高亮型号:
<列表框 名称="产品列表" 水平内容对齐="拉伸">
<列表框.项模板>
<数据模板>
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色" 圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体"
文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1"
文本="{绑定 路径=型号名称}"></文本框>
</网格>
</边框>
</数据模板>
</列表框.项模板>
</列表框>
分离和重用模板
提取前一个示例的模板:
<窗口.资源>
<数据模板 x:键="产品数据模板">
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}">
</文本框>
<文本框 网格.行="1" 文本="{绑定 路径=型号名称}">
</文本框>
</网格>
</边框>
</数据模板>
</窗口.资源>
现在,您可以将数据模板作为静态资源添加到列表中:
<列表框 名称="产品列表" 水平内容对齐="拉伸"
项模板="{静态资源 产品数据模板}"></列表框>
可以通过设置DataTemplate.DataType属性为绑定数据的类型,在不同类型的控件中自动重用同一个数据模板。例如,前一个示例中,移除Key并指定绑定Product对象:
<窗口.资源>
<数据模板 数据类型="{x:Type local:产品}">
</数据模板>
</窗口.资源>
使用高级模板
以下示例是一个更复杂的数据模板,包含图片和按钮:
<窗口.资源>
<local:图片路径转换器 x:键="图片路径转换器"></local:图片路径转换器>
<数据模板 x:键="产品模板">
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1" 文本="{绑定 路径=型号名称}"></文本框>
<图片 网格.行="2" 网格.行跨距="2" 来源=
"{绑定 路径=产品图片路径, 转换器={静态资源 图片路径转换器}}">
</图片>
</网格>
</边框>
</数据模板>
</窗口.资源>
处理按钮点击事件。显然,所有按钮都将链接到同一个事件处理器,您需要在模板内部定义它。但是,您需要确定点击了哪个列表项。一个解决方案是在按钮的Tag属性中存储一些额外的识别信息,如下所示:
<数据模板>
<网格 边距="3">
<网格.列定义>
<列定义></列定义>
<列定义 宽度="自动"></列定义>
</网格.列定义>
<文本框 文本="{绑定 路径=类别名称}"></文本框>
<按钮 网格.列="2" 水平对齐方式="右" 内边距="2"
点击="查看按钮_点击" 标签="{绑定 路径=类别ID}">查看 ...</按钮>
</网格>
</数据模板>
您可以在事件处理器中获取Tag属性:
private void 查看按钮_点击(object 发送者, 路由事件参数 e)
{
var 按钮 = (按钮)发送者;
var 类别ID = (int)按钮.标签;
...
}
当定义绑定时,省略Path属性,您可以获取整个数据对象:
<按钮 水平对齐方式="右" 内边距="1"
点击="查看按钮_点击" 标签="{绑定}">查看 ...</按钮>
传递整个对象使更新列表选择更容易。在点击查看按钮之前,将选择移动到按钮被点击的列表项。如下所示:
var 按钮 = (按钮)发送者;
var 产品 = (产品)按钮.标签;
产品列表.选中项 = 产品;
不同模板的实现
以不同方式呈现列表项有三种主要方法:
- 数据触发器
- 值转换器
- 模板选择器
数据触发器示例:基于数据项的属性,设置模板元素的属性。例如,基于相应产品对象的CategoryName属性,可以更改包装每个列表项的自定义边框的背景。以下示例用红色文本突出显示工具目录中的产品:
<数据模板 x:键="默认模板">
<数据模板.触发器>
<数据触发器 绑定="{绑定 路径=类别名称}" 值="工具">
<设置器 属性="列表框项.前景色" 值="红色"></设置器>
</数据触发器>
</数据模板.触发器>
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体"
文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1"
文本="{绑定 路径=型号名称}"></文本框>
</网格>
</边框>
</数据模板>
值转换器方法:
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色" 圆角="4"
背景=
"{绑定 路径=类别名称, 转换器={静态资源 类别转颜色转换器}">
模板选择器实现
需要创建一个派生自DataTemplateSelector类的专用类,检查绑定对象并使用提供的逻辑选择合适的模板。
public class 单条件高亮模板选择器 : 数据模板选择器
{
public 数据模板 默认模板
{
get; set;
}
public 数据模板 高亮模板
{
get; set;
}
public string 要评估的属性
{
get; set;
}
public string 高亮值
{
get; set;
}
public override 数据模板 选择模板(object 项,
依赖对象 容器)
{
产品 产品 = (产品)项;
// 使用反射获取要检查的属性
类型 类型 = 产品.GetType();
属性信息 属性 = 类型.GetProperty(要评估的属性);
// 根据属性值决定是否应该高亮此产品
if (属性.获取值(产品, null).ToString() == 高亮值)
{
return 高亮模板;
}
else
{
return 默认模板;
}
}
}
以下是创建两个数据模板的标记:
<窗口.资源>
<数据模板 x:键="默认模板">
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框
文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1"
文本="{绑定 路径=型号名称}"></文本框>
</网格>
</边框>
</数据模板>
<数据模板 x:键="高亮模板">
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
背景="淡黄色" 圆角="4">
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体"
文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1" 字体粗细="粗体"
文本="{绑定 路径=型号名称}"></文本框>
<文本框 网格.行="2" 字体样式="斜体" 水平对齐方式="右">
*** 适合度假 ***</文本框>
</网格>
</边框>
</数据模板>
</窗口.资源>
以下是应用模板选择器的标记:
<列表框 名称="产品列表" 水平内容对齐="拉伸">
<列表框.项模板选择器>
<local:单条件高亮模板选择器
默认模板="{静态资源 默认模板}"
高亮模板="{静态资源 高亮模板}"
要评估的属性="类别名称"
高亮值="旅行"
>
</local:单条件高亮模板选择器>
</列表框.项模板选择器>
</列表框>
模板与选择交互
示例1:修改选中项的背景色:
Foreground属性使用属性继承,因此添加到模板的任何元素自动获得白色。Background属性不使用属性继承,但默认背景颜色是透明的。
首先,设置项容器的样式:
<列表框 名称="产品列表" 水平内容对齐="拉伸">
<列表框.项容器样式>
<样式>
<设置器 属性="控件.内边距" 值="0"></设置器>
<样式.触发器>
<触发器 属性="列表框项.是否已选中" 值="真">
<设置器 属性="列表框项.背景" 值="深红色" />
</触发器>
</样式.触发器>
</样式>
</列表框.项容器样式>
</列表框>
然后,修改数据模板:
<数据模板>
<网格 边距="0" 背景="白色">
<边框 边距="5" 边框粗细="1"
边框画笔="钢蓝色" 圆角="4"
背景="{绑定 路径=背景, 相对源={
相对源
模式=查找上级,
上级类型={x:Type 列表框项}
}}" >
<网格 边距="3">
<网格.行定义>
<行定义></行定义>
<行定义></行定义>
</网格.行定义>
<文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}"></文本框>
<文本框 网格.行="1" 文本="{绑定 路径=型号名称}"></文本框>
</网格>
</边框>
</网格>
</数据模板>
示例2:选中项后显示详细信息:
在数据模板中,使用Binding的RelativeSource属性搜索当前的ListBoxItem。如果它没有被选中,可以设置额外信息的Visibility属性来隐藏它。
使用数据触发器,当ListBoxItem的IsSelected属性改变时,修改容器的Visibility属性。
数据触发器只需放在要隐藏的容器内。
这是简化版,尚未实现自动展开功能:
<数据模板>
<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
圆角="4">
<堆栈面板 边距="3">
<文本框 文本="{绑定 路径=型号名称}"></文本框>
<堆栈面板>
<文本框 边距="3" 文本="{绑定 路径=描述}"
文本换行="自动" 最大宽度="250" 水平对齐方式="左"></文本框>
<图片 来源="{绑定 路径=产品图片路径, 转换器={静态资源 图片路径转换器}}">
</图片>
<按钮 字体粗细="常规" 水平对齐方式="右" 内边距="1"
标签="{绑定}">查看详情...</按钮>
</堆栈面板>
</堆栈面板>
</边框>
</数据模板>
最内层的StackPanel包含仅对选中项显示的内容,因此为此容器设置样式:
<堆栈面板>
<堆栈面板.样式>
<样式>
<样式.触发器>
<数据触发器
绑定="{绑定 路径=是否已选中, 相对源={
相对源
模式=查找上级,
上级类型={x:Type 列表框项}
}}"
值="假">
<设置器 属性="堆栈面板.可见性" 值="折叠" />
</数据触发器>
</样式.触发器>
</样式>
</堆栈面板.样式>
</堆栈面板>
在此示例中,需要使用DataTrigger而不是普通触发器,因为所需的属性在祖先元素中(ListBoxItem),唯一访问它的方法是使用数据绑定表达式。
改变项布局
通过设置ListBox的ItemsPanelTemplate属性为任何派生自System.Windows.Controls.Panel的类,可以改变项布局。
以下示例使用WrapPanel改变项布局:
<列表框 边距="7,3,7,10" 名称="产品列表"
项模板="{静态资源 项模板}"
滚动查看器.水平滚动条可见性="禁用">
<列表框.项面板>
<项面板模板>
<环绕面板></环绕面板>
</项面板模板>
</列表框.项面板>
</列表框>
``
还必须设置ScrollViewer.HorizontalScrollBarVisibility附加属性为Disabled。这确保ScrollViewer从不使用水平滚动条。
对于大量数据项,VirtualizingStackPanel具有更好的性能。
组合框高级用法
---
默认情况下,ComboBox是只读的。当设置IsReadOnly属性为false且IsEditable属性为true时,选择框会变成文本框,允许您输入任何文本。
组合框具有自动完成功能。要关闭它,设置ComboBox.IsTextSearchEnabled属性为false。此属性位于ItemsControl类中。
如果IsEditable属性为false(默认值),选择框将显示一个项目的精确视觉副本。
一个重要的细节是组合框显示的内容,而不是它的数据源是什么。例如,用Product对象填充组合框控件,并设置DisplayMemberPath属性为ModelName。这样,组合框将显示每个项目的ModelName属性。即组合框从一组Product对象获取信息,您的标记创建的是一个普通的文本列表。它将显示当前产品的ModelName,并且如果IsEditable为true且IsReadOnly为false,它将允许您编辑该值。
如果IsEditable属性为true,选择框显示它的逐字表示。简单地说,就是对项目调用ToString()。通常,它会显示类名的全称。
解决此问题最简单的方法是设置TextSearch.TextPath附加属性,指示组合框应该使用的内容。
<组合框 可编辑="真" 只读="真" 文本搜索.文本路径="型号名称" ...>