【问题标题】:WPF DataGrid ContextMenu Command binding to MVVMLight RelayCommand<T> not always workingWPF DataGrid ContextMenu 命令绑定到 MVVMLight RelayCommand<T> 并不总是有效
【发布时间】:2015-03-13 20:39:05
【问题描述】:

我有一个 WPF DataGrid,我想使用 MVVM 添加一个 ContextMenu。这个DataGrid 位于UserControl 中(我删除了一堆我认为与问题本质无关的东西):

<UserControl x:Class="Legend.MarkerMultiStatisticsControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:legend="clr-namespace:Legend"
             Name="Self"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <GroupBox Header="{Binding ElementName=Self, Path=StaisticsData.Title}">
        <DockPanel>
            <DataGrid 
                      DataContext="{Binding ElementName=Self}"
                      ItemsSource="{Binding ElementName=Self, Path=StaisticsData.Statistics}"
                      SelectionMode="Single"
                      CurrentCell="{Binding ElementName=Self, Path=CurrentCell, Mode=OneWayToSource}">
                <DataGrid.ContextMenu>
                    <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext}"
                                 ItemsSource="{Binding Path=Commands}">
                        <ContextMenu.ItemContainerStyle>
                            <Style TargetType="MenuItem">
                                <Setter Property="Header" Value="{Binding Header}"></Setter>
                                <Setter Property="Command" Value="{Binding Command, diag:PresentationTraceSources.TraceLevel=High}"></Setter>
                                <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.CurrentLegendCell}"></Setter>
                            </Style>
                        </ContextMenu.ItemContainerStyle>
                    </ContextMenu>
                </DataGrid.ContextMenu>
            </DataGrid>
        </DockPanel>
    </GroupBox>
</UserControl>

这个控件有一个名为CommandDependencyProperty,我将菜单的ItemsSource 绑定到它,它的类型是ObservableCollection&lt;LegendCommand&gt;,其中LegendCommand

public class LegendCommand : ObservableObject
{
    private string _header;
    private ICommand _command;

    public string Header
    {
        get { return _header; }
        set { Set(()=>Header, ref _header, value); }
    }

    public ICommand Command
    {
        get { return _command; }
        set { Set(() => Command, ref _command, value); }
    }
}

这些命令是在我的视图模型的不同位置生成的。

在一个地方(在 Commands 绑定到控件的 CommandsAdapter 类中)我有以下内容:

Commands.Add(new LegendCommand
            {
                Header = "From inside adapter...",
                Command = new RelayCommand<LegendCellInfo>(info =>
                {
                    MessageBox.Show(string.Format("State: {0}, Property: {1}", info.State, info.PropertyName));
                })
            });

在不同的地方(包含Adapter 的视图模型)我有以下内容:

adapter.Commands.Add(new LegendCommand
            {
                Header = "Select",
                Command = new RelayCommand<LegendCellInfo>(info =>
                {
                    // selection logic
                })
            });

我的问题是“从内部适配器...”命令已执行,但“选择”未执行。我在“选择”代码中放置了一个断点,但它从未被调用过。

ContextMenuItemsSource 绑定到Commands 属性有效,因为我在菜单中看到了这两个选项。 ICommans 的绑定可能有效,因为执行了其中一个命令。

我调试了代码和绑定,得到了以下信息:

Commands 集合包含两个元素,其哈希码为 40262542 和 26818564(通过在 Visual Studio 的即时窗口中运行 adapter.Commands[0].Command.GetHashCode()adapter.Commands[1].Command.GetHashCode() 获得它们)。

当我右键单击DataGrid 时,绑定跟踪给出以下输出:

System.Windows.Data Warning: 56 : Created BindingExpression (hash=11440639) for Binding (hash=54536677)
System.Windows.Data Warning: 58 :   Path: 'Command'
System.Windows.Data Warning: 60 : BindingExpression (hash=11440639): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=11440639): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=11440639): Attach to System.Windows.Controls.MenuItem.Command (hash=54276594)
System.Windows.Data Warning: 67 : BindingExpression (hash=11440639): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=11440639): Found data context element: MenuItem (hash=54276594) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=11440639): Activate with root item LegendCommand (hash=5940773)
System.Windows.Data Warning: 108 : BindingExpression (hash=11440639):   At level 0 - for LegendCommand.Command found accessor RuntimePropertyInfo(Command)
System.Windows.Data Warning: 104 : BindingExpression (hash=11440639): Replace item at level 0 with LegendCommand (hash=5940773), using accessor RuntimePropertyInfo(Command)
System.Windows.Data Warning: 101 : BindingExpression (hash=11440639): GetValue at level 0 from LegendCommand (hash=5940773) using RuntimePropertyInfo(Command): RelayCommand`1 (hash=40262542)
System.Windows.Data Warning: 80 : BindingExpression (hash=11440639): TransferValue - got raw value RelayCommand`1 (hash=40262542)
System.Windows.Data Warning: 89 : BindingExpression (hash=11440639): TransferValue - using final value RelayCommand`1 (hash=40262542)
System.Windows.Data Warning: 56 : Created BindingExpression (hash=31617173) for Binding (hash=54536677)
System.Windows.Data Warning: 58 :   Path: 'Command'
System.Windows.Data Warning: 60 : BindingExpression (hash=31617173): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=31617173): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=31617173): Attach to System.Windows.Controls.MenuItem.Command (hash=16119107)
System.Windows.Data Warning: 67 : BindingExpression (hash=31617173): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=31617173): Found data context element: MenuItem (hash=16119107) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=31617173): Activate with root item LegendCommand (hash=16131920)
System.Windows.Data Warning: 107 : BindingExpression (hash=31617173):   At level 0 using cached accessor for LegendCommand.Command: RuntimePropertyInfo(Command)
System.Windows.Data Warning: 104 : BindingExpression (hash=31617173): Replace item at level 0 with LegendCommand (hash=16131920), using accessor RuntimePropertyInfo(Command)
System.Windows.Data Warning: 101 : BindingExpression (hash=31617173): GetValue at level 0 from LegendCommand (hash=16131920) using RuntimePropertyInfo(Command): RelayCommand`1 (hash=26818564)
System.Windows.Data Warning: 80 : BindingExpression (hash=31617173): TransferValue - got raw value RelayCommand`1 (hash=26818564)
System.Windows.Data Warning: 89 : BindingExpression (hash=31617173): TransferValue - using final value RelayCommand`1 (hash=26818564)

如果我理解正确,我们可以看到发生了两个绑定操作——第一个导致绑定到'RelayCommand'1 (hash=40262542),第二个导致绑定到'RelayCommand'1 (hash=26818564)——顺便说一下,这些是我的两个命令的哈希码。

此外,不会发生异常或其他错误。

我的调查陷入了困境。还有哪里可以看?

更新 1: 当我将“选择”命令中的代码更改为以下代码而不是之前的代码时:

adapter.Commands.Add(new LegendCommand
            {
                Header = "Select",
                Command = new RelayCommand<LegendCellInfo>(info =>
                {
                    MessageBox.Show("yes");
                })
            });

然后代码突然开始工作......

更新 2: 原始命令使用Adapter 类的成员。在线搜索 MVVMLight RelayCommand 和成员函数的问题发现了这个 SO 问题:RelayCommand with Argument throwing MethodAccessException (但显然我想继续使用 MVVMLight...)

更新 3:阅读 RelayCommand 的 MVVMLight 代码,它没有保存对该方法的硬引用,所以我认为没有什么能让 lambda 保持活力。现在将重构代码并在此处更新,如果它有效。

【问题讨论】:

    标签: wpf mvvm mvvm-light wpfdatagrid


    【解决方案1】:

    确实原因是“选择”命令没有被 MVVMLight 的弱引用保持活动状态。 我在这里更详细地写了它:http://blogs.microsoft.co.il/dinazil/2015/01/16/relaycommands-weakfuncs/

    【讨论】:

    • 像魔术一样工作。换句话说,解决方案是将 Action 存储到属性中,然后再将其分配给 RelayCommand ctor
    • @Ryan 很高兴这对某人有所帮助:-)
    • 链接已损坏!