You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Prism项目DataGrid单元格ContextMenu绑定ViewModel命令失败求助

问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 16:43:20