Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF MVVM ContextMenu binding IsOpen to Model

Tags:

c#

mvvm

wpf

I have a button with a context menu associated to it. I can right click on the button and show the context menu as you would expect, however I want to be able to show the context menu after another event, such as a left click, or a drag and drop style event.

I am attempting to do this by binding the IsOpen property of the context menu to the view model, but this is not working as expected. On first left click of the button, nothing happens, although I can see the property on the view model that IsOpen is bound to being updated correctly.

If I right click, the menu will display correctly, and after this if I left click the menu will also show.

Has anyone ever seen this or have any ideas on what I need to do to get the contextMenu to open when the IsOpen property is updated?

XAML

<Window x:Class="PopUpTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mvp="clr-namespace:PopUpTest"
    Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
    <mvp:MainWindowViewModel />
</Window.DataContext>
<Grid>
    <Grid.Resources>
        <ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" IsOpen="{Binding PopupViewModel.IsOpen, Mode=TwoWay}">
            <MenuItem Header="Delete" />
        </ContextMenu>
    </Grid.Resources>

    <Button Command="{Binding DisplayPopupCommand}" ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"/>
</Grid>

Code Behind

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.Commands;

namespace PopUpTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class MainWindowViewModel : BaseViewModel
{
    private readonly PopupViewModel<ChildViewModel> _popupViewModel;
    private readonly DelegateCommand _displayPopupCommand;

    public MainWindowViewModel()
    {
        _popupViewModel = new PopupViewModel<ChildViewModel>(new ChildViewModel { FirstName = "John", LastName = "Doe" });
        _displayPopupCommand = new DelegateCommand(() => { PopupViewModel.IsOpen = PopupViewModel.IsOpen == false; Console.WriteLine(PopupViewModel.IsOpen); });
    }

    public ICommand DisplayPopupCommand
    {
        get { return _displayPopupCommand; }
    }

    public PopupViewModel<ChildViewModel> PopupViewModel
    {
        get { return _popupViewModel; }
    }
}

public class PopupViewModel<T> : BaseViewModel
{
    private readonly T _data;

    public PopupViewModel(T data)
    {
        _data = data;
    }

    public T Data
    {
        get { return _data; }
    }

    private bool _isOpen;
    public bool IsOpen
    {
        get { return _isOpen; }
        set
        {
            if (_isOpen != value)
            {
                _isOpen = value;
                OnPropertyChanged("IsOpen");
            }
        }
    }
}

public class ChildViewModel : BaseViewModel
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (_firstName != value)
            {
                _firstName = value;
                OnPropertyChanged("FirstName");
            }
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            if (_lastName != value)
            {
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }

}

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
}
like image 641
coffeecoder Avatar asked Apr 08 '14 09:04

coffeecoder


1 Answers

I have been able to solve this by introducing a BindingProxy to the XAML as described in the answer to this post on the MSDN forums:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/a4149979-6fcf-4240-a172-66122225d7bc/wpf-mvvm-contextmenu-binding-isopen-to-view-model?forum=wpf

The binding proxy gets around the issue where the ContextMenu does not have a DataContext until it first displays after a right click.

The issue is discussed further here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

like image 80
coffeecoder Avatar answered Oct 10 '22 05:10

coffeecoder