2
votes

I have an application that displays a list of ViewModels as tab control items. The list of items is databound to a property on the parent ViewModel. Now I want to add a context menu to support actions for each of the TabItems (not the whole TabControl itself).

This is the control in question:

<TabControl x:Name="Items"
                Grid.Column="2"
                Grid.Row="0"
                Margin="3,5,5,3"
                Visibility="{Binding Path=TabControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding DisplayName}" />
                    <Button Padding="10,0,0,0"
                            Content="X"
                            Style="{DynamicResource NoChromeButton}"
                            cal:Message.Attach="CloseTab($dataContext)" />
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
    </TabControl>

When I put the context menu code inside the TabControl tags the context menu iis registered for the TabControl as a whole. This is not the behaviour I want.

If I add it to the StackPanel tags inside the DataTemplate Tags, the DataTriggers registered for each Item is getting executed on the child ViewModel, but the view model does not have the methods and properties to execute the event.

Is there a possibility to solve this problem? How can I add a context menu to each item to support actions like: "Close This", "Save This", "Close Everything Except This"


Some more Infos: I use Caliburn.Micro as framework and use it's conventions to bind the TabControl to the Items property on the ViewModel, which is an IObservableCollection<LocationDetailsViewModel> created by inheriting my ViewModel from Conductor<LocationDetailsViewModel>.Collection.OneActive. The LocationsDetailsViewModel also inherits from Screen

Everything works as intended. If I add an item to the Items property the TabControl gets updated properly. I want to add a ContextMenu to each TabControl item, which is accessible by right-clicking the header. The context menu then should contain actions, like "Close This", "Save This", "Close Everything Except This" For that I added a context menu to the StackPanel which controls the design of the header and used CM to call the appropriate method on the view model. But when I call it, I get an exception telling me that no suitable method can be found. I double checked and it seems CM wants to call a method on the LocationDetailsViewModel and not the LocationViewModel, even though a similar method call exist in the close button for each tab item.

Here is the code with the context menu:

<UserControl x:Class="RpgTools.LocationPresenter.Views.LocationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:cal="http://www.caliburnproject.org"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:vm="clr-namespace:RpgTools.LocationPresenter.ViewModels"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance Type=vm:LocationViewModel, IsDesignTimeCreatable=True}"
             cal:Bind.AtDesignTime="True"
             Padding="5">
<!-- Code left out for brevity -->
 <TabControl x:Name="Items"
                Grid.Column="2"
                Grid.Row="0"
                Margin="3,5,5,3"
                Visibility="{Binding Path=TabControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <StackPanel.ContextMenu>
                        <ContextMenu>
                            <MenuItem Header="Close This"
                                      cal:Message.Attach="CloseTab($dataContext)">
                            </MenuItem>
                        </ContextMenu>
                    </StackPanel.ContextMenu>
                    <TextBlock Text="{Binding DisplayName}" />
                    <Button Padding="10,0,0,0"
                            Content="X"
                            Style="{DynamicResource NoChromeButton}"
                            cal:Message.Attach="CloseTab($dataContext)" />
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
    </TabControl>

1
You put it where you want it, then you bind to what you need to bind to. Look at Binding.ElementName in the docs. - user1228
As I already wrote: Putting it inside the StackPanel does not work. The method gets executed on the child view model and an MethodNotFound exception is thrown. - Ruhrpottpatriot
@Ruhrpottpatriot Are you able to post more detail on what your XAML looks like when you attach it to the StackPanel, and what your data classes look like? My best guess is that you do not have the DataContext being set correctly in the ContextMenu (bind using the PlacementTarget property), but I'm not sure if that is the case or not since I can't see your code - Rachel
You bind to the "view model [that has] the methods and properties to execute the event." I didn't say otherwise. I don't know who or what that is (your question isn't clear in that regard), but you bind to it using Binding.ElementName. - user1228
I updated my question with more details. I hope this is enough - Ruhrpottpatriot

1 Answers

0
votes

ContextMenus aren't part of the normal VisualTree the way other controls are, so the .DataContext isn't inherited as expected for binding purposes.

You need to bind the ContextMenu.DataContext to the ContextMenu.PlacementTarget.DataContext, which in this case would be StackPanel.DataContext.

<ContextMenu DataContext="{Binding 
    RelativeSource={RelativeSource Self},
    Path=PlacementTarget.DataContext}">