为何ListBox的ItemContainerStyle会使ItemTemplateSelector失效?
我在.NET 6.0 WPF应用中定义了一个ListBoxItem的ItemContainerStyle,此前单独在ListBox上使用时一切正常。但当给ListBox同时设置ItemTemplateSelector属性后,发现ItemTemplateSelector完全被忽略:调试输出窗口无任何警告或错误,SelectTemplate方法从未被调用,ListBox最终仅显示项的类名文本。移除ItemContainerStyle后,ItemTemplateSelector恢复正常,SelectTemplate会为每个项执行,但此时ListBoxItem的样式不符合预期。
以下是我使用的ItemContainerStyle代码:
<Style x:Key="ListBoxItemStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Padding" Value="3,0,3,0" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Top" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="BorderBrush" Value="Transparent" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="Foreground" Value="{StaticResource GsForegroundLight}" /> <Setter Property="KeyboardNavigation.TabNavigation" Value="Local" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Grid> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1" SnapsToDevicePixels="True" /> <!-- MouseOver Visual is exactly like Selected Visual (below) minus the rounded corners --> <Border x:Name="MouseOverVisual" BorderBrush="{StaticResource GsForegroundLight}" BorderThickness="1" CornerRadius="3" Opacity="0" SnapsToDevicePixels="True"> <Border BorderThickness="1" CornerRadius="3" SnapsToDevicePixels="True"> <Border.Background> <LinearGradientBrush Opacity="0.5" StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Offset="1" Color="{StaticResource GsBackgroundDarkColor}" /> <GradientStop Color="Gray" /> </LinearGradientBrush> </Border.Background> </Border> </Border> <!-- Selected Visual uses no border but just a gradient background brush --> <Border x:Name="SelectedVisual" BorderThickness="0" CornerRadius="3" Opacity="0" SnapsToDevicePixels="True"> <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3" SnapsToDevicePixels="True"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Offset="1" Color="{StaticResource GsBackgroundDarkColor}" /> <GradientStop Color="Gray" /> </LinearGradientBrush> </Border.Background> </Border> </Border> <ContentPresenter x:Name="contentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" /> <!-- JMO: Removed the focus visual altogether by setting border thickness to zero --> <Border x:Name="FocusVisual" BorderBrush="#FFFFC92B" BorderThickness="0" CornerRadius="1" Opacity="0" SnapsToDevicePixels="True" /> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimation Storyboard.TargetName="MouseOverVisual" Storyboard.TargetProperty="Opacity" To="1" Duration="0" /> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="SelectionStates"> <VisualState x:Name="Unselected" /> <VisualState x:Name="Selected"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectedVisual" Storyboard.TargetProperty="Opacity" To="1" Duration="0" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Storyboard.TargetName="FocusVisual" Storyboard.TargetProperty="Opacity" To="1" Duration="0" /> </Storyboard> </VisualState> <VisualState x:Name="Unfocused" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
问题原因
问题出在自定义ControlTemplate内的ContentPresenter上:你只绑定了ContentTemplate属性,但没有绑定ContentTemplateSelector。当ListBox通过ItemTemplateSelector为项选择模板时,这个选择器是传递给ListBoxItem的ContentTemplateSelector属性,而非直接设置ContentTemplate。由于你的ContentPresenter没有响应这个Selector,无法应用选中的模板,最终只能显示对象的ToString()结果(即类名)。
修复方案
修改ControlTemplate中的ContentPresenter,添加ContentTemplateSelector的模板绑定,完整代码如下:
<ContentPresenter x:Name="contentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
修改后,ItemContainerStyle和ItemTemplateSelector即可同时正常工作:ListBoxItem会应用你的自定义样式,同时每个项会通过ItemTemplateSelector选择对应的模板进行渲染。
内容的提问来源于stack exchange,提问作者Joe

