Command bind to ContextMenu (which on ListBoxItem in ListBox) don't work

14,525

Solution 1

You need a proxy to bind commands to a context menu of a listboxitem. See the answer here:

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

Solution 2

Can you use the contextmenu as a resource?

Something like:

<UserControl.Resources>

<ContextMenu x:name="contextMenuExample" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                    <MenuItem Header="Show Selected" 
                            Command="{Binding Path=DataContext.RemoveStudentCommand}"
                            CommandParameter="{Binding Path=SelectedItem}"/>
                 </ContextMenu>
</UserControl.Resources>

Then on list, do something like:

<Listbox ContextMenu = {StaticResource contextMenuExample} ... />

Or do you really want to use ItemContainerStyle?

from -> how to right click on item from Listbox and open menu on WPF

<ListBox Name="someListBox" MouseDown="someListBox_MouseDown">
    <ListBox.Resources>

        <!--Defines a context menu-->
        <ContextMenu x:Key="MyElementMenu">
            <MenuItem Header="Delete" Click="MenuItemDelete_Click"/>
        </ContextMenu>

        <!--Sets a context menu for each ListBoxItem in the current ListBox-->
        <Style TargetType="{x:Type ListBoxItem}">
             <Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
        </Style>

    </ListBox.Resources>
    <ListBoxItem>...</ListBoxItem>
    <ListBoxItem>...</ListBoxItem>
    <ListBoxItem>...</ListBoxItem>
</ListBox>

Solution 3

By moving the ContextMenu to the ListBoxItem, you've changed the DataContext from ClassDetailsViewModel(the DataContext of the ListBox) to StudentViewModel (the DataContext of the ListBoxItem). As a result, you need to change your path to access the parent ListBox's DataContext to get access to the RelayCommand.

<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext}">
                    <MenuItem Header="Show Selected" 
                            Command="{Binding Path=RemoveStudentCommand}"
                            CommandParameter="{Binding Path=SelectedItem}"/>
                 </ContextMenu>
             </Setter.Value>
         </Setter>
     </Style>
 </ListBox.ItemContainerStyle>
Share:
14,525

Related videos on Youtube

SubmarineX
Author by

SubmarineX

Updated on June 04, 2022

Comments

  • SubmarineX
    SubmarineX almost 2 years

    In WPF, with MVVM light, there's a Class(which is consist of some students), and the Class hold some Students.

    enter image description here

    Right-Click one Student's name, then will show a MessageBox, it is ok in this way:

    ClassDetailView.xaml

    <UserControl DataContext="{Binding ClassDetail, Source={StaticResource Locator}}">
        <DockPanel>
            <ListBox 
                ItemsSource="{Binding Students}" 
                DisplayMemberPath="Name">
                <ListBox.ContextMenu>
                    <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                        <MenuItem 
                            Header="Show Selected" 
                            Command="{Binding Path=DataContext.RemoveStudentCommand}"
                            CommandParameter="{Binding Path=SelectedItem}"/>
                    </ContextMenu>
                </ListBox.ContextMenu>
            </ListBox>
        </DockPanel>
    </UserControl>
    

    But, it don't work in this way (use ListBox.ItemContainerStyle):

    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="Show Selected" 
                                Command="{Binding Path=DataContext.RemoveStudentCommand}"
                                CommandParameter="{Binding Path=SelectedItem}"/>
                     </ContextMenu>
                 </Setter.Value>
             </Setter>
         </Style>
     </ListBox.ItemContainerStyle>
    

    instead of

    <ListBox.ContextMenu>
        <ContextMenu ...>
            ...
        <ContextMenu />
    </ListBox.ContextMenu>
    

    ClassDetailViewModel.cs

    namespace ContextMenu.ViewModel
    {
        public class ClassDetailViewModel : ViewModelBase
        {
            public ClassDetailViewModel()
            {
                CreateData();
            }
    
            public void CreateData()
            {
                students.Add(new StudentViewModel() { Name = "QQ" });
                students.Add(new StudentViewModel() { Name = "WW" });
                students.Add(new StudentViewModel() { Name = "EE" });
                students.Add(new StudentViewModel() { Name = "RR" });
                students.Add(new StudentViewModel() { Name = "AA" });
                students.Add(new StudentViewModel() { Name = "SS" });
                students.Add(new StudentViewModel() { Name = "DD" });
                students.Add(new StudentViewModel() { Name = "FF" });
                students.Add(new StudentViewModel() { Name = "ZZ" });
                students.Add(new StudentViewModel() { Name = "XX" });
            }
    
            public const string StudentsPropertyName = "Students";
            private ObservableCollection<StudentViewModel> students = 
                new ObservableCollection<StudentViewModel>();
            public ObservableCollection<StudentViewModel> Students
            {
                get { return students; }
                set
                {
                    if (students == value) { return; }
                    students = value;
                    RaisePropertyChanged(StudentsPropertyName);
                }
            }
    
            private RelayCommand<StudentViewModel> removeStudentCommand;
            public RelayCommand<StudentViewModel> RemoveStudentCommand
            {
                get
                {
                    return removeStudentCommand
                        ?? (removeStudentCommand =
                            new RelayCommand<StudentViewModel>(ExecuteRemoveStudentCommand));
                }
            }
            private void ExecuteRemoveStudentCommand(StudentViewModel student)
            {
                if (null == student) { return; }
                MessageBox.Show(string.Format("RemoveStudent:{0}", student.Name));
            }
        }
    }
    

    StudentViewModel.cs

    namespace ContextMenu.ViewModel
    {
        public class StudentViewModel : ViewModelBase
        {
            public const string NamePropertyName = "Name";
            private string name = "";
            public string Name
            {
                get { return name; }
                set
                {
                    if (name == value) { return; }
                    name = value;
                    RaisePropertyChanged(NamePropertyName);
                }
            }
        }
    }
    
  • SubmarineX
    SubmarineX over 10 years
    I just want to show the ContextMenu only when right-click the ListBoxItem rather than entire ListBox.
  • SubmarineX
    SubmarineX over 10 years
    In this way, it also don't work.
  • sexta13
    sexta13 over 10 years
  • SubmarineX
    SubmarineX over 10 years
    I don't think they are different if use ListBox.Resources. And use MVVM in my app, so it will be more complex.
  • sexta13
    sexta13 over 10 years
    you can have the ContextMenu in resources in the user control, and then apply it in different listboxes with the same behaviour. The only thing you will have to do in those listboxes is the style part...
  • SubmarineX
    SubmarineX over 10 years
    could you transform above to mvvm? After all, i think that is a problem with the DataContent.
  • Vasudevan Kannan
    Vasudevan Kannan over 8 years
    This solution worked fine for me ,also I had read earlier about a similar solution by Josh Smith
  • satnhak
    satnhak over 8 years
    Yes! You can also bind to a control using this technique with a RelativeSource binding. Thanks.
  • blandau
    blandau over 8 years
    This was the closest answer that worked for me. The only difference is I needed to consume the resource as a DynamicResource because my Command binding was relative to the UserControl's DataContext. Here's an answer to the difference between Static and Dynamic resource stackoverflow.com/a/200875/403725
  • Taras
    Taras over 6 years
    It actualy works, but for some wierd reason only if ContextMenu defined as resouce.