Scott Whitlock
Scott Whitlock

Reputation: 13839

Can I apply a ContextMenu to a ContextMenuViewModel using a DataTemplate?

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?

Upvotes: 2

Views: 763

Answers (3)

Shawn
Shawn

Reputation: 1030

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.

Upvotes: 0

Timores
Timores

Reputation: 14589

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.

Upvotes: 0

Andy
Andy

Reputation: 30418

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.

Upvotes: 1

Related Questions