数据绑定回顾
见601页。
数据转换
使用StringFormat属性
StringFormat属性适用于显示数字为文本。
使用Binding对象的StringFormat属性,可以将绑定对象的属性转化为指定格式的文本:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=UnitCost, StringFormat={}{0:C}}"> TextBox>
StrngFormat值开始的成对花括号,是逃跑序列,表式StringFormat值不是标记扩展。
顺便,只有StringFormat值以花括号开头时,才需要{}逃跑序列。
WPF列表控件也支持字符串格式化列表项目。为使用它,你简单地设置列表的ItemStringFormat属性(定义在ItemsControl类)。这是一个例子,列出产品价格:
<ListBox Name="lstProducts" DisplayMemberPath="UnitCost" ItemStringFormat="{}{0:C}"> ListBox>
介绍值转换器
值转换器负责源数据和目标的转换。恰好在源数据被显示之前转换,以及在两路绑定的情况下,恰好在目标值被应用回源数据之前转换。
创造值转换器,需要4步:
- 创造实现IValueConverter接口的类
- 添加特性ValueConversion属性到类声明,指定目标数据类型。
- 实现Convert()方法,此方法改变数据,从原始格式到显示格式。
- 实现ConvertBack()方法,此方法反向改变值,从显示格式到本地格式。
这是处理价格的完整的值转换器类:
[ValueConversion(typeof(decimal), typeof(string))] public class PriceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var price = (decimal) value; return price.ToString("C", culture); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { var price = value.ToString(); decimal result; if (Decimal.TryParse(price, NumberStyles.Any, culture, out result)) { return result; } return value; } }
应用转换器,创建一个PriceConverter实例,将它赋值给绑定对象的转换器属性:
<TextBlock Margin="7" Grid.Row="2">Unit Cost:TextBlock> <TextBox Margin="5" Grid.Row="2" Grid.Column="1"> <TextBox.Text> <Binding Path="UnitCost"> <Binding.Converter> <local:PriceConverter>local:PriceConverter> Binding.Converter> Binding> TextBox.Text> TextBox>
在许多情况下,相同的转换器被用于多个绑定。在这种情况下,为每个绑定创造转换器的一个实例毫无意义。而是,在Resources集合创造转换器对象,如下所示:
<Window.Resources> <local:PriceConverter x:Key="PriceConverter">local:PriceConverter> Window.Resources>
然后引用资源:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=UnitCost, Converter={StaticResource PriceConverter}}"> TextBox>
用值转换器创建对象
值转换器是连接数据类和窗口显示的桥梁。
这是实现文件路径到BitmapImage对象转换的类:
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, CultureInfo culture) { var imagePath = Path.Combine(ImageDirectory, (string)value); return new BitmapImage(new Uri(imagePath)); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException("The method or operation is not implemented."); } }
为了使用这个转换器,添加它到资源:
<Window.Resources> <local:ImagePathConverter x:Key="ImagePathConverter">local:ImagePathConverter> Window.Resources>
创造一个使用此转换器的绑定表达式:
<Image Margin="5" Grid.Row="2" Grid.Column="1" Stretch="None" HorizontalAlignment="Left" Source= "{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}"> Image>
注意,Image.Source属性期待一个ImageSource对象,而BitmapImage类派生自ImageSource类。
应用条件格式
例如,你希望标记高价的项目,给他们一个不同的背景颜色:
public class PriceToBackgroundConverter : IValueConverter { public decimal MinimumPriceToHighlight {get; set;} public Brush HighlightBrush{get; set;} public Brush DefaultBrush{get; set;} public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var price = (decimal)value; if (price >= MinimumPriceToHighlight) return HighlightBrush; else return DefaultBrush; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
定义资源对象:
<local:PriceToBackgroundConverter x:Key="PriceToBackgroundConverter" DefaultBrush="{x:Null}" HighlightBrush="Orange" MinimumPriceToHighlight="50"> local:PriceToBackgroundConverter>
在元素上应用值转换器:
<Border Background= "{Binding Path=UnitCost, Converter={StaticResource PriceToBackgroundConverter}}" ... >
估值多个属性
这个过程是不可逆的,也就是只能将多个值合并为一个结果。而不能将结果分解为多个属性。
结合多个属性的第一个方法是使用MultiBinding。下面例子:
<TextBlock> <TextBlock.Text> <MultiBinding StringFormat="{1}, {0}"> <Binding Path="FirstName">Binding> <Binding Path="LastName">Binding> MultiBinding> TextBlock.Text> TextBlock>
第二个方法是使用值转换器,实现的是IMultiValueConverter。
MultiBinding使用源对象的UnitCost和UnitsInStock属性,并且使用一个值转换器结合他们:
<TextBlock>Total Stock Value: TextBlock> <TextBox> <TextBox.Text> <MultiBinding Converter="{StaticResource ValueInStockConverter}"> <Binding Path="UnitCost">Binding> <Binding Path="UnitsInStock">Binding> MultiBinding> TextBox.Text> TextBox>
注意Convert()方法的values是一个对象数组,按照他们在MultiBinding中的顺序放置这些值。在前一个示例中,首先出现UnitCost,然后是UnitsInStock。
public class ValueInStockConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var unitCost = (decimal)values[0]; var unitsInStock = (int)values[1]; return unitCost * unitsInStock; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { 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
如果ItemContainerStyle被设置,当项目被创造时,样式将向下传递到列表控件的每个项目。在列表框控件的情况,每个项目是一个ListBoxItem对象。(在组合框,它是ComboBoxItem,等等。)因而,你用ListBox.ItemContainerStyle属性应用任何样式被用来设置每个ListBoxItem对象的属性。
<ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName"> <ListBox.ItemContainerStyle> <Style> <Setter Property="ListBoxItem.Background" Value="LightSteelBlue" /> <Setter Property="ListBoxItem.Margin" Value="5" /> <Setter Property="ListBoxItem.Padding" Value="5" /> Style> ListBox.ItemContainerStyle> ListBox>
带触发器的样式,样式中的触发器是在满足某个前提条件的情况下,设置控件的属性:
<ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="LightSteelBlue" /> <Setter Property="Margin" Value="5" /> <Setter Property="Padding" Value="5" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="DarkRed" /> <Setter Property="Foreground" Value="White" /> <Setter Property="BorderBrush" Value="Black" /> <Setter Property="BorderThickness" Value="1" /> Trigger> Style.Triggers> Style> ListBox.ItemContainerStyle> ListBox>
带复选框或单选按钮的列表框
基本技术是修改代表每个列表项目容器的控件模板。不要修改ListBox.Template属性,因为它是列表框的模板。你需要修改ListBoxItem.Template属性。这里是带有单选按钮的模板:
<Window.Resources> <Style x:Key="RadioButtonListStyle" TargetType="{x:Type ListBox}"> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="{x:Type ListBoxItem}" > <Setter Property="Margin" Value="2" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <RadioButton Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent} }"> <ContentPresenter>ContentPresenter> RadioButton> ControlTemplate> Setter.Value> Setter> Style> Setter.Value> Setter> Style> Window.Resources>
直接设置列表框的样式:
<ListBox Style="{StaticResource RadioButtonListStyle}" Name="lstProducts" DisplayMemberPath="ModelName">
复选框样式的列表框建立方法与单选列表框基本相同。只是允许列表框多选:
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}"> <Setter Property="SelectionMode" Value="Multiple">Setter> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="{x:Type ListBoxItem}" > <Setter Property="Margin" Value="2" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <CheckBox Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent} }"> <ContentPresenter>ContentPresenter> CheckBox> ControlTemplate> Setter.Value> Setter> Style> Setter.Value> Setter> Style>
交替项目样式
AlternationCount是形成一个序列的项目数,在此数目以后样式交替。默认情况下,AlternationCount被设置为0,并且不使用交替格式化。如果你设置AlternationCount为1,列表将在每个项目以后交替,这允许你应用偶奇格式化模式。
给每个ListBoxItem一个AlternationIndex属性,允许你决定它在交替项目的序列如何编号。假设你设置AlternationCount为2,第一ListBoxItem获得一个AlternationIndex的0,第二获得一个AlternationIndex的1,第三获得一个AlternationIndex的0,第四获得一个AlternationIndex的1,等等。
<ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName" AlternationCount=”2”>> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="LightSteelBlue" /> <Setter Property="Margin" Value="5" /> <Setter Property="Padding" Value="5" /> <Style.Triggers> <Trigger Property="ItemsControl.AlternationIndex" Value="1"> <Setter Property="Background" Value="LightBlue" /> Trigger> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="DarkRed" /> <Setter Property="Foreground" Value="White" /> <Setter Property="BorderBrush" Value="Black" /> <Setter Property="BorderThickness" Value="1" /> Trigger> Style.Triggers> Style> ListBox.ItemContainerStyle> ListBox>
样式选择器
必须用代码方式实现,从StyleSelector派生一个专用的类,覆盖SelectStyle()方法,选择合适的样式。
这里是一个基本的选择器,二样式选择一个:
public class ProductByCategoryStyleSelector : StyleSelector { public override Style SelectStyle(object item, DependencyObject container) { var product = (Product)item; var window = Application.Current.MainWindow; if (product.CategoryName == "Travel") { return (Style)window.FindResource("TravelProductStyle"); } else { return (Style)window.FindResource("DefaultProductStyle"); } } }
下面代码使用了反射,更为通用的一个样式选择器:
public class SingleCriteriaHighlightStyleSelector : StyleSelector { public Style DefaultStyle { get; set; } public Style HighlightStyle { get; set; } public string PropertyToEvaluate { get; set; } public string PropertyValueToHighlight { get; set; } public override Style SelectStyle(object item, DependencyObject container) { Product product = (Product)item; // 使用反射获得要检测的属性 Type type = product.GetType(); PropertyInfo property = type.GetProperty(PropertyToEvaluate); // 根据属性值决定是否这个产品应该被高亮 if (property.GetValue(product, null).ToString() == PropertyValueToHighlight) { return HighlightStyle; } else { return DefaultStyle; } } }
定义代码中用到的两个样式:
<Window.Resources> <Style x:Key="DefaultStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="LightYellow" /> <Setter Property="Padding" Value="2" /> Style> <Style x:Key="HighlightStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="LightSteelBlue" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="Padding" Value="2" /> Style> Window.Resources>
内联使用样式选择器:
<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch"> <ListBox.ItemContainerStyleSelector> <local:SingleCriteriaHighlightStyleSelector DefaultStyle="{StaticResource DefaultStyle}" HighlightStyle="{StaticResource HighlightStyle}" PropertyToEvaluate="CategoryName" PropertyValueToHighlight="Travel" > local:SingleCriteriaHighlightStyleSelector> ListBox.ItemContainerStyleSelector> ListBox>
样式选择过程,当第一次绑定列表时,被执行一次。如果,由于修改了决定样式的数据,样式不会自动改变。只能用暴力的方法强制样式更新:
var selector = lstProducts.ItemContainerStyleSelector; lstProducts.ItemContainerStyleSelector = null; lstProducts.ItemContainerStyleSelector = selector;
数据模板
每个ListBoxItem只能绑定一个字段,没有办法结合多个字段。如果要显示多个字段,这就需要用到数据模板。数据模板是一块XAML标记。它定义了应该如何显示一个绑定数据对象。二类型的控件支持数据模板:
- 内容控件通过ContentTemplate属性支持数据模板。内容模板被用来显示Content属性。
- 列表控件通过ItemTemplate属性支持数据模板。这模板被用来显示集合每个项目。集合通过ItemsSource提供。
列表项目是一个内容控件。
数据模板应该包括数据绑定表达式。
这是一个例子,用一个导圆角的边界包裹每个项目,显示二片信息,并且使用粗体的格式化高亮模型数:
<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplate> <DataTemplate> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">TextBlock> Grid> Border> DataTemplate> ListBox.ItemTemplate> ListBox>
分离和重用模板
提取前一个例子的模板:
<Window.Resources> <DataTemplate x:Key="ProductDataTemplate"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}"> TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}"> TextBlock> Grid> Border> DataTemplate> Window.Resources>
现在,你能添加你的数据模板到列表,使用一个静态资源:
<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch" ItemTemplate="{StaticResource ProductDataTemplate}">ListBox>
自动地在不同的类型的控件重用同一个数据模板。设置DataTemplate.DataType属性为绑定数据的类型。例如,前一个例子,移除Key并指定绑定Product对象:
<Window.Resources> <DataTemplate DataType="{x:Type local:Product}"> DataTemplate> Window.Resources>
使用更高级的模板
第一个例子,
<Window.Resources> <local:ImagePathConverter x:Key="ImagePathConverter">local:ImagePathConverter> <DataTemplate x:Key="ProductTemplate"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">TextBlock> <Image Grid.Row="2" Grid.RowSpan="2" Source= "{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}"> Image> Grid> Border> DataTemplate> Window.Resources>
直接一个模板内部放置控件。例如,一列种类。挨着每个种类是一个View按钮。你能使用按钮发射另一个窗口恰好匹配在那种类产品。
处理按钮点击。明显地,所有的按钮将被链接到同样的事件处理器,你在模板内部定义它。但是,你需要决定哪一个列表项目被点击。一个解决方案是存储一些额外的识别信息在按钮的Tag属性,如下所示:
<DataTemplate> <Grid Margin="3"> <Grid.ColumnDefinitions> <ColumnDefinition>ColumnDefinition> <ColumnDefinition Width="Auto">ColumnDefinition> Grid.ColumnDefinitions> <TextBlock Text="{Binding Path=CategoryName}">TextBlock> <Button Grid.Column="2" HorizontalAlignment="Right" Padding="2" Click="cmdView_Clicked" Tag="{Binding Path=CategoryID}">View ...Button> Grid> DataTemplate>
你能取回Tag属性,在事件处理器中:
private void cmdView_Clicked(object sender, RoutedEventArgs e) { var cmd = (Button)sender; var categoryID = (int)cmd.Tag; ... }
当你定义绑定时,遗漏Path属性,你能抓取整个数据对象:
<Button HorizontalAlignment="Right" Padding="1" Click="cmdView_Clicked" Tag="{Binding}">View ...Button>
传递整个的对象使更新列表选择更容易。当点击View按钮之前,移动选择到按钮被点击的列表项目。如下所示:
var cmd = (Button)sender; var product = (Product)cmd.Tag; lstCategories.SelectedItem = product;
不同的模板
以不同的方式呈现列表项目:
- 数据触发器
- 值转换器
- 模板选择器
数据触发器。基于数据项目的一个属性,设置模板元素的一个属性。例如,基于相应产品对象的CategoryName属性,你能改变包裹每个列表项目的自定义边界的背景。这是一个例子,用红字高亮在工具目录中的产品:
<DataTemplate x:Key="DefaultTemplate"> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=CategoryName}" Value="Tools"> <Setter Property="ListBoxItem.Foreground" Value="Red">Setter> DataTrigger> DataTemplate.Triggers> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">TextBlock> Grid> Border> DataTemplate>
值转换器方法:
<Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4" Background= "{Binding Path=CategoryName, Converter={StaticResource CategoryToColorConverter}">
模板选择器
你需要创造一个派生自DataTemplateSelector类。检查绑定对象并且使用你提供逻辑选择一个合适的模板。
public class SingleCriteriaHighlightTemplateSelector : DataTemplateSelector { public DataTemplate DefaultTemplate { get; set; } public DataTemplate HighlightTemplate { get; set; } public string PropertyToEvaluate { get; set; } public string PropertyValueToHighlight { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { Product product = (Product)item; // Use reflection to get the property to check. Type type = product.GetType(); PropertyInfo property = type.GetProperty(PropertyToEvaluate); // Decide if this product should be highlighted // based on the property value. if (property.GetValue(product, null).ToString() == PropertyValueToHighlight) { return HighlightTemplate; } else { return DefaultTemplate; } } }
这里是创建两个数据模板的标记:
<Window.Resources> <DataTemplate x:Key="DefaultTemplate"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">TextBlock> Grid> Border> DataTemplate> <DataTemplate x:Key="HighlightTemplate"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" Background="LightYellow" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" FontWeight="Bold" Text="{Binding Path=ModelName}">TextBlock> <TextBlock Grid.Row="2" FontStyle="Italic" HorizontalAlignment="Right"> *** Great for vacations ***TextBlock> Grid> Border> DataTemplate> Window.Resources>
这里是应用模板选择器的标记:
<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplateSelector> <local:SingleCriteriaHighlightTemplateSelector DefaultTemplate="{StaticResource DefaultTemplate}" HighlightTemplate="{StaticResource HighlightTemplate}" PropertyToEvaluate="CategoryName" PropertyValueToHighlight="Travel" > local:SingleCriteriaHighlightTemplateSelector> ListBox.ItemTemplateSelector> ListBox>
模板与选择
例1,修改选中项目的背景色:
Foreground属性使用属性继承,所以你添加到模板任何元素自动地获得白颜色。Background属性不使用属性继承,但是默认背景颜色是透明。
首先,设置项目容器的样式:
<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch"> <ListBox.ItemContainerStyle> <Style> <Setter Property="Control.Padding" Value="0">Setter> <Style.Triggers> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="DarkRed" /> Trigger> Style.Triggers> Style> ListBox.ItemContainerStyle> ListBox>
然后,修改数据模板:
<DataTemplate> <Grid Margin="0" Background="White"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4" Background="{Binding Path=Background, RelativeSource={ RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem} }}" > <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition>RowDefinition> <RowDefinition>RowDefinition> Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">TextBlock> Grid> Border> Grid> DataTemplate>
例2,选中项目后,显示此项目的细节:
在数据模板中,使用Binding的RelativeSource属性搜索目前的ListBoxItem。如果它没有被选择,设置额外信息的Visibility属性,你能隐藏它。
使用一个数据触发器,当ListBoxItem的IsSelected属性被改变,修改容器的Visibility属性。
数据触发器只需要放在要隐藏的容器内。
这是还没有自动扩展功能的简化版:
<DataTemplate> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <StackPanel Margin="3"> <TextBlock Text="{Binding Path=ModelName}">TextBlock> <StackPanel> <TextBlock Margin="3" Text="{Binding Path=Description}" TextWrapping="Wrap" MaxWidth="250" HorizontalAlignment="Left">TextBlock> <Image Source="{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}"> Image> <Button FontWeight="Regular" HorizontalAlignment="Right" Padding="1" Tag="{Binding}">View Details...Button> StackPanel> StackPanel> Border> DataTemplate>
最内层的StackPanel包含着只对选中项目显示的内容,所以设置这个容器的样式:
<StackPanel> <StackPanel.Style> <Style> <Style.Triggers> <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={ RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem} }}" Value="False"> <Setter Property="StackPanel.Visibility" Value="Collapsed" /> DataTrigger> Style.Triggers> Style> StackPanel.Style> StackPanel>
在这个例子中,你需要使用DataTrigger而不是一个普通的触发器,因为你需要的属性在祖先元素中(ListBoxItem),并且唯一访问它的办法是使用数据绑定表达式。
改变项目布局
使用任何从System.Windows.Controls.Panel派生的类,设置ListBox的ItemsPanelTemplate属性,可以改变项目布局。
下面例子,使用WrapPanel改变项目布局:
<ListBox Margin="7,3,7,10" Name="lstProducts" ItemTemplate="{StaticResource ItemTemplate}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel>WrapPanel> ItemsPanelTemplate> ListBox.ItemsPanel> ListBox>
你必须也设置ScrollViewer.HorizontalScrollBarVisibility附加属性为Disabled。这确保ScrollViewer从不使用水平的scrollbar。
VirtualizingStackPanel对于大量数据项目有更好的性能。
组合框
默认,ComboBox是只读的。当设置IsReadOnly属性为假并且IsEditable属性为真,选择框变成文本框,你能键入任何文本。
组合框有自动完成的功能。要关闭它,设置ComboBox.IsTextSearchEnabled属性为假。此属性位于ItemsControl类。
如果IsEditable属性是假(这是默认值),选择框将显示一个项目的精确视觉副本。
重要的细节是组合框显示的内容是什么,而不是它的数据源是什么。例如,用Product对象填充一个组合框控件,并且设置DisplayMemberPath属性为ModelName。如此,组合框显示每个项目的ModelName属性。即组合框从一组Product对象获取信息,你的标记创造的是一个普通的文本列表。它将显示当前产品的ModelName,并且如果IsEditable是真并且IsReadOnly是假,它将允许你编辑那值。
如果IsEditable属性是真,选择框显示一个它的一个逐字的表示法。就是简单地调用ToString()到项目。一般情况下,显示一个类名的全称。
纠正这个问题最简单的方法是,设置TextSearch.TextPath附着属性指明组合框应该使用的内容。
<ComboBox IsEditable="True" IsReadOnly="True" TextSearch.TextPath="ModelName" ...>