Как связать ObservableCollection ViewModels с MenuItem?
Когда я связываю элементы меню с ObservableCollection, только "внутренняя" область MenuItem доступна для кликов:
alt text http://tanguay.info/web/external/mvvmMenuItems.png
В моем представлении у меня есть это меню:
<Menu>
<MenuItem
Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
Затем я связываю его с этим DataTemplate:
<DataTemplate x:Key="MainMenuTemplate">
<MenuItem
Header="{Binding Title}"
Command="{Binding DataContext.SwitchPageCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"
Background="Red"
CommandParameter="{Binding IdCode}"/>
</DataTemplate>
Так как каждый ViewModel в ObservableCollection ManageMenuPageItemViewModels имеет свойство Заголовок и IdCode, приведенный выше код отлично работает с первого взгляда.
ОДНАКО, проблема заключается в том, что MenuItem в DataTemplate на самом деле внутри другого MenuItem (, как если бы он был привязан дважды), так что в приведенном выше DataTemplate с Фон = "Красный" есть красный флаг внутри каждого пункта меню, и только эта область может быть нажата, а не вся область самого пункта меню (например, если пользователь нажимает на область, где находится галочка, или справа или слева от внутренней области, которой можно щелкнуть), ничего не происходит, что, если у вас нет отдельного цвета, очень сбивает с толку. )
Каков правильный способ привязки MenuItems к ObservableCollection ViewModels, чтобы вся область внутри каждого MenuItem была доступна для кликов?
UPDATE:
Итак, я сделал следующие изменения, основанные на приведенном ниже совете, и теперь имею следующее:
alt text http://tanguay.info/web/external/mvvmMenuItemsYellow.png
У меня есть только TextBlock внутри моего DataTemplate, но я все еще не могу "раскрасить весь MenuItem", а просто TextBlock:
<DataTemplate x:Key="MainMenuTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
И я поместил привязку Command в Menu.ItemContainerStyle, но теперь они не запускаются:
<Menu DockPanel.Dock="Top">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Command" Value="{Binding DataContext.SwitchPageCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter" Value="{Binding IdCode}"/>
</Style>
</Menu.ItemContainerStyle>
<MenuItem
Header="MVVM" ItemsSource="{Binding MvvmMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
<MenuItem
Header="Application" ItemsSource="{Binding ApplicationMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
<MenuItem
Header="Manage" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
Ответы
Ответ 1
Я нашел использование MVVM с MenuItems очень сложным. Остальная часть моего приложения использует DataTemplates для сопряжения View с ViewModel, но это, похоже, не работает с меню из-за именно тех причин, которые вы описали. Вот как я в итоге решил это. Мой вид выглядит следующим образом:
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:MainViewModel.MainMenu)}">
<Menu.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/>
<Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/>
<Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/>
<Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/>
<Setter Property="MenuItem.Command" Value="{Binding}"/>
<Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IMenuItem.Visible),
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IMenuItem.ToolTip)}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true">
<Setter Property="MenuItem.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
</Menu>
</DockPanel>
Если вы заметили, я определил интерфейс IMenuItem, который является ViewModel для MenuItem. Вот код для этого:
public interface IMenuItem : ICommand
{
string Header { get; }
IEnumerable<IMenuItem> Items { get; }
object Icon { get; }
bool IsCheckable { get; }
bool IsChecked { get; set; }
bool Visible { get; }
bool IsSeparator { get; }
string ToolTip { get; }
}
Обратите внимание, что IMenuItem определяет IEnumerable Items, как вы получаете подменю. Кроме того, IsSeparator - это способ определить разделители в меню (еще один жесткий небольшой трюк). Вы можете увидеть в xaml, как он использует DataTrigger, чтобы изменить стиль на существующий стиль разделителя, если IsSeparator является истинным. Здесь, как MainViewModel определяет свойство MainMenu (с которым связано представление):
public IEnumerable<IMenuItem> MainMenu { get; set; }
Это, кажется, работает хорошо. Я предполагаю, что вы можете использовать ObservableCollection для MainMenu. Я на самом деле использую MEF для создания меню из частей, но после этого сами элементы статичны (хотя свойства каждого элемента меню нет). Я также использую класс AbstractMenuItem, который реализует IMenuItem и является вспомогательным классом для создания элементов меню в различных частях.
UPDATE:
Относительно проблемы с цветом, этот поток помогает?
Ответ 2
Не помещайте MenuItem
в DataTemplate
. DataTemplate
определяет содержимое MenuItem
. Вместо этого задайте посторонние свойства для MenuItem
через ItemContainerStyle
:
<Menu>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Title}"/>
...
</Style>
</Menu.ItemContainerStyle>
<MenuItem
Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
Кроме того, взгляните на HierarchicalDataTemplate
s.
Ответ 3
Вот как я сделал свое меню. Возможно, это не совсем то, что вам нужно, но я думаю, что это довольно близко.
<Style x:Key="SubmenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding MenuName}"></Setter>
<Setter Property="Command" Value="{Binding Path=MenuCommand}"/>
<Setter Property="ItemsSource" Value="{Binding SubmenuItems}"></Setter>
</Style>
<DataTemplate DataType="{x:Type systemVM:TopMenuViewModel}" >
<Menu>
<MenuItem Header="{Binding MenuName}"
ItemsSource="{Binding SubmenuItems}"
ItemContainerStyle="{DynamicResource SubmenuItemStyle}" />
</Menu>
</DataTemplate>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
TopMenuViewModel - это набор меню, который появится в строке меню. Каждый из них содержит имя MenuName, которое будет отображаться, и коллекцию под названием SubMenuItems, которую я установил как ItemsSource.
Я контролирую способ отображения SubMenuItems с помощью стиля SumMenuItemStyle. Каждый SubMenuItem имеет свое собственное свойство MenuName, свойство Command типа ICommand и, возможно, еще один набор SubMenuItems.
В результате я могу хранить всю информацию о моем меню в базе данных и динамически переключаться, какие меню отображаются во время выполнения. Вся область меню доступна и отображается правильно.
Надеюсь, что это поможет.
Ответ 4
Просто сделайте свой DataTemplate текстовым блоком (или панелью со стеклом со значком и TextBlock).