I have a WPF DataGrid
for which I want to add a ContextMenu
using MVVM. This DataGrid
resides in a UserControl
(I removed a bunch of stuff which I don't think have to do with the essence of the problem):
<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>
This control has a DependencyProperty
called Command
to which I bind the menu's ItemsSource
and its type is ObservableCollection<LegendCommand>
with LegendCommand
being
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); }
}
}
These commands are generated in different places in my view models.
In one place (inside an Adapter
class whose Commands
are bound to the control's Commands
) I have the following:
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));
})
});
And in a different place (the view model which holds the Adapter
) I have the following:
adapter.Commands.Add(new LegendCommand
{
Header = "Select",
Command = new RelayCommand<LegendCellInfo>(info =>
{
// selection logic
})
});
My problem is that the "From inside adapter..." command is executed but "Select" isn't. I put a breakpoint in the "Select" code but it's never called.
The binding of the ContextMenu
's ItemsSource
to the Commands
property works as I see both options in the menu.
The binding of the ICommans
's probably works because one of the commands is executed.
I debugged the code and binding and have the following info:
The Commands
collection holds two elements whose hash codes are 40262542 and 26818564 (got them from running adapter.Commands[0].Command.GetHashCode()
and adapter.Commands[1].Command.GetHashCode()
in Visual Studio's Immediate Window).
The binding tracing gives the following output when I right-click the 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)
If I understand correctly, we can see that two binding operations take place - the first results in binding to 'RelayCommand'1 (hash=40262542)
and the second to 'RelayCommand'1 (hash=26818564)
- incidentally these are the hash codes for my two commands.
Also, no exceptions or other errors occur.
I'm stuck in my investigation. Where else to look?
UPDATE 1: When I change the code in "Select" command to the following instead of the code I had before:
adapter.Commands.Add(new LegendCommand
{
Header = "Select",
Command = new RelayCommand<LegendCellInfo>(info =>
{
MessageBox.Show("yes");
})
});
then the code starts working all of a sudden...
UPDATE 2:
The original command uses members of the Adapter
class. Searching online for problems with MVVMLight RelayCommand
and member functions found this SO question: RelayCommand with Argument throwing MethodAccessException
(but obviously I want to continue using MVVMLight...)
UPDATE 3: Read the MVVMLight code for RelayCommand
and it doesn't save a hard reference to the method so I think that nothing is keeping the lambda alive. Will refactor the code now and update here if it works.