0

I have a context menu on a button, which I have added in xaml as follows:

<Button Content="{Binding MaterialLabel}"
        Command="{Binding SwapCommand}">
    <Button.Resources>
        <ContextMenu x:Key="RClickSwaps" ItemsSource="{Binding SwapList}">
            <ContextMenu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Header" Value="{Binding targetMaterial.Component_Code}"/>
                    <Setter Property="Command" Value="{Binding RClickSwapCommand}"/>
                    <Setter Property="ToolTip">
                        <Setter.Value>
                            <ContentControl>
                                <Border CornerRadius="2" BorderThickness="2">
                                    <TextBlock Text="{Binding targetMaterial.Component_Name}"/>
                                </Border>
                            </ContentControl>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ContextMenu.ItemContainerStyle>
        </ContextMenu>
    </Button.Resources>
</Button>

The Binding for the UserControl as a whole is to a view model which contains a collection (SwapList) of classes that contain the command and TargetMaterial properties for the context menu used above:

//Control View Model
public ObservableCollection<SwapMenuItem> SwapList { get; set; }

//... Some irrelevant Code excluded ...

SwapList = new ObservableCollection<SwapMenuItem>();
var swaps = new ObservableCollection<Raw_Materials>(await ComponentDataService.GET_MaterialSwaps(New_Material));
foreach (var item in swaps)
{
    SwapList.Add(new SwapMenuItem(item, this));
}
OnPropertyChanged("SwapList");

The SwapMenuItem itself contains the following properties and a command that look a bit like this:

public class SwapMenuItem : Observable
{
    public ComponentChangeViewModel ComponentChangeViewModel { get; set; }
    public Raw_Materials TargetMaterial { get; set; }
    public ICommand RClickSwapCommand {get;set;}

    public SwapMenuItem(Raw_Materials raw_Materials, ComponentChangeViewModel componentChangeViewModel)
    {
        ComponentChangeViewModel = componentChangeViewModel;
        TargetMaterial = raw_Materials;
    }

    private void Swap()
    {
        //The command actually calls this method, but I excluded the long winded code
    }
}

My problem is that the context menu does not appear when I right click the button, nothing happens at all. I am trying to work within the MVVM pattern, but am I missing some sort of event to make the context menu appear on right click? Most of the advice online does not use the context menu as part of Button.Resources, but this seems to be the best way to do it according to some (for instance here). None of these examples contain anything to make the context menu appear on right click, so I figured it must be there by default?

Why can I not make the context menu appear, even when there are values in the ItemsSource collection?

thatguy
  • 21,059
  • 6
  • 30
  • 40
High Plains Grifter
  • 1,357
  • 1
  • 12
  • 36

1 Answers1

2

You only define a context menu with a key in the resources. The Button does not automatically know that it should use it. You have to set this resource to the ContextMenu property of Button.

You can either use the StaticResource markup extension in element property syntax like this:

<Button Content="{Binding MaterialLabel}"
        Command="{Binding SwapCommand}">
    <Button.Resources>
        <ContextMenu x:Key="RClickSwaps" ItemsSource="{Binding SwapList}">
            <!-- ...your context menu markup. -->
        </ContextMenu>
    </Button.Resources>
    <Button.ContextMenu>
       <StaticResource ResourceKey="RClickSwaps"/>
    </Button.ContextMenu>
</Button>

Or you can set it in attribute syntax by using the DynamicResource markup extension. Note that StaticResource does not work here, as the resource RClickSwaps is defined later in XAML, so it cannot be resolved statically at this point.

<Button Content="{Binding MaterialLabel}"
        Command="{Binding SwapCommand}"
        ContextMenu="{DynamicResource RClickSwaps}">
    <Button.Resources>
        <ContextMenu x:Key="RClickSwaps" ItemsSource="{Binding SwapList}">
            <!-- ...your context menu markup. -->
        </ContextMenu>
    </Button.Resources>
</Button>

Of course you can also just assign the context menu directly, without Resources.

<Button Content="{Binding MaterialLabel}"
        Command="{Binding SwapCommand}">
   <Button.ContextMenu>
      <ContextMenu ItemsSource="{Binding StringItems}">
         <!-- ...your context menu markup. -->
      </ContextMenu>
   </Button.ContextMenu>
</Button>
thatguy
  • 21,059
  • 6
  • 30
  • 40
  • Yes, so simple, but apparently missed out of a lot of the examples online, that only show the resources, assuming that the reader knows to also use it (which to be fair, I probably should have). This works perfectly - is there any particular reason to choose one option over another, or are they all totally equivalent? – High Plains Grifter Jun 18 '21 at 10:24
  • 1
    @HighPlainsGrifter They are not equivalent. I personally prefer to put context menus directly in controls (last example) or share them in a resource dictionary that is included upfront, thus making `StaticResource` also usable via attribute syntax. Using `DynamicResource` resolves the resource at runtime, which means the context menu might just not be there if it is not found and the application keeps running, while `StaticResource` will lead to an exception. – thatguy Jun 18 '21 at 10:58