Kiran
Kiran

Reputation: 123

How to bind List View Item's Context Menu to ICommand

I am trying to bind the context menu of the listview item to the ICommand contained in my VM but unable to bind it.

I have tried the following code to display the context menu on the listview item and bind this to my VM.

View Code

<ListView ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}" >
    <ListView.Resources>
        <ContextMenu x:Key="ItemContextMenu" ItemsSource="{Binding StudyVM}">
            <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>

    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.View>
        <GridView>
            <GridViewColumn Header="Status" DisplayMemberBinding="{Binding FullStatus}" Width="60"/>
            <GridViewColumn Header="Patient Name" DisplayMemberBinding="{Binding FullName}" Width="350"/>
        </GridView>
    </ListView.View>
</ListView>

VM Code

public sealed class StudyVM : BaseVM {
   public RelayCommand LockCommand { get; set; }

   public StudyVM() {
       LockCommand = new RelayCommand(() => ExecuteLockCommad());
   }

   void ExecuteLockCommad() {
       //Some code to be execute when menu item clicked
   }
}

I have set the DataContext of my view to StudyVM. Please note that I have skipped some code (that is off-topic and works fine) related to list view item source and list view selected item.

All view part works fine like all list view items are displayed in the list, context menu display when we click on list item. But the only problem is that the ExecuteLockCommad method not execute when we click on the menu item even I bind it to the context menu item through LockCommand.

Upvotes: 1

Views: 1380

Answers (3)

Aakanksha
Aakanksha

Reputation: 349

The problem is that the ContextMenu it not in the visual tree, so you basically have to tell the Context menu about which data context to use. You can pass the datacontext through the tag property of the container as below.

<ListView Name="lvSelectStudy" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
    <ListView.Resources>
        <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource {RelativeSource Self}}">                                
            <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>
    ...
</ListView>

Upvotes: 0

XAMlMAX
XAMlMAX

Reputation: 2363

The problem that you had with your code was the DataContext of the ContextMenu.
Your current ConextMenu Binding would resolve the command from current context. Since the ContextMenu is assigned per ListViewItem it's DataContext is whatever the ListViewItem's is. In your case StudyEntity.
This is however not a major problem, Binding can be pointed to other Context. Which in your case must be StudyVM. As that would be the DataContext of your ListView we can point it to through this snippet:

Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.LockCommand}"  

Article from MSDN about RelativeSource MarkupExtension
Another way of getting that DataContext could be done through Name. (I know I asked you why are you using it and this is why)

<ListView Name="ListView" ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}">
    <ListView.Resources>
        <ContextMenu x:Key="ContextMenu">
            <MenuItem Header="Lock" Command="{Binding ElementName=ListView, Path=DataContext.LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>
    ...
</ListView>  

This achieves the same result but, there is a down side of using names in WPF (with x:Name to be exact). Article about memory leak.

Upvotes: 3

tombobadil
tombobadil

Reputation: 150

I tried changing and further simplifying your code...

MainWindow.xaml

<Window x:Class="WpfApp5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>

    <Grid Margin="5,5,5,5">
        <ListView Name="lvSelectStudy"  >
            <!--ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}"-->
            <ListView.Resources>
                <ContextMenu x:Key="ItemContextMenu">
                    <!--ItemsSource="{Binding StudyVM}"-->
                    <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
                </ContextMenu>
            </ListView.Resources>

            <ListView.ItemContainerStyle>
                <Style TargetType="{x:Type ListViewItem}">
                    <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
                </Style>
            </ListView.ItemContainerStyle>
            <!--<ListView.View>
                <GridView>
                    <GridViewColumn Header="Status" DisplayMemberBinding="{Binding FullStatus}" Width="60"/>
                    <GridViewColumn Header="Patient Name" DisplayMemberBinding="{Binding FullName}" Width="350"/>
                </GridView>
            </ListView.View>-->
            <ListViewItem>Item1</ListViewItem>
            <ListViewItem>Item2</ListViewItem>
            <ListViewItem>Item3</ListViewItem>
            <ListViewItem>Item4</ListViewItem>
            <ListViewItem>Item5</ListViewItem>
        </ListView>
    </Grid>
</Window>

MainWindowViewModel.cs

using GalaSoft.MvvmLight.Command;

namespace WpfApp5
{
    public sealed class MainWindowViewModel : ViewModelBase
    {
        public RelayCommand LockCommand { get; set; }

        public MainWindowViewModel()
        {
            LockCommand = new RelayCommand(() => ExecuteLockCommand());
        }
        void ExecuteLockCommand()
        {
            //Some code to be execute when menu item clicked
        }
    }
}

If you put a break point in void ExecuteLockCommand(), it is being called after a right click on a ListViewItem... Notice that this method, in your code, had a different name than the one defined in your ViewModel's constructor

Upvotes: 0

Related Questions