WPF的数据绑定系统自动生成列表项对象,为单个项应用所需的样式不是很容易。解决方案是ItemContainerStyle 属性。如果设置了ItemContainerStyle 属性,当创建列表项时,列表控件会将其向下传递给每个项。对于ListBox控件,每个项有ListBoxItem 对象表示,对于CombBox 控件,则对应是 CombBoxItem。
交替条目样式
WPF通过两个属性为交替项提供内置支持:AlternationCount 和 AlternationIndex。
cs
<Window>
<Window.Resources>
<Style x:Key="listBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Padding" Value="5"></Setter>
<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="10"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid x:Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition MinHeight="100"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" ItemContainerStyle="{StaticResource listBoxItemStyle}" ItemsSource="{Binding Path=Orders}" AlternationCount="2" DisplayMemberPath="Price"/>
</Grid>
</Window>
也可以直接将样式设置到ListBox层次
cs
<Window>
<Window.Resources>
<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 IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}">
<ContentPresenter/>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition MinHeight="100"/>
</Grid.RowDefinitions>
<ListView Grid.Row="1" Grid.Column="0" Style="{StaticResource checkBoxListStyle}" ItemsSource="{Binding Path=Orders}" DisplayMemberPath="Price" Name="checkButtonListBox"/>
</Grid>
</Window>
样式选择器
可以使用样式选择器来为不同的子项提供不同的样式,自定义样式选择器需要继承自 StyleSelector 类,需要重写 SelectStyle() 方法。
cs
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)
{
Order order = (Order)item;
if (order.Price > 1000)
{
return HighlightStyle;
}
else
{
return DefaultStyle;
}
}
}
完整的代码文件:
MainWindow.xaml
cs
<Window x:Class="ListBoxStyle.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:ListBoxStyle"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="listBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Padding" Value="5"></Setter>
<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="10"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="radioButtonListStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Focusable="False" IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}">
<ContentPresenter/>
</RadioButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<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 IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}">
<ContentPresenter/>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<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>
<Grid x:Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition MinHeight="100"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" ItemContainerStyle="{StaticResource listBoxItemStyle}" ItemsSource="{Binding Path=Orders}" AlternationCount="3" DisplayMemberPath="Price"/>
<ListBox Grid.Row="0" Grid.Column="1" ItemContainerStyle="{StaticResource radioButtonListStyle}" ItemsSource="{Binding Path=Orders}" DisplayMemberPath="Price" Name="radioButtonListBox"/>
<ListView Grid.Row="1" Grid.Column="0" Style="{StaticResource checkBoxListStyle}" ItemsSource="{Binding Path=Orders}" DisplayMemberPath="Price" Name="checkButtonListBox"/>
<ListBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Path=Orders}" DisplayMemberPath="Price" Name="styleSelectorListBox">
<ListBox.ItemContainerStyleSelector>
<local:SingleCriteriaHighlightStyleSelector DefaultStyle="{StaticResource DefaultStyle}" HighlightStyle="{StaticResource HighlightStyle}"></local:SingleCriteriaHighlightStyleSelector>
</ListBox.ItemContainerStyleSelector>
</ListBox>
<Button Grid.Row="4" Click="Button_Click">Test</Button>
<Button Grid.Row="4" Grid.Column="1" Click="Button_Click_1">Test</Button>
</Grid>
</Window>
MainWindow.xaml.cs
cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ListBoxStyle;
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<T>(ref T member, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.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 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)
{
Order order = (Order)item;
if (order.Price > 1000)
{
return HighlightStyle;
}
else
{
return DefaultStyle;
}
}
}
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;
order2.Price = 1000;
order2.Volume = 100;
order3.Price = 10000;
order3.Volume = 1000;
order4.Price = 100000;
order4.Volume = 10000;
Orders.Add(order1);
Orders.Add(order2);
Orders.Add(order3);
Orders.Add(order4);
}
public ObservableCollection<Order> Orders {get; set;} = new ();
private void Button_Click(object sender, RoutedEventArgs e)
{
string message = "";
if(radioButtonListBox.SelectedItem != null)
{
Order order = (Order)radioButtonListBox.SelectedItem;
message = order.Price.ToString();
}
message += "\n";
foreach (var selectedItem in checkButtonListBox.SelectedItems)
{
Order order = (Order)selectedItem;
message += order.Price.ToString() + " ";
}
MessageBox.Show(message);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Orders[1].Price = 50000;
StyleSelector selector = styleSelectorListBox.ItemContainerStyleSelector;
styleSelectorListBox.ItemContainerStyleSelector = null;
styleSelectorListBox.ItemContainerStyleSelector = selector;
}
}