Имеется в виду создание иерархической модели и презентера для меню, а так же байндинг и написание стиля отображения меню. Неясно, как присоединять обработчики событий клика на пункты меню. Могу предположить, что при помощи команд. Что-то типа этого: у модели пункта меню DataMenuItem вводим свойство типа ICommand, а затем байндим свойство MenuItem.Command на это свойство модели. Непонятно, как передать параметр в команду. В моем случае мне в качестве параметра нужно передать DataContext того визуального элемента, от которого вызывается контекстное меню.
Как в WPF создавать контекстное меню в стиле MVP?
Лучший ответ:
В статье Using MVVM with Menus in WPF рассказывается о том, как реализовать контекстное меню в рамках паттерна MVVM. Идея такая — создается собственный класс для хранения параметров пункта меню:
public class MenuItem
{
public string Text { get; set; }
public List<MenuItem> Children { get; private set; }
public ICommand Command { get; set; }
public MenuItem(string item)
{
Text = item;
Children = new List<MenuItem>();
}
}
Используя этот класс, создается дерево данных меню для определенного элемента:
public List<MenuItem> MenuOptions
{
get {
var menu = new List<MenuItem>();
if (SupportedFileFormats.Count > 0)
{
var mi = new MenuItem("O_pen");
foreach(var fl in SupportedFileFormats)
{
var sff = fl;
mi.Children.Add(new MenuItem(fl.Attributes.Description)
{ Command = new DelegatingCommand(() => { LoadFromFormat(sff); })});
}
menu.Add(mi);
}
menu.Add(new MenuItem("Close _All") { Command = new DelegatingCommand(OnCloseAll, () => FileList.Count > 0)});
return menu;
}
}
На стороне xaml создается стиль, который связывает элемент управления пункта меню и наш собственный класс данных:
<Style x:Key="ContextMenuItemStyle">
<Setter Property="MenuItem.Header" Value="{Binding Text}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Children}"/>
<Setter Property="MenuItem.Command" Value="{Binding Command}" />
</Style>
Далее, стиль присваивается конкретному контролу и все работает:
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="16" Height="16" />
<TextBlock Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Header}" />
<StackPanel.ContextMenu>
<!-- Собственно меню -->
<ContextMenu ItemContainerStyle="{StaticResource ContextMenuItemStyle}" ItemsSource="{Binding MenuOptions}" />
</StackPanel.ContextMenu>
</StackPanel>
Новые ответы
Есть мнение, что для передачи параметра в команду меню можно использовать такой код:
<MenuItem CommandParameter="{Binding}" .../>
При этом, если сам элемент MenuItem построен без помощи binding, то в команду передастся именно DataContext родительского элемента контекстного меню.
Если же само контекстное меню генерируется с помощью binding, то его DataContext отличается от родительского. Тогда, для получения родительского DataContext нужно использовать код типа
<MenuItem CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=DataContext}" />
Вот это отлично! Я примерно так и делал, но были проблемы с поставщиком команд и параметрами команд.