Asha
Asha

Reputation: 25

Retrieve the selected item from the menuitems of a context menu. I am using WPF, MVVM

I have a context Menu on my ListView with FilterBy as one of the Menu items in it. This Menu Item is bound to an observable collection of Menuitems(_childFilterMenuitems coming from an object ContextMenuclass). I want to retrieve the selected item from the submenu of the FilterBy MenuItem and bind to a property called SelectedItem in my MainView Model.

Everything works fine with the relay command and populating the items. However I cannot access the selected item in my main view model. I tried binding the SelectedItem Property(From the MainViewModel) to the Tag Property on the Menuitem. I cannot get this one to work. I read through several blogs about visual tree and its datacontext and Placement target.Tag, I cant figure out where to include the tag property. Please guide me so that I can establish proper bindings. I am fairly new and this website has been of great help.

In my MainWindow.xaml

 <ListView.ContextMenu >
   <ContextMenu Name="menuListContext" >
 <MenuItem Header="Reset" Name="menuReset" Command="{Binding ResetCmd}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext}" >
<MenuItem Header="Filter By"   ItemsSource="{Binding ChildFilterMenuItems}">  
<MenuItem.ItemContainerStyle>                                   
   <Style TargetType="{x:Type MenuItem}" <Setter Property="Header" Value="{Binding  Header, Mode=TwoWay}" />  
   <Setter Property="ItemsSource" Value="{Binding Path=  ChildFilterMenuItems}"/>
   <Setter Property= "Command" Value= "{Binding DataContext.FilterByCmd, RelativeSource   ={RelativeSource AncestorType={x:Type MenuItem}}}"/>
   <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=DataContext}" />
   <Setter Property="Tag" Value= "{Binding DataContext.SelectedItem, RelativeSource ={RelativeSource AncestorType=MenuItem}}"/> 
  </Style>
 </MenuItem.ItemContainerStyle>

  </ContextMenu >
</ListView.ContextMenu>

MainViewModel.xaml

  public class MainViewModel : ViewModelBase, INotifyPropertyChanged
   {
       /// <summary>
       /// The menu item selected from FilterBy Menu.
       /// </summary>
       public string SelectedItem {get;set;}

     public ObservableCollection<ContextMenuClass> ChildFilterMenuItems
     {
        get
        {
            return _childFilterMenuItems;

        }

        set
        {
            _childFilterMenuItems = value;
            // Call OnPropertyChanged whenever the property is updated
            OnPropertyChanged("ChildFilterMenuItems");
        }
    }

    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>      
    public MainViewModel()
    {  _childFilterMenuItems = new ObservableCollection<ContextMenuClass>(); 
       //populates the menuitems for FilterBy
       PopulateFilterBy  
       FilterByCmd = new RelayCommand(() => FilterByMenu(), () => true);
    }
    private void FilterByMenu()
    {
        try
        {
          string MenuName = GetExactName(SelectedItem);
         }

ContextMenuClass.cs

public class ContextMenuClass : INotifyPropertyChanged
{
    #region Fields

    private ObservableCollection<ContextMenuClass> _filterItems;

    private string _header;

    private bool _isEnabled;

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the header.
    /// </summary>
    /// <value>The header.</value>
    public string Header
    {
        get
        {
            return _header;
        }
        set
        {
            _header = value; OnPropertyChanged("Header");
        }
    }



    public bool IsEnabled
    {
        get
        {
            return _isEnabled;
        }
        set
        {
             _isEnabled = value; OnPropertyChanged("IsEnabled");
        }
    }

    public ObservableCollection<ContextMenuClass> ChildFilterMenuItems
    {
        get
        {
            return (_filterItems = _filterItems ??
             new ObservableCollection<ContextMenuClass>());
        }
    }
    /// <summary>
    /// Gets or sets the SelectedItem.
    /// </summary>
    /// <value>The header.</value>
    public object SelectedMenuItem
    {
        get
        {
            return _currentItem = Header;
        }
        set
        {
            _currentItem = value; OnPropertyChanged("SelectedMenuItem");

        }

    }       
    #endregion

    #region INotifyPropertyChanged
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    private object _currentItem;

    /// <summary>
    /// Safely raises the PropertyChanged event.
    /// </summary>
    /// <param name="property">The property name.</param>

    protected void OnPropertyChanged(string Property)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(Property));
        }
    }
    #endregion

Upvotes: 1

Views: 5215

Answers (2)

Emir
Emir

Reputation: 390

I hope this can help:

<MenuItem ItemsSource="{Binding MyList}">
    <MenuItem.ItemContainerStyle>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="Command" Value="{Binding RelativeSource={ RelativeSource FindAncestor, AncestorType={ x:Type Window}}, Path= DataContext.MyListItemCommand}"/>
            <Setter Property="CommandParameter" Value="{Binding}" />
            <Setter Property="IsCheckable" Value="True"/>
            <Setter Property="IsChecked" Value="{Binding IsChecked, Mode=OneWay}"/>
        </Style>
    </MenuItem.ItemContainerStyle>
</MenuItem>

The list is therefore MyList and the command is MyListItemCommand with command parameter being an actual item from the MyList (because Value="{Binding}" returns the actual list item).

This also means you would need to write MyListItemCommand (which implements ICommand interface) class which would manage which list item is currently checked (please note that also multiple items can be checked at the same time in this case).

Upvotes: 0

Mike Strobel
Mike Strobel

Reputation: 25623

If you want to know which ContextMenuClass corresponds to the menu item that was clicked, you should modify your CommandParameter setter as follows:

<Setter Property="CommandParameter"
        Value="{Binding}" />

And then change your FilterByCmd from a RelayCommand to a RelayCommand<ContextMenuClass>:

FilterByCmd = new RelayCommand<ContextMenuClass>(FilterByMenu, _ => true);

And add a ContextMenuClass parameter to your FilterByMenu method:

private void FilterByMenu(ContextMenuClass selectedItem)
{
    // ...
}

Upvotes: 1

Related Questions