Prism项目DataGrid单元格ContextMenu绑定ViewModel命令失败求助
我在Prism模板生成的“Prism Full App”项目中,尝试为DataGrid某列的单元格添加ContextMenu,希望点击菜单项时执行ViewModel中的TimePointOnCommand命令。目前ContextMenu可正常显示,但点击时命令未执行,且出现绑定错误:
ICommand TimePointOnCommand property not found on object of type Customer
我不想将该ICommand定义在Customer实体类中,而是保留在ViewModel里。已知Button可通过以下方式绑定ViewModel的命令:
<Button Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl},Mode=FindAncestor},Path=DataContext.TimePointOnCommand}" >Go</Button>
但ContextMenu无法使用该方式,请问如何实现ContextMenu菜单项绑定ViewModel中的命令?
我的View代码
<Grid> <StackPanel> <DataGrid ItemsSource="{Binding Customers}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" /> <DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="*" /> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch"> <TextBlock.ContextMenu> <ContextMenu> <MenuItem Header="An Zeitpunkt festlegen" Command="{ Binding Path=TimePointOnCommand}" /> </ContextMenu> </TextBlock.ContextMenu> </TextBlock> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </StackPanel> </Grid>
我的ViewModel代码
using System; using System.Collections.ObjectModel; using Prism.Commands; using Prism.Regions; using PrismWpf.Core.Mvvm; using PrismWpf.Services.Interfaces; namespace PrismWpf.Modules.ModuleName.ViewModels { public class ViewAViewModel : RegionViewModelBase { public DelegateCommand TimePointOnCommand { get; private set; } private ObservableCollection<Customer> _customers; public ObservableCollection<Customer> Customers { get => _customers; set => SetProperty(ref _customers, value); } public ViewAViewModel(IRegionManager regionManager, IMessageService messageService) : base(regionManager) { TimePointOnCommand = new DelegateCommand(SetOn); Customers = new ObservableCollection<Customer> { new() { Name = "John Doe", Id = 1, IsValid = true, Birthdate = new DateTime(2022, 4, 15, 10, 4, 2) }, new() { Name = "Jane Smith", Id = 2, IsValid = false, Birthdate = new DateTime(2022, 6, 15, 8, 2, 2) } }; } private void SetOn() { } public override void OnNavigatedTo(NavigationContext navigationContext) { } } }
ContextMenu不属于Visual Tree,因此无法直接用RelativeSource查找UserControl这类祖先元素。以下是两种可行的解决方法:
方法一:通过Tag传递ViewModel引用
在弹出ContextMenu的TextBlock上,将ViewModel绑定到Tag属性,再让ContextMenu的菜单项通过PlacementTarget获取这个Tag:
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" <!-- 绑定UserControl的DataContext(即ViewModel)到Tag --> Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> <TextBlock.ContextMenu> <ContextMenu> <MenuItem Header="An Zeitpunkt festlegen" <!-- 通过ContextMenu的PlacementTarget(TextBlock)获取Tag中的ViewModel,再绑定命令 --> Command="{Binding PlacementTarget.Tag.TimePointOnCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" <!-- 可选:传递当前Customer实体作为命令参数 --> CommandParameter="{Binding}" /> </ContextMenu> </TextBlock.ContextMenu> </TextBlock>
方法二:直接通过PlacementTarget查找祖先DataContext
另一种写法是直接从PlacementTarget向上查找UserControl的DataContext,无需Tag:
<TextBlock.ContextMenu> <ContextMenu> <MenuItem Header="An Zeitpunkt festlegen" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}, Path=DataContext.TimePointOnCommand, Source={x:Reference Name=YourUserControlName}}" /> </ContextMenu> </TextBlock.ContextMenu>
注意:此方法需要给UserControl设置x:Name属性,比如x:Name="ViewA",然后将YourUserControlName替换为实际名称。
补充说明
如果需要在命令中操作当前的Customer实体,添加CommandParameter="{Binding}"即可,ViewModel的命令可以改为泛型版本来接收参数:
public DelegateCommand<Customer> TimePointOnCommand { get; private set; } // 在构造函数中初始化 TimePointOnCommand = new DelegateCommand<Customer>(SetOn); private void SetOn(Customer customer) { // 处理选中的customer实体 }
内容的提问来源于stack exchange,提问作者Mathias F

