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.
Indeed the reason is that the "Select" command was not kept alive by MVVMLight's weak reference. I wrote about it in more detail here: http://blogs.microsoft.co.il/dinazil/2015/01/16/relaycommands-weakfuncs/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With