WPF 如果选中它,则只保留一个 TreeViewItem 展开,折叠其他的,除非它是 Selected TreeViewItem 的祖先

Posted

技术标签:

【中文标题】WPF 如果选中它,则只保留一个 TreeViewItem 展开,折叠其他的,除非它是 Selected TreeViewItem 的祖先【英文标题】:WPF Keep only one TreeViewItem expanded if it is selected, collapse the others unless it is the ancestor of the Selected TreeViewItem 【发布时间】:2021-11-08 04:37:23 【问题描述】:

我有一个带有自定义样式的 TreeViewItem,我希望它具有如下行为,因此我想将其设置为我的 WPF 应用程序中的侧边菜单。问题是我只想将一个 TreeViewItem 保持为展开状态,而其他的则折叠起来,只要它们不是所选树的祖先。 例如,如果我在 Mammal 中选择 Cat,Mammal 显然也应该被扩展,但不是 Insects。如果我选择非洲,则必须扩大非洲和大陆,而必须折叠美洲,欧洲和亚洲。 还发生了一些奇怪的事情,SelectedItemChanged 事件没有触发,所以在选择新的 TreeViewItem 时 IsSelected 属性没有改变,所以我保留了应用于 TreeViewItem 的样式。

自定义样式

        <Style x:Key="TreeViewExpanderHeaderStyle" TargetType="x:Type ToggleButton">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="x:Type ToggleButton">
                        <Border Background="TemplateBinding Background">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="auto"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <ContentPresenter Grid.Column="1" Content="TemplateBinding Content"
                                              ContentTemplate="TemplateBinding ContentTemplate"
                                              ContentStringFormat="TemplateBinding ContentStringFormat"
                                              ContentTemplateSelector="TemplateBinding ContentTemplateSelector"
                                              VerticalAlignment="Center"
                                              Margin="0,0,16,0" />
                                <ToggleButton Grid.Column="2"
                                          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=IsChecked, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent">
                                    <ToggleButton.Style>
                                        <Style TargetType="x:Type ToggleButton">
                                            <Setter Property="Template">
                                                <Setter.Value>
                                                    <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>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </ToggleButton.Style>
                                </ToggleButton>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="MyTreeViewItemFocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="x:Type TreeViewItem">
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="BorderBrush" Value="Gray"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalContentAlignment" Value="Binding HorizontalContentAlignment, RelativeSource=RelativeSource AncestorType=x:Type ItemsControl"/>
            <Setter Property="VerticalContentAlignment" Value="Binding VerticalContentAlignment, RelativeSource=RelativeSource AncestorType=x:Type ItemsControl"/>
            <Setter Property="Padding" Value="8" />
            <Setter Property="FocusVisualStyle" Value="StaticResource MyTreeViewItemFocusVisual"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="x:Type TreeViewItem">
                        <Border BorderThickness="TemplateBinding BorderThickness" BorderBrush="TemplateBinding BorderBrush">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="SelectionStates">
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition GeneratedDuration="0:0:0.6"/>
                                    </VisualStateGroup.Transitions>
                                    <VisualState Name="Selected">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="HeaderSite"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="0.18" Duration="0"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState Name="Unselected"/>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="ExpansionStates">
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition GeneratedDuration="0" To="Expanded">
                                            <VisualTransition.GeneratedEasingFunction>
                                                <CubicEase EasingMode="EaseOut"/>
                                            </VisualTransition.GeneratedEasingFunction>
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="x:Static Visibility.Visible" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ItemsHost" To="1" Duration="0:0:0.3"/>
                                            </Storyboard>
                                        </VisualTransition>
                                        <VisualTransition GeneratedDuration="0" To="Collapsed">
                                            <VisualTransition.GeneratedEasingFunction>
                                                <CubicEase EasingMode="EaseOut"/>
                                            </VisualTransition.GeneratedEasingFunction>
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">
                                                    <DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="x:Static Visibility.Collapsed" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ItemsHost" To="0" Duration="0:0:0.3"/>
                                            </Storyboard>
                                        </VisualTransition>
                                    </VisualStateGroup.Transitions>
                                    <VisualState x:Name="Expanded">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ItemsHost" To="1" Duration="0"/>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="x:Static Visibility.Visible" />
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Collapsed">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ItemsHost" To="0" Duration="0"/>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="x:Static Visibility.Hidden" />
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <DockPanel Background="TemplateBinding Background">
                                <ToggleButton Name="HeaderSite"
                                              DockPanel.Dock="Top"
                                              BorderThickness="0" Cursor="Hand"
                                              IsChecked="Binding Path=IsExpanded,RelativeSource=RelativeSource TemplatedParent"
                                              Style="StaticResource TreeViewExpanderHeaderStyle"
                                              Opacity=".87"
                                              Foreground="TemplateBinding Foreground"
                                              Content="TemplateBinding Header"
                                              ContentTemplate="TemplateBinding HeaderTemplate"
                                              ContentTemplateSelector="TemplateBinding HeaderTemplateSelector"
                                              ContentStringFormat="TemplateBinding HeaderStringFormat"/>
                                <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>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="TextElement.Foreground" Value="Red"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Opacity" Value=".56"/>
                            </Trigger>
                            
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>

            </Style.Triggers>
        </Style>

和树视图

        <TreeView SelectedItemChanged="TreeView_SelectedItemChanged">
            <TreeViewItem Header="Animals">
                <TreeViewItem Header="Mammals">
                    <TreeViewItem Header="Cat"/>
                    <TreeViewItem Header="Dog"/>
                    <TreeViewItem Header="Horse"/>
                </TreeViewItem>
                <TreeViewItem Header="Insects">
                    <TreeViewItem Header="Fly"/>
                    <TreeViewItem Header="Wasp"/>
                    <TreeViewItem Header="Bee"/>
                </TreeViewItem>
            </TreeViewItem>
            <TreeViewItem Header="Continents">
                <TreeViewItem Header="Africa">
                    <TreeViewItem Header="Angola"/>
                    <TreeViewItem Header="Congo"/>
                    <TreeViewItem Header="Egypth"/>
                    <TreeViewItem Header="S. Africa"/>
                </TreeViewItem>
                <TreeViewItem Header="America">
                    <TreeViewItem Header="USA"/>
                    <TreeViewItem Header="Canada"/>
                    <TreeViewItem Header="Mexico"/>
                    <TreeViewItem Header="Brazil"/>
                </TreeViewItem>
                <TreeViewItem Header="Europe">
                    <TreeViewItem Header="UK"/>
                    <TreeViewItem Header="Spain"/>
                    <TreeViewItem Header="France"/>
                    <TreeViewItem Header="Italy"/>
                </TreeViewItem>
                <TreeViewItem Header="Asia">
                    <TreeViewItem Header="China"/>
                    <TreeViewItem Header="Korea"/>
                    <TreeViewItem Header="Japan"/>
                    <TreeViewItem Header="Viet Nam"/>
                </TreeViewItem>
            </TreeViewItem>
        </TreeView>

【问题讨论】:

【参考方案1】:

我推荐使用这个简单的sn-p,一个简单的全局样式,可以转换成一个Behavior

<Window.Resources>
    <Style TargetType="x:Type TreeViewItem">
        <EventSetter Event="Expanded" Handler="TreeViewItem_Expanded"/>
    </Style>
</Window.Resources>

还有一个EventSetter 的处理程序

private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)

    var trvi = sender as TreeViewItem;
    //Using pattern matching
    //If parent is an ItemsControl and it's the current expanded item
    //That's a generic way to achieve such behavior
    if(trvi is Parent: ItemsControl parent, IsExpanded: true )
        foreach (TreeViewItem item in parent.Items)
            item.IsExpanded = false;

更新

由于您在选择另一个分支后要求折叠整个分支,因此您应该递归地遍历和折叠节点

private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)

    var trvi = sender as TreeViewItem;
    if (trvi is  Parent: ItemsControl parent, IsExpanded: true )
        foreach (TreeViewItem item in parent.Items)
            if (item != trvi && item.IsExpanded)
                CollapseBranch(item);




private static void CollapseBranch(TreeViewItem trvi)

    trvi.IsExpanded = false;

    foreach (TreeViewItem item in trvi.Items)
        CollapseBranch(item);

【讨论】:

以上是关于WPF 如果选中它,则只保留一个 TreeViewItem 展开,折叠其他的,除非它是 Selected TreeViewItem 的祖先的主要内容,如果未能解决你的问题,请参考以下文章

选中树形图treeview的一个节点,怎么得到它的父节点

c# treeview 知道节点name 如何选中节点!

WPF TreeView 虚拟化-设置滚动到选中项

求教:c# wpf treeView如何知道怎么选中了哪个子树?

wpf treeview 怎么获取节点的值

wpf中选中treeview的某个子节点后获取子节点所在的所有父节点的内容用于数据库查询