Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
462 views
in Technique[技术] by (71.8m points)

c# - How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?

I'm using MVVM and I want to data bind my list of MenuViewModels to my maim menu. Which consists of a set of menu items and separators.

Here's my MenuItemViewModel code:

public interface IMenuItemViewModel
{
}

[DebuggerDisplay("---")]
public class SeparatorViewModel : IMenuItemViewModel
{
}

[DebuggerDisplay("{Header}, Children={Children.Count}")]
public class MenuItemViewModel : IMenuItemViewModel, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public MenuItemViewModel(string header, ICommand command, ImageSource imageSource)
    {
        Header = header;
        Command = command;
        ImageSource = imageSource;

        Children = new List<IMenuItemViewModel>();
    }

    public string Header { get; private set; }
    public ICommand Command { get; private set; }

    public ImageSource ImageSource { get; private set; }

    public IList<IMenuItemViewModel> Children { get; private set; }
}

And my Main window looks like this:

<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}"
        ItemsSource="{Binding Children}">
        <MenuItem Header="{Binding Header}"
                  Command="{Binding Command}"/>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type ViewModel:SeparatorViewModel}">
        <Separator />
    </DataTemplate>
</Window.Resources>

<DockPanel>
    <Menu DockPanel.Dock="Top"
          ItemsSource="{Binding MenuItems}">
    </Menu>
</DockPanel>

Should be very simple stuff. Unfortunately, either the menu item looks wrong or the separator is an empty menuItem (depending on what I've tried).

So, how do I get my Menu to find my two DataTemplates?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Solved my own question

After spending several hours searching the web, I found lots of examples that work against the WPF's natural intentions but none that worked with it.

Here's how to work with the Menu control and not against it...

A little Background

WPF's Menu control will normally auto create MenuItem objects for you when it is binded to a POCO collection, using the ItemsSource property.

However, this default behavior can be overridden! Here's how...

The Solution

First, you must create a class that derives from ItemContainerTemplateSelector. Or use the simple class I've created:

public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
    {
        var key = new DataTemplateKey(item.GetType());
        return (DataTemplate) parentItemsControl.FindResource(key);
    }
}

Second, you must add a reference to the MenuItemContainerTemplateSelector class to your Windows resources object, like so:

<Window.Resources>
    <Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />

Third, you must set two properties (UsesItemContainerTemplate, and ItemContainerTemplateSelector) on both the Menu and the MenuItem (which is defined in the HierarchicalDataTemplate).

Like so:

    <HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}"
        ItemsSource="{Binding Children}">
        <MenuItem Header="{Binding Header}"
                  Command="{Binding Command}"
                  UsesItemContainerTemplate ="true"
                  ItemContainerTemplateSelector=
                  "{StaticResource _menuItemContainerTemplateSelector}"/>
    </HierarchicalDataTemplate>

    <Menu DockPanel.Dock="Top"
          ItemsSource="{Binding MenuItems}"
          UsesItemContainerTemplate="True"
          ItemContainerTemplateSelector=
          "{StaticResource _menuItemContainerTemplateSelector}">
    </Menu>

Why it Works

For optimization purposes, the Menu uses the UsesItemContainerTemplate flag (which has a default value of false) to skip the DataTemplate lookup and just returns a normal MenuItem object. Therefore, we needed to set this value to true and then our ItemContainerTemplateSelector works as expected.

Happy Coding!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...