WPF TreeView类型及共享上下文菜单实现技术咨询
我来给你梳理下这个场景的具体实现思路和代码示例,结合你提到的TemplateFamily(父项)、TemplateParameter(子项)的结构,还有三个标签页共用同一个ContextMenu的需求,咱们一步步来:
一、界面布局(XAML)
首先是三个标签页的布局,把ContextMenu定义在窗口资源里,这样TreeView和两个ListBox都能直接引用:
<Window.Resources> <!-- 共享的ContextMenu --> <ContextMenu x:Key="SharedContextMenu"> <MenuItem Header="新建" Click="NewMenuItem_Click"/> <MenuItem Header="编辑" Click="EditMenuItem_Click"/> <MenuItem Header="删除" Click="DeleteMenuItem_Click"/> </ContextMenu> </Window.Resources> <TabControl> <!-- 带TreeView的标签页 --> <TabItem Header="模板结构"> <TreeView x:Name="TemplateTreeView" ContextMenu="{StaticResource SharedContextMenu}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Parameters}"> <!-- 父项(TemplateFamily)显示模板 --> <TextBlock Text="{Binding FamilyName}"/> <!-- 子项(TemplateParameter)显示模板 --> <HierarchicalDataTemplate.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding ParameterName}"/> </DataTemplate> </HierarchicalDataTemplate.ItemTemplate> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </TabItem> <!-- 第一个ListBox标签页 --> <TabItem Header="参数列表1"> <ListBox x:Name="ParamListBox1" ContextMenu="{StaticResource SharedContextMenu}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding ParameterName}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </TabItem> <!-- 第二个ListBox标签页 --> <TabItem Header="参数列表2"> <ListBox x:Name="ParamListBox2" ContextMenu="{StaticResource SharedContextMenu}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding ParameterName}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </TabItem> </TabControl>
二、数据类型定义(C#)
按照你说的父项TemplateFamily和子项TemplateParameter,咱们定义对应的实体类,实现INotifyPropertyChanged方便数据绑定:
using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; public class TemplateFamily : INotifyPropertyChanged { private string _familyName; public string FamilyName { get => _familyName; set { _familyName = value; OnPropertyChanged(); } } public ObservableCollection<TemplateParameter> Parameters { get; set; } = new ObservableCollection<TemplateParameter>(); public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class TemplateParameter : INotifyPropertyChanged { private string _parameterName; public string ParameterName { get => _parameterName; set { _parameterName = value; OnPropertyChanged(); } } // 可根据需求添加其他属性,比如参数类型、默认值等 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
三、ContextMenu共享逻辑处理(C#后台代码)
因为三个控件共用同一个ContextMenu,所以在菜单点击事件里需要先判断当前触发菜单的源对象类型,再执行对应的新建/编辑/删除逻辑:
using System.Windows; using System.Windows.Controls; using System.Linq; private void NewMenuItem_Click(object sender, RoutedEventArgs e) { var menuItem = sender as MenuItem; var contextMenu = menuItem?.Parent as ContextMenu; var sourceElement = contextMenu?.PlacementTarget as FrameworkElement; // 判断触发源是TreeView还是ListBox,以及TreeView选中项的类型 if (sourceElement is TreeView treeView) { var selectedItem = treeView.SelectedItem; if (selectedItem is TemplateFamily family) { // 给选中的父项添加新子参数 family.Parameters.Add(new TemplateParameter { ParameterName = "新参数" }); } else if (selectedItem is TemplateParameter param) { // 给当前参数的父家族添加同级新参数 var parentFamily = treeView.Items.Cast<TemplateFamily>().First(f => f.Parameters.Contains(param)); parentFamily.Parameters.Add(new TemplateParameter { ParameterName = "新参数" }); } } else if (sourceElement is ListBox listBox) { // 给ListBox添加新参数 if (listBox.ItemsSource is ObservableCollection<TemplateParameter> paramList) { paramList.Add(new TemplateParameter { ParameterName = "新参数" }); } } } private void EditMenuItem_Click(object sender, RoutedEventArgs e) { var menuItem = sender as MenuItem; var contextMenu = menuItem?.Parent as ContextMenu; var sourceElement = contextMenu?.PlacementTarget as FrameworkElement; if (sourceElement is TreeView treeView) { var selectedItem = treeView.SelectedItem; if (selectedItem is TemplateFamily family) { // 这里可以替换为弹窗交互修改名称 family.FamilyName = "修改后的模板家族名称"; } else if (selectedItem is TemplateParameter param) { param.ParameterName = "修改后的参数名称"; } } else if (sourceElement is ListBox listBox) { var selectedParam = listBox.SelectedItem as TemplateParameter; if (selectedParam != null) { selectedParam.ParameterName = "修改后的参数名称"; } } } private void DeleteMenuItem_Click(object sender, RoutedEventArgs e) { var menuItem = sender as MenuItem; var contextMenu = menuItem?.Parent as ContextMenu; var sourceElement = contextMenu?.PlacementTarget as FrameworkElement; if (sourceElement is TreeView treeView) { var selectedItem = treeView.SelectedItem; if (selectedItem is TemplateFamily family) { // 删除选中的父项 var familyList = treeView.ItemsSource as ObservableCollection<TemplateFamily>; familyList?.Remove(family); } else if (selectedItem is TemplateParameter param) { // 删除选中的子参数 var parentFamily = treeView.Items.Cast<TemplateFamily>().First(f => f.Parameters.Contains(param)); parentFamily.Parameters.Remove(param); } } else if (sourceElement is ListBox listBox) { var selectedParam = listBox.SelectedItem as TemplateParameter; if (selectedParam != null && listBox.ItemsSource is ObservableCollection<TemplateParameter> paramList) { paramList.Remove(selectedParam); } } }
四、初始化绑定数据(C#)
在窗口的构造函数里给TreeView和ListBox绑定测试数据:
public MainWindow() { InitializeComponent(); // 初始化TreeView的模板家族数据 var familyList = new ObservableCollection<TemplateFamily> { new TemplateFamily { FamilyName = "基础模板家族", Parameters = { new TemplateParameter { ParameterName = "宽度" }, new TemplateParameter { ParameterName = "高度" } } }, new TemplateFamily { FamilyName = "高级模板家族", Parameters = { new TemplateParameter { ParameterName = "分辨率" }, new TemplateParameter { ParameterName = "帧率" } } } }; TemplateTreeView.ItemsSource = familyList; // 初始化两个ListBox的参数数据 var paramList = new ObservableCollection<TemplateParameter> { new TemplateParameter { ParameterName = "颜色" }, new TemplateParameter { ParameterName = "透明度" } }; ParamListBox1.ItemsSource = paramList; ParamListBox2.ItemsSource = new ObservableCollection<TemplateParameter>(paramList); }
这样就完整实现了你需要的功能:TreeView的两层结构展示、三个标签页共享ContextMenu,点击不同控件的项时菜单能执行对应逻辑。
内容的提问来源于stack exchange,提问作者Igavshne
相关产品推荐
相关产品推荐

