-1

I am using .NET Core 3.1 WPF.

Question 1. Is defining the <ContextMenu> inside of the item template, like this one a recommended way? If, for example, there are 100 items, won't that create 100 invisible context menus?

Question 2. If it is not efficient to define the <ContextMenu> inside of the item template, I want to avoid defining <ItemsControl.ContextMenu> like this one, because that would show the context menu, if the user right-clicks on an empty (where there is no items) area. It seems efficient to define one <ContextMenu> in a parent control like this, but the source code of the existing question is a little bit too complex, and I could not get it work.

Below is a simple example code. How can I get Dog object that is bound to the StackPanel in the context menu click event ShowBreed?

<Window x:Class="deletewpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="600">

    <ItemsControl Name="DogList">
        <ItemsControl.Resources>
            <ContextMenu x:Key="ItemContextMenu"
                         DataContext="{Binding PlacementTarget.Tag}">
                <MenuItem Click="ShowBreed" CommandParameter="{Binding}" >Show Breed</MenuItem>
            </ContextMenu>
        </ItemsControl.Resources>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel ContextMenu="{StaticResource ItemContextMenu}"
                            Tag="{Binding DataContext}">
                    <TextBlock FontSize="30pt" Text="{Binding Name}"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

Code behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var dogs = new List<Dog>();
        dogs.Add(new Dog { Name = "Dog1", Breed = "Shiba" });
        dogs.Add(new Dog { Name = "Dog2", Breed = "Corgi" });
        DogList.ItemsSource = dogs;
    }

    private void ShowBreed(object sender, RoutedEventArgs e)
    {
        Dog d;
        //MessageBox.Show(d.Breed);
    }
}

class Dog
{
    public string Name { get; set; }
    public string Breed { get; set; }
}
Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • The question 1 and 2 are related. Depending on the answer of the question 1, question 2 would not be necessary (because the method in question 1 would do the job, without the method in question 2). – Damn Vegetables Jul 28 '20 at 22:37

1 Answers1

0

Question 1: I believe this depend on your scenario, if you require having an individual contextmenu for every item this might be necessary Otherwise the way you did it via the use of ressource is perfect. If you ever need a separate resource per item you could add x:Shared ="False" to the resource.

I don't think the third question you mentionned (here) is necessarily more efficient, it just has different requirement.

Question 2: I think you mixed the three different questions which had different goals while not fully understanding the answers which led to some confusion. You didn't explain what it is you are trying to achieve but to answer the question in your exemple:

<ItemsControl Name="DogList">
    <ItemsControl.Resources>
        <ContextMenu x:Key="ItemContextMenu" >
            <MenuItem Click="ShowBreed">Show Breed</MenuItem>
        </ContextMenu>
    </ItemsControl.Resources>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel x:Name="PlacementTarget" ContextMenu="{StaticResource ItemContextMenu}">
                <TextBlock FontSize="30pt" Text="{Binding Name}"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

code behind:

private void ShowBreed(object sender, RoutedEventArgs e)
    {
        //The sender is the MenuItem which was clicked and its DataContext is the item (dog in the exemple)
        Dog d = ((MenuItem)sender).DataContext as Dog;
        MessageBox.Show(d.Breed);
    }

As mentioned earlier, you mixed of few things in the answer you quoted: In the first one, the question was how to bind to the viewModel instead of the item and the accepted answer was to set the Tag property of the button to the DataContext of the ItemControl and then bind to it from the ContextMenu. In the exemple you provided, this is not what you want. You want to access the dog object which is the default Datacontext of the items so you don't have to change anything.

You don't need the CommandParameter="{Binding}" here if you don't actually use a Command. This is not used by the ClickEvent.

Side note : The syntax to bind to the Datacontext is actually {Binding} and not {Binding DataContext} which is trying to access the "Datacontext" property of the source of the Binding (by default the DataContext). If you check the first answer you quoted you will see that the source is also set in the Binding (Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ItemsControl}}")

Ostas
  • 839
  • 7
  • 11
  • Thanks. XAML gets really difficult to understand/read when it gets complicated, like `DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"`. I am not sure what the heck the nested RelativeSources mean. The things like `DataContext="{Binding PlacementTarget.Tag}"` or `CommandParameter="{Binding}"` were from the existing answers. When I searched the web for ItemControls context menu, the complicated answer was shown at the top, so I thought it would be that complex. I didn't know that I simply don't need anything in the `MenuItem` to get the data – Damn Vegetables Jul 28 '20 at 22:35
  • Can't disagree with that. You just have to practice and test different scenario until it feels more natural. The concept behind Datacontext is the most important IMHO. – Ostas Jul 29 '20 at 17:02