Вызов команды в ContextMenu из DataContext Window

Столкнулся со следующей проблемой, необходимо настроить выпадающее меню у ListBox. Нужно в нём вызывать команды из DataContext окна. Пробую сделать это вот так вот :

 <ListBox ItemsSource="{Binding Categories}">
     <ListBox.ContextMenu>
         <ContextMenu>
             <MenuItem Header="Удалить" Command="{Binding DataContext.DeleteCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
         </ContextMenu>
     </ListBox.ContextMenu>
     <ListBox.ItemTemplate>
             <DataTemplate>
             <Border Background="{Binding BackGroundColor, Converter={StaticResource ColorToBrushConverter}}" Padding="5 0" MinWidth="100">
                 <StackPanel>
                     <TextBlock Text="{Binding Name}"/>
                     <Button Content="TEST" Command="{Binding DataContext.DeleteCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
                 </StackPanel>                                                            
                 </Border>                                                        
             </DataTemplate>
         </ListBox.ItemTemplate>
 </ListBox>

При этом у объекта Button тот же самый Binding нормально срабатывает. А у MenuItem команда почему-то не вызывается. Что я делаю не так и как достучаться до DataContext window из ContextMenu?

Не уверен влияет или нет, всё происходит внутри TreeView/DataTemplate и у ContextMenu текущий DataContext похоже берётся от DataTemplate

 <TreeView ItemsSource="{Binding Ports}" >
    <TreeView.Resources>
   ...
     <DataTemplate DataType="{x:Type model:Device}">

Ответы (2 шт):

Автор решения: Cost

Контекстное меню не является частью визуального дерева окна, и поэтому не имеет Window в качестве своего предка. Следовательно, относительный источник в RelativeSource не находится и получаем ошибку привязки.
При этом контекстное меню наследует DataContex элемента в котором было вызвано (ListBox), который, в свою очередь, наследует DataContex окна. Поэтому в этом случае достаточно

<MenuItem Header="Удалить" Command="{Binding DeleteCategoryCommand}"/>
→ Ссылка
Автор решения: Pavel

Остановился на следующем решении:

Объявил DataContext не на стороне кода, а в XAML. Но в моём случае это осложнилось тем, что мой ViewModel не имеет конструкторов без параметров, и в качестве параметра принимает вызывающее его окно.

Поэтому пришлось создать, видимо, реализацию паттерна "Фабричный метод"?

public static MainWindowViewModel CreateWithWindow(Window window)
{
    return new MainWindowViewModel(window);
}

Дальше на стороне XAML создаём объект:

<Window.Resources>
    <ObjectDataProvider x:Key="MainWindowViewModelProvider" ObjectType="{x:Type cmd:MainWindowViewModel}">
        <ObjectDataProvider.ConstructorParameters>
            <Window>{Binding RelativeSource={RelativeSource AncestorType=Window}}</Window>
        </ObjectDataProvider.ConstructorParameters>
    </ObjectDataProvider>
</Window.Resources>

и назначаем его в качестве DataContext окна:

<Window.DataContext>
    <Binding Source="{StaticResource MainWindowViewModelProvider}" />
</Window.DataContext>

И теперь из контекстного меню можно будет вызвать эту команду через статический объект. Так же есть некоторые заморочки с тем, чтобы передать в качестве параметра выбранный объект:

<ListBox.ContextMenu>
    <ContextMenu>                                                     
        <MenuItem Header="Удалить" Command="{Binding DeleteCategoryCommand, Source={StaticResource MainWindowViewModelProvider}}"
            CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
        />
    </ContextMenu>
</ListBox.ContextMenu>

И похоже вот здесь я что-то делаю не правильно:

<ObjectDataProvider x:Key="MainWindowViewModelProvider" ObjectType="{x:Type cmd:MainWindowViewModel}">
            <ObjectDataProvider.ConstructorParameters>
                <Window>{Binding RelativeSource={RelativeSource AncestorType=Window}}</Window>
            </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

хотя, возможно, отсюда это нельзя сделать в принципе? В общем в конструктор, похоже, просто передался объект new Window, а не наше окно.

Также пробовал передать вот так:

<ObjectDataProvider x:Key="MainWindowViewModelProvider" ObjectType="{x:Type cmd:MainWindowViewModel}">
    <ObjectDataProvider.ConstructorParameters>
        <Binding RelativeSource="{RelativeSource AncestorType=Window}" />
    </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

Но здесь уже, видимо, проблеме с тем, что инициализация происходит в каком-то неврном порядке. В общем, сбой на этапе

InitializeComponent();

Похоже, от идеи передавать само окно в качестве параметра в конструктор ViewModel придётся отказаться.

Применил обходное решение с добавлением параметра уже после инициализации:

public MainWindow()
{           
    InitializeComponent();
    (DataContext as MainWindowViewModel).MainWindow = this;
}

В целом сейчас вроде бы всё работает так, как я хотел. Но в плане правильности применённых решений вот вообще не уверен.

→ Ссылка