1

I have a Window with a viewmodel as the DataContext. My window contains an ItemsControl with its ItemsSource bound to a viewmodel object collection.

My ItemsControl uses a Canvas for the ItemsPanelTemplate. The DataTemplate includes an Ellipse with a ContextMenu associated with it; that ContextMenu has a single MenuItem.

My Window viewmodel contains an ICommand which takes an object parameter (the current ItemsSource item).

I am attempting to right-click on one of the ellipses within my ItemsControl and bring up the ContextMenu, then click on the MenuItem to execute the ICommand and pass the current ItemsSource item as a parameter.

For some reason, I am unable to access the Window's DataContext from within the ContextMenu. I've tried to research this issue, but none of the proposed solutions seem to work for me.

I've tried gaining access to the Window datacontext by using the Window's elementname, and by finding ancestor type, no luck however.

public class VM_MainWindow : ViewModelBase
{
    public DelegateCommand<EllipseObject> TestClick { get; }

    // constructor
    public VM_MainWindow()
    {
        // initialization
        EllipseCollection = DynamicData.Ellipses;

        ScreenResolutionWidth = ClientConfig.Info.ScreenResolutionWidth - 8;
        ScreenResolutionHeight = ClientConfig.Info.ScreenResolutionHeight - 120;

        // commands
        TestClick = new DelegateCommand<EllipseObject>(OnTestClickCommand);
    }

    #region "Properties"
    private ObservableCollection<EllipseObject> _ellipseCollection;
    public ObservableCollection<EllipseObject> EllipseCollection
    {
        get => _ellipseCollection;
        set
        {
            _ellipseCollection = value;
            OnPropertyChanged("EllipseCollection");
        }
    }

    private int _screenResolutionWidth;
    public int ScreenResolutionWidth
    {
        get => _screenResolutionWidth;
        set
        {
            _screenResolutionWidth = value;
            OnPropertyChanged("ScreenResolutionWidth");
        }
    }

    private int _screenResolutionHeight;
    public int ScreenResolutionHeight
    {
        get => _screenResolutionHeight;
        set
        {
            _screenResolutionHeight = value;
            OnPropertyChanged("ScreenResolutionHeight");
        }
    }
    #endregion

    private void OnTestClickCommand(EllipseObject eObj)
    {
        MessageBox.Show("Ellipse Name: " + eObj.DisplayName, "Test", MessageBoxButton.OK, MessageBoxImage.Information);
    }
}

public class DelegateCommand<T> : System.Windows.Input.ICommand
{
    private readonly Predicate<T> _canExecute;
    private readonly Action<T> _execute;

    public DelegateCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;

        return _canExecute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
    }

    public void Execute(object parameter)
    {
        _execute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

and here's the MainWindow.xaml, note that my window is named x:Name="mainWindow":

<Window.DataContext>
    <viewModels:VM_MainWindow/>
</Window.DataContext>

<Grid Background="Black">
    <Grid.RowDefinitions>
        <RowDefinition Height="27px"/>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="30px"/>
    </Grid.RowDefinitions>

    <Grid x:Name="icContainerGrid" Grid.Row="1" Background="{Binding TrackMapBackground}">
        <Grid>
            <!-- ELLIPSES -->
            <ItemsControl ItemsSource="{Binding EllipseCollection}">

                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas IsItemsHost="True" Width="{Binding ScreenResolutionWidth}" Height="{Binding ScreenResolutionHeight}"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>

                <ItemsControl.ItemContainerStyle>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Left" Value="{Binding X1}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Y1}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>

                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Ellipse Width="24" Height="24" Fill="Red">
                            <Ellipse.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Header="Menu item 1" Command="{Binding ElementName=mainWindow, Path=DataContext.TestClick}" CommandParameter="{Binding}"/>
                                </ContextMenu>
                            </Ellipse.ContextMenu>
                        </Ellipse>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Grid>
</Grid>

I expected my command to fire upon clicking the MenuItem, however it does not.

I receive the following binding error upon running the application:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=mainWindow'. BindingExpression:Path=DataContext.TestClick; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

mt1985
  • 85
  • 1
  • 7
  • Try to use relative source binding instead of element name to find a window, like `RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}` Also your error says about mainGrid instead of mainWindow – Pavel Anikhouski Apr 13 '19 at 06:01
  • Pavel, the error is correct for what I was doing, I just forgot to update my post to reflect previous changes. I tried your suggestion of using relative source, but still no luck – mt1985 Apr 13 '19 at 06:13

2 Answers2

1

ISSUE RESOLVED:

After a lot of reading, I finally managed to find a solution which involved using the template object's tag to store the data context, which can then be accessed from the ContextMenu's MenuItem via the ContextMenu's PlacementTarget property:

                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Ellipse Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding DisplayColor}" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}">
                                <Ellipse.ContextMenu>
                                    <ContextMenu>
                                        <MenuItem Header="Menu item 2" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.TestClick}" CommandParameter="{Binding}"/>
                                    </ContextMenu>
                                </Ellipse.ContextMenu>
                            </Ellipse>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>

For some reason, I was completely unable to access the datacontext of the Window from within the ContextMenu.. this fix probably isn't ideal, but will work for me.

Hope this helps others who run into a similar issue.

mt1985
  • 85
  • 1
  • 7
-1

If you want to access DataContext within DataTemplate, try this:

{Binding DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}
Oscar
  • 1
  • 1
  • Welcome to Stack Overflow! Please add some explanation to your answer as explanations are an important element here on Stack Overflow. You can edit your answer by clicking on the `edit` link below it. Her you find our guide [How to write a good answer](https://stackoverflow.com/help/how-to-answer). Thanks! – David Apr 13 '19 at 06:42