通过Style在ContextMenu中定义的MenuItem命令无法执行
问题描述
用HierarchicalDataTemplate实现绑定集合的TreeView文件树,通过Style给TreeViewItem设置ContextMenu后,右键能正常弹出菜单,但点击MenuItem时绑定的NewFile命令完全不执行。
原代码
XML代码
<TreeView ItemsSource="{Binding FileTree}"> <TreeView.Resources> <Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}"> <Setter Property="ContextMenu"> <Setter.Value> <ContextMenu Width="250"> <!-- 点击此MenuItem时命令从未执行 --> <MenuItem Header="New File" Command="{Binding NewFile}" /> </ContextMenu> </Setter.Value> </Setter> </Style> </TreeView.Resources> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type model:FileTreeModel}" ItemsSource="{Binding Path=Children}"> <StackPanel Orientation="Horizontal"> <Image Source="{Binding FileName, Converter={StaticResource FileNameToIconConverter}}" Width="14" Height="12" Margin="5,0,5,0" SnapsToDevicePixels="True" Stretch="Fill" /> <TextBlock Text="{Binding Path=FileName}" /> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
C#模型代码
public class FileTreeModel { public string? FileName { get; set; } public string? Path { get; set; } public IList<FileTreeModel> Children { get; set; } = []; }
C# ViewModel代码
public ObservableCollection<FileTreeModel> FileTree { get; } public ICommand NewFile { get; } public ExplorerViewModel(INewFileService newFileService) { FileTree = []; NewFile = new RelayCommand(() => { Console.WriteLine("New file"); // 从未执行 newFileService.NewFile(); }); }
原因分析
ContextMenu是独立的弹出元素,不属于TreeView的Visual Tree,它默认的DataContext是当前TreeViewItem绑定的FileTreeModel实例,而NewFile命令定义在ExplorerViewModel中,所以绑定找不到对应的命令,自然无法触发。
解决方法
方法1:绑定到TreeView的ViewModel
修改MenuItem的绑定路径,通过PlacementTarget找到TreeViewItem,再获取TreeView的DataContext(也就是ViewModel):
<MenuItem Header="New File" Command="{Binding PlacementTarget.TreeView.DataContext.NewFile, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
这种方式适合不需要当前选中项上下文的全局命令。
方法2:将命令移到模型中
如果需要基于当前选中的文件/文件夹执行操作(比如在当前目录下创建新文件),可以把命令移到FileTreeModel中,同时传递当前路径给服务:
public class FileTreeModel { public string? FileName { get; set; } public string? Path { get; set; } public IList<FileTreeModel> Children { get; set; } = []; public ICommand NewFile { get; } public FileTreeModel(INewFileService newFileService) { NewFile = new RelayCommand(() => { Console.WriteLine($"New file in {Path}"); newFileService.NewFile(Path); // 传递当前选中项的路径 }); } }
这种方式更贴合文件树的上下文操作场景。
完整修改后的XML(方法1)
<TreeView ItemsSource="{Binding FileTree}"> <TreeView.Resources> <Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}"> <Setter Property="ContextMenu"> <Setter.Value> <ContextMenu Width="250"> <MenuItem Header="New File" Command="{Binding PlacementTarget.TreeView.DataContext.NewFile, RelativeSource={RelativeSource AncestorType=ContextMenu}}" /> </ContextMenu> </Setter.Value> </Setter> </Style> </TreeView.Resources> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type model:FileTreeModel}" ItemsSource="{Binding Path=Children}"> <StackPanel Orientation="Horizontal"> <Image Source="{Binding FileName, Converter={StaticResource FileNameToIconConverter}}" Width="14" Height="12" Margin="5,0,5,0" SnapsToDevicePixels="True" Stretch="Fill" /> <TextBlock Text="{Binding Path=FileName}" /> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
内容的提问来源于stack exchange,提问作者GHOST
相关产品推荐
相关产品推荐

