views:

190

answers:

3

I have a ViewModel (AbstractContextMenu) that represents my context menu (IContextMenu), and I bind a real ContextMenu to it with a DataTemplate:

<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
    <ContextMenu x:Name="contextMenu" 
          ItemsSource="{Binding Path=(local:IContextMenu.Items)}"
          IsEnabled="{Binding Path=(local:IContextMenu.IsEnabled)}"/>
</DataTemplate>

Then I have a dummy ConcreteContextMenu for testing that just inherits from AbstractContextMenu. AbstractContextMenu just implements this interface:

public interface IContextMenu : IExtension
{
    IEnumerable<IMenuItem> Items { get; set; }
    bool IsEnabled { get; set; }
}

I'm using it as a property of another ViewModel object:

    public IContextMenu ContextMenu
    {
        get
        {
            return m_ContextMenu;
        }
        protected set
        {
            if (m_ContextMenu != value)
            {
                m_ContextMenu = value;
                NotifyPropertyChanged(m_ContextMenuArgs);
            }
        }
    }
    private IContextMenu m_ContextMenu = new ConcreteContextMenu();
    static readonly PropertyChangedEventArgs m_ContextMenuArgs =
        NotifyPropertyChangedHelper.CreateArgs<AbstractSolutionItem>(o => o.ContextMenu);

Then I bind a StackPanel to that ViewModel and bind the ContextMenu property on the StackPanel to the ContextMenu property of the ViewModel:

    <StackPanel Orientation="Horizontal" 
                ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
                ContextMenuOpening="stackPanel_ContextMenuOpening">
    <!-- stuff goes in here -->
    </StackPanel>

When I run this, the ContextMenuOpening event on the StackPanel is fired, but the ContextMenu is never displayed. I'm not sure if I can even do this (apply a ContextMenu to a ContextMenu ViewModel using a DataTemplate). Anyone know?

+1  A: 

What is the type of AbstractSolutionItem.ContextMenu? If it corresponds to the ContextMenu property in your question, then the problem could be that the type is wrong. The ContextMenu property of FrameworkElement is expecting an actual ContextMenu, not an IContextMenu. Try checking the output window while debugging your app - you might get an error message stating that this is the problem.

Instead of using a DataTemplate to define your ContextMenu, just put the contents of the template StackPanel.ContextMenu:

<StackPanel Orientation="Horizontal" 
    ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
    ContextMenuOpening="stackPanel_ContextMenuOpening">
    <StackPanel.ContextMenu DataContext="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}">
        <ContextMenu x:Name="contextMenu" 
            ItemsSource="{Binding Path=Items}"
            IsEnabled="{Binding Path=IsEnabled}"/>
    </StackPanel.ContextMenu>
    <!-- stuff goes in here -->
</StackPanel>

That should get you most of the way there. However, there is still a problem since the ContextMenu does not know how to create a MenuItem from an IMenuItem. To solve this, create an ItemTemplate for the ContextMenu, which binds members of IMenuItem to `MenuItem.

Andy
Hi Andy. WPF recognizes the DataTemplate I defined for AbstractMenuItem, and it does create the menu just fine (that is the heart of MVVM after all), but as you said, it seems I can't just bind the StackPanel.ContextMenu property to a random object and have WPF apply my DataTemplate to it. I was hoping for a way around this, but it looks like my options are nil. Thanks. Too bad - I'm trying to go full MVVM, but it's hard.
Scott Whitlock
This didn't really solve my problem, but as it looks like there isn't a solution to what I wanted to do, it is the "correct" one. ;)
Scott Whitlock
A: 

Could you shed some light on the syntax used in the ItemsSource property in the DataTemplate ? Using parentheses usually means an attached property. And Items does not seem to be an attached property defined by IContextMenu (as an interface cannot define such a property).

The DataTemplate is linked to an object of type AbstractContextMenu which has a property called Items. So, the DataTemplate could simply reference it like this:

<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
    <ContextMenu x:Name="contextMenu" 
          ItemsSource="{Binding Path=Items)}"
          IsEnabled="{Binding Path=IsEnabled}"/>
</DataTemplate>

If the AbstractSolutionItem class is the VM of the StackPanel, you could bind it like this:

<StackPanel Orientation="Horizontal" 
            ContextMenu="{Binding Path=ContextMenu}"
            ContextMenuOpening="stackPanel_ContextMenuOpening">
<!-- stuff goes in here -->
</StackPanel>

Of course, the DataTemplate must be "accessible" from the StackPanel.

Timores
You may not be familiar with this binding syntax, but it works exactly the same as you've described, only it's more efficient because it can be resolved at compile time rather than runtime. I forget the name of the syntax, but I converted to using it where possible because with MVVM you do a lot of binding and performance matters.
Scott Whitlock
A: 

Bind the ContextMenu property of your view (StackPanel in this scenario) to the ContextMenu property of your ViewModel and provide a IValueConverter to the binding that will create the ContextMenu object and set the IContextMenu to it's DataContext.

Shawn