WPF自定义样式TreeView实现单选展开及SelectedItemChanged事件不触发问题
问题根源
你自定义的TreeViewItem模板存在嵌套ToggleButton的问题,外层整个Header区域被做成了绑定IsExpanded的ToggleButton,所有点击都被这个ToggleButton拦截处理,不会触发TreeViewItem的选中逻辑,所以IsSelected不会更新,SelectedItemChanged事件自然无法触发。
修复步骤
1. 修复选中事件不触发的问题
调整TreeViewItem模板,仅保留右侧箭头作为展开控制按钮,头部主区域点击触发选中逻辑:
<!-- 替换原来TreeViewItem模板中的DockPanel部分 --> <DockPanel Background="{TemplateBinding Background}"> <!-- 头部区域改为Border,点击触发选中 --> <Border Name="HeaderSite" DockPanel.Dock="Top" BorderThickness="0" Cursor="Hand" Opacity=".87" Foreground="{TemplateBinding Foreground}" MouseLeftButtonDown="HeaderSite_MouseLeftButtonDown"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <ContentPresenter Grid.Column="0" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" VerticalAlignment="Center" Margin="0,0,16,0" /> <!-- 仅右侧箭头为展开控制按钮 --> <ToggleButton Grid.Column="1" VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}" x:Name="Expander" Visibility="{Binding HasItems, Converter={StaticResource BoolToVisibilityConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}" IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> <ToggleButton.Template> <ControlTemplate TargetType="{x:Type ToggleButton}"> <Border Background="Transparent"> <Path Data="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" Opacity=".38" x:Name="ExpandPath" RenderTransformOrigin="0.5,0.5" Height="24" Width="24" Fill="{TemplateBinding Foreground}"/> </Border> </ControlTemplate> </ToggleButton.Template> </ToggleButton> </Grid> </Border> <!-- 下方内容区域保持原来的代码不变 --> <Border Name="ContentSite" DockPanel.Dock="Bottom"> <StackPanel x:Name="ItemsPanel" Margin="10 0 0 0"> <StackPanel.Height> <MultiBinding Converter="{StaticResource MathMlpMultipleConverter}"> <Binding ElementName="ItemsHost" Path="ActualHeight"/> <Binding ElementName="ItemsHost" Path="Opacity"/> </MultiBinding> </StackPanel.Height> <ItemsPresenter x:Name="ItemsHost" VerticalAlignment="Top" Opacity="0" Visibility="Collapsed"/> </StackPanel> </Border> </DockPanel>
后台添加头部点击事件和辅助方法:
// 头部点击触发选中 private void HeaderSite_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var treeViewItem = FindVisualParent<TreeViewItem>(sender as DependencyObject); if (treeViewItem == null) return; treeViewItem.IsSelected = true; e.Handled = true; } // 视觉树查找父节点辅助方法 private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject { while (child != null) { if (child is T parent) return parent; child = VisualTreeHelper.GetParent(child); } return null; }
2. 实现自动折叠非关联节点逻辑
现在SelectedItemChanged已经可以正常触发,添加如下逻辑即可实现需求:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { var treeView = sender as TreeView; if (treeView == null || e.NewValue == null) return; var selectedItem = e.NewValue as TreeViewItem; // 先折叠所有节点 foreach (TreeViewItem rootItem in treeView.Items) { CollapseAll(rootItem); } // 展开选中节点的所有祖先节点 var parent = VisualTreeHelper.GetParent(selectedItem); while (parent != null) { if (parent is TreeViewItem item) { item.IsExpanded = true; } if (parent is TreeView) break; parent = VisualTreeHelper.GetParent(parent); } } // 递归折叠节点及其所有子节点 private void CollapseAll(TreeViewItem item) { item.IsExpanded = false; foreach (TreeViewItem subItem in item.Items) { CollapseAll(subItem); } }
如果你的TreeView是数据绑定模式而非直接硬编码TreeViewItem,只需要在获取选中节点时通过ItemContainerGenerator.ContainerFromItem方法拿到对应的TreeViewItem即可,逻辑和上面一致。
内容的提问来源于stack exchange,提问作者Roberto Vargas
相关产品推荐
相关产品推荐

