WPF中ItemContainerStyle不适用的一种情况
WPF中的ItemsControl定义了ItemContainerStyle这一属性,顾名思义,该属性用来给ItemsControl中包含的每一个Item的容器定义样式。
比如在ListBox中这个容器就是ListBoxItem,在TabControl中这个容器就是TabItem。
下面是ItemContainerStyle的一种简单应用:
XAML:
< Window ......> < StackPanel > < ListBox Name="itemsControl" ItemsSource="{Binding}"> < ListBox.ItemContainerStyle > < Style TargetType="ListBoxItem"> < Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </ Style > </ ListBox.ItemContainerStyle > < ListBox.ItemTemplate > < DataTemplate > < TextBlock Text="{Binding Text}"/> </ DataTemplate > </ ListBox.ItemTemplate > </ ListBox > </ StackPanel > </ Window > |
在这段XAML中定义了一个ListBox,在其ItemTemplate中有一个TextBlock绑定到数据实体的Text属性上。在其ItemContainerStyle中将其每个Item的IsSelected属性绑定到数据实体的IsSelected上。其数据实体的生成在下面的代码中:
public partial class ComboBoxTest : Window { public ComboBoxTest() { InitializeComponent(); itemsControl.DataContext = GetData(); } private object GetData() { Collection< object > data = new Collection< object >(); for ( int i = 1; i <= 10; i++) { data.Add( new { Text = i.ToString(), IsSelected = i == 5 }); } return data; } } |
为了简单,没有单独定义实体类而是用了匿名对象。一共生成十个匿名实体,把其中第五个的IsSelected设置为true,把这十个实体放入一个Collection中赋值给控件的DataContext,这样XAML中对ItemsSource的绑定(ItemsSource="{Binding}")就会起效。当然,直接把这个Collection赋值给ItemsSource也可以。
运行一下,结果和预期的一样,第五项被选中了。
试试把XAML中的ListBox换成TabControl,更换之后的XAML如下:
< Window ......> < StackPanel > < TabControl Name="itemsControl" ItemsSource="{Binding}"> < TabControl.ItemContainerStyle > < Style TargetType="TabItem"> < Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </ Style > </ TabControl.ItemContainerStyle > < TabControl.ItemTemplate > < DataTemplate > < TextBlock Text="{Binding Text}"/> </ DataTemplate > </ TabControl.ItemTemplate > </ TabControl > </ StackPanel > </ Window > |
仅仅是把ListBox换成了TabControl,把ListBoxItem换成了TabItem而已,C#代码没有改。试着运行一下,结果还是和预期的一样,第五项会被选中。
ListBox和TabControl都是间接继承自ItemsControl而直接继承自Selector的,那是不是所有Selector的子类都会有如上的行为呢?
实际上不是,把Selector的另一个子类ComboBox拿出来试试。
仍然是只改XAML,不改C#代码,改完之后的XAML如下:
< Window ......> < StackPanel > < ComboBox Name="itemsControl" ItemsSource="{Binding}"> < ComboBox.ItemContainerStyle > < Style TargetType="ComboBoxItem"> < Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </ Style > </ ComboBox.ItemContainerStyle > < ComboBox.ItemTemplate > < DataTemplate > < TextBlock Text="{Binding Text}"/> </ DataTemplate > </ ComboBox.ItemTemplate > </ ComboBox > </ StackPanel > </ Window > |
运行之后的效果如下:
可见启动后没有任何选中项。而只有当用鼠标将ComboBox展开时第五项才会被选中。对这种现象,我的猜测是因为ItemContainerStyle只有在所有Item加载之后才会开始应用,而ComboBox默认情况下并不会把其Items展示出来,所以直到用鼠标将ComboBox展开时才会有选中效果。
对这种情况有一个不太完美的解决方案,把C#代码中的GetData方法修改如下:
private object GetData() { Collection< object > data = new Collection< object >(); for ( int i = 1; i <= 10; i++) { data.Add( new { Text = i.ToString() }); } return new { Data = data, SelectedData = data[4] }; } |
上面的代码中再次应用了匿名对象,把整个实体集合赋值给新的匿名对象中的Data属性,并把集合的第五项赋值给新的匿名对象的SelectedData属性。这样GetData方法的返回值--也就是这个新的匿名对象就会被赋值到控件的DataContext上去。
然后修改XAML,把ComboBox的ItemsSource绑定到匿名对象的Data属性,把SelectedValue绑定到匿名对象的SelectedData属性。修改后的XAML如下:
< Window ......> < StackPanel > < ComboBox Name="itemsControl" ItemsSource="{Binding Data}" SelectedValue="{Binding SelectedData, Mode=OneTime}"> < ComboBox.ItemTemplate > < DataTemplate > < TextBlock Text="{Binding Text}"/> </ DataTemplate > </ ComboBox.ItemTemplate > </ ComboBox > </ StackPanel > </ Window > |
这次直接绑定SelectedValue,就不用像使用Style一样等到Item加载之后才会生效了。
再运行,启动效果如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述