WPF TreeView实现鼠标悬停节点时添加项功能(附XAML代码)
实现TreeView节点悬停显示添加按钮(类似OneNote)
我帮你完善了TreeView的实现,刚好能满足你要的类似OneNote的悬停显示添加按钮功能,核心是通过自定义TreeViewItem的模板,配合触发器来控制按钮的显示隐藏,同时适配你的递归类绑定需求。
完整XAML实现
你可以直接把这段代码替换/补充到你的现有XAML中,关键是添加了TreeView.ItemContainerStyle来定义节点的模板和悬停逻辑:
<TreeView Grid.Row="1" BorderBrush="{x:Null}" ItemsSource="{Binding Law.RootGroup.Groups}"> <i:Interaction.Behaviors> <functions:BindableSelectedItemBehavior SelectedItem="{Binding SelectedGroup, Mode=TwoWay}" /> </i:Interaction.Behaviors> <!-- 自定义TreeViewItem样式,实现悬停显示添加按钮 --> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <Setter Property="Padding" Value="4,2"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TreeViewItem"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <!-- 节点展开/折叠按钮(保留默认逻辑) --> <ToggleButton x:Name="Expander" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Visibility="{Binding HasItems, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}" Margin="0,0,4,0"> <ToggleButton.Style> <Style TargetType="ToggleButton"> <Setter Property="Width" Value="16"/> <Setter Property="Height" Value="16"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ToggleButton"> <Path x:Name="ExpandPath" Data="M0,0 L0,6 L6,0 Z" Fill="#888" HorizontalAlignment="Center" VerticalAlignment="Center"/> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="ExpandPath" Property="Data" Value="M0,0 L6,0 L3,6 Z"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ToggleButton.Style> </ToggleButton> <!-- 节点内容显示 --> <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/> <!-- 悬停显示的添加按钮 --> <Button x:Name="AddButton" Content="+" Width="20" Height="20" Margin="8,0,0,0" Visibility="Collapsed" Style="{StaticResource CircleButtonStyle}" Command="{Binding DataContext.AddNewGroupCommand, RelativeSource={RelativeSource AncestorType=TreeView}}" CommandParameter="{Binding}"/> </StackPanel> <ControlTemplate.Triggers> <!-- 鼠标悬停节点时显示添加按钮 --> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="AddButton" Property="Visibility" Value="Visible"/> </Trigger> <!-- 可选:节点选中时也保持按钮显示 --> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="AddButton" Property="Visibility" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </TreeView.ItemContainerStyle> <!-- 递归类的层级数据模板 --> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Groups}"> <TextBlock Text="{Binding Name}" Padding="2,0"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
配套ViewModel与递归类实现
你的递归类(比如Group)需要包含子项集合,同时ViewModel要提供添加子项的命令:
递归类示例
public class Group : INotifyPropertyChanged { private string _name; public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } private ObservableCollection<Group> _groups; public ObservableCollection<Group> Groups { get => _groups; set { _groups = value; OnPropertyChanged(); } } public Group() { Groups = new ObservableCollection<Group>(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
ViewModel中的命令实现
public class YourViewModel : INotifyPropertyChanged { public Law Law { get; set; } // 你的Law对象,包含RootGroup public Group SelectedGroup { get; set; } public ICommand AddNewGroupCommand { get; } public YourViewModel() { // 初始化数据(示例) Law = new Law { RootGroup = new Group { Name = "Root" } }; AddNewGroupCommand = new RelayCommand<Group>(AddNewGroup); } private void AddNewGroup(Group parentGroup) { if (parentGroup == null) return; // 添加新的子分组 parentGroup.Groups.Add(new Group { Name = $"New Group {parentGroup.Groups.Count + 1}" }); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
可选:自定义圆形按钮样式
如果想让添加按钮更贴近OneNote的风格,可以添加这个样式到资源字典中:
<Style x:Key="CircleButtonStyle" TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Ellipse x:Name="ellipse" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"> <Storyboard> <ColorAnimation To="#E0E0E0" Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse" Duration="0:0:0.1"/> </Storyboard> </VisualState> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation To="#C0C0C0" Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse" Duration="0:0:0.1"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Ellipse> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Background" Value="#F0F0F0"/> <Setter Property="BorderBrush" Value="#CCC"/> <Setter Property="Foreground" Value="#333"/> </Style>
关键细节说明
- 按钮命令绑定:因为TreeViewItem的DataContext是单个
Group对象,所以通过RelativeSource找到TreeView的DataContext(也就是ViewModel)来绑定命令,同时把当前Group作为参数传递,方便添加子项。 - 悬停触发器:通过
IsMouseOver触发器控制按钮显示,你也可以根据需求调整逻辑,比如只有选中节点悬停才显示。 - 层级模板:
HierarchicalDataTemplate自动绑定递归类的Groups集合,实现无限层级的节点展示。
内容的提问来源于stack exchange,提问作者Thibaud
相关产品推荐
相关产品推荐

