WPF DataGrid ContextMenu 命令绑定到 MVVMLight RelayCommand <T> 并不总是有效
WPF DataGrid ContextMenu Command binding to MVVMLight RelayCommand<T> not always working
我有一个 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>
此控件有一个名为 Command
的 DependencyProperty
,我将菜单的 ItemsSource
绑定到该控件,其类型为 ObservableCollection<LegendCommand>
,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); }
}
}
这些命令是在我的视图模型中的不同位置生成的。
在一个地方(在 Adapter
class 内,其 Commands
绑定到控件的 Commands
)我有以下内容:
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
})
});
我的问题是 "From inside adapter..." 命令已执行,但 "Select" 未执行。我在 "Select" 代码中放置了一个断点,但它从未被调用过。
ContextMenu
的 ItemsSource
与 Commands
属性 的绑定有效,因为我在菜单中看到了这两个选项。
ICommans
的绑定可能有效,因为执行了其中一个命令。
我调试了代码和绑定,得到以下信息:
Commands
集合包含两个哈希码分别为 40262542 和 26818564 的元素(从 运行 adapter.Commands[0].Command.GetHashCode()
和 adapter.Commands[1].Command.GetHashCode()
in Visual Studio's立即 Window).
当我右键单击 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:
当我将 "Select" 命令中的代码更改为以下代码而不是之前的代码时:
adapter.Commands.Add(new LegendCommand
{
Header = "Select",
Command = new RelayCommand<LegendCellInfo>(info =>
{
MessageBox.Show("yes");
})
});
然后代码突然开始工作...
更新 2:
原始命令使用 Adapter
class 的成员。在线搜索 MVVMLight RelayCommand
和成员函数的问题发现了这个 SO 问题:RelayCommand with Argument throwing MethodAccessException
(但显然我想继续使用 MVVMLight...)
更新 3:阅读 RelayCommand
的 MVVMLight 代码,它不会保存对该方法的硬引用,所以我认为没有什么能让 lambda 保持活动状态.现在将重构代码并在此处更新。
的确,原因是 "Select" 命令没有被 MVVMLight 的弱引用保持活动状态。
我在这里写得更详细:http://blogs.microsoft.co.il/dinazil/2015/01/16/relaycommands-weakfuncs/
我有一个 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>
此控件有一个名为 Command
的 DependencyProperty
,我将菜单的 ItemsSource
绑定到该控件,其类型为 ObservableCollection<LegendCommand>
,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); }
}
}
这些命令是在我的视图模型中的不同位置生成的。
在一个地方(在 Adapter
class 内,其 Commands
绑定到控件的 Commands
)我有以下内容:
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
})
});
我的问题是 "From inside adapter..." 命令已执行,但 "Select" 未执行。我在 "Select" 代码中放置了一个断点,但它从未被调用过。
ContextMenu
的 ItemsSource
与 Commands
属性 的绑定有效,因为我在菜单中看到了这两个选项。
ICommans
的绑定可能有效,因为执行了其中一个命令。
我调试了代码和绑定,得到以下信息:
Commands
集合包含两个哈希码分别为 40262542 和 26818564 的元素(从 运行 adapter.Commands[0].Command.GetHashCode()
和 adapter.Commands[1].Command.GetHashCode()
in Visual Studio's立即 Window).
当我右键单击 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: 当我将 "Select" 命令中的代码更改为以下代码而不是之前的代码时:
adapter.Commands.Add(new LegendCommand
{
Header = "Select",
Command = new RelayCommand<LegendCellInfo>(info =>
{
MessageBox.Show("yes");
})
});
然后代码突然开始工作...
更新 2:
原始命令使用 Adapter
class 的成员。在线搜索 MVVMLight RelayCommand
和成员函数的问题发现了这个 SO 问题:RelayCommand with Argument throwing MethodAccessException
(但显然我想继续使用 MVVMLight...)
更新 3:阅读 RelayCommand
的 MVVMLight 代码,它不会保存对该方法的硬引用,所以我认为没有什么能让 lambda 保持活动状态.现在将重构代码并在此处更新。
的确,原因是 "Select" 命令没有被 MVVMLight 的弱引用保持活动状态。 我在这里写得更详细:http://blogs.microsoft.co.il/dinazil/2015/01/16/relaycommands-weakfuncs/