Result I figured out what was going on for myself. I had kind of a weird combination of things, so this may or may not be useful to someone else.
I'll go ahead and document the solution in an answer entry. My solution involves a small amount of code-behind. I'm not generally a fan of code-behind. It's only view-related, so doesn't break MVVM, though. If someone gives me a XAML-only solution that isn't too far out, I'll gladly use that as the accepted answer.
Problem
I have a WPF app (.NET6) that has a grid of images that represent discreet objects. I want each to have an identical context menu. Each one needs to send a unique CommandParameter
so that the view-model can identify which object to operate on.
In order to give myself a way to add the identifier, I subclassed Button like this:
public class PartButton : Button
{
public int PartPosition { get; set; }
}
I'm aware of the issue regarding a context menu not being in the visual tree, and thus not inheriting the DataContext
from the top level control (UserControl
in this case). The trick is usually just to use PlacementTarget
to get the parent, and use its DataContext
.
It was also a goal to have a left-click work as well as a right-click. To do that, I used a RoutedEvent
for Click
and used a Storyboard
to set ContextMenu.IsOpen
to true
on the button. If you're more experienced with XAML, you might already see the problem.
Because there are 30 buttons, the ultimate goal was to add the menu through a style so that the button could be declared using as little code as possible.. Just to get one working, I tried it like this:
<jb:PartButton
Grid.Row="2" Grid.Column="2"
IsEnabled="{ Binding Path=PartStatuses[1] }"
PartPosition="1">
<Image Source="../Resources/Part.png" />
<jb:PartButton.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Test Part"
CommandParameter="{Binding Path=PartPosition}"
Command="{Binding Source={StaticResource Proxy}, Path=Data.TestPartCmd}"
/>
</ContextMenu>
</jb:PartButton.ContextMenu>
</jb:PartButton>
That works fine for a right-click. However, when using the RoutedEvent
trick above, left-click results in PlacementTarget
always being null
.
CodePudding user response:
<ContextMenu>
<MenuItem Header="Test Part" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}" Command="{Binding TestPartCmd}"/>
</ContextMenu>
and ViewModel like this
public RelayCommand<System.Windows.Controls.MenuItem> TestPartCmd
{
get
{
return new RelayCommand<System.Windows.Controls.MenuItem>((menuItem) =>
{
int partPosition = ((PartButton)((System.Windows.Controls.Primitives.Popup)((System.Windows.FrameworkElement)menuItem.Parent).Parent).PlacementTarget).PartPosition;
});
}
}
There may be a better way, but I haven't thought of it yet。
CodePudding user response:
As promised, here is my current solution:
<ContextMenu>
<MenuItem Header="Bootloader Test"
CommandParameter="{Binding Path=PlacementTarget.PartPosition, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding Path=PlacementTarget.DataContext.BootloaderTestCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
/>
To work, it needs this code-behind:
public void OnPartButtonPressed(object sender, RoutedEventArgs e)
{
if (sender is Button btn)
{
btn.ContextMenu.PlacementTarget = btn;
btn.ContextMenu.IsOpen = true;
}
}
As it sits, this is now fully implemented as a style so that all that's really needed for each button is something like this:
<jb:PartButton
PartPosition="1"
Grid.Row="2" Grid.Column="2" />
In the real project, I also declare the image for the button at the object because I was struggling with getting that done via a style. The image depends on an index (PartPosition
) that has to be resolved in the view-model. It's hard to use a style to get a property from the view-model using a value that has to be queried from the object. Different problem, though, and something I may or may not worry about later.