4

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
  • 850
  • 5
  • 19
  • 38

3 Answers3

4

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/

James
  • 954
  • 2
  • 13
  • 27
3

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>
Community
  • 1
  • 1
sexta13
  • 1,558
  • 1
  • 11
  • 19
  • 1
    I just want to show the ContextMenu only when right-click the ListBoxItem rather than entire ListBox. – SubmarineX Oct 18 '13 at 11:07
  • take a look at:http://stackoverflow.com/questions/9549231/how-to-right-click-on-item-from-listbox-and-open-menu-on-wpf – sexta13 Oct 18 '13 at 11:15
  • I don't think they are different if use ListBox.Resources. And use MVVM in my app, so it will be more complex. – SubmarineX Oct 18 '13 at 11:30
  • 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... – sexta13 Oct 18 '13 at 11:33
  • could you transform above to mvvm? After all, i think that is a problem with the DataContent. – SubmarineX Oct 18 '13 at 11:37
  • 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 http://stackoverflow.com/a/200875/403725 – blandau Jan 15 '16 at 17:29
1

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>
Barracoder
  • 3,696
  • 2
  • 28
  • 31