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 的祖先的主要内容,如果未能解决你的问题,请参考以下文章