Вызов команды в 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 шт):
Контекстное меню не является частью визуального дерева окна, и поэтому не имеет Window
в качестве своего предка. Следовательно, относительный источник в RelativeSource
не находится и получаем ошибку привязки.
При этом контекстное меню наследует DataContex
элемента в котором было вызвано (ListBox
), который, в свою очередь, наследует DataContex
окна. Поэтому в этом случае достаточно
<MenuItem Header="Удалить" Command="{Binding DeleteCategoryCommand}"/>
Остановился на следующем решении:
Объявил 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;
}
В целом сейчас вроде бы всё работает так, как я хотел. Но в плане правильности применённых решений вот вообще не уверен.