views:

771

answers:

2

I have the TabControl on which I am setting the ContextMenu.

All ContextMenu elements have commands set.

<ContextMenu x:Key="tabMenu">
  <MenuItem Command="{x:Static tabs:TabCommands.Close}" />
  <MenuItem Command="{x:Static tabs:TabCommands.CloseAllButThis}" />
  <MenuItem Command="{x:Static tabs:TabCommands.CloseAll}" />
</ContextMenu>

All commands are routed, and CommandBindings are defined several levels above the TabControl.

So the question is: in the CommandBinding CanExecute/Execute event handlers, what is the correct way to find out on which TabItem the menu was invoked? By correct I mean the one that would not broke if I change something like the TabItem template.

Or may be the whole approach is wrong and I should not use routed commands for this? I have originally used routing for Add New Tab command which requires hotkeys.

Thanks in advance.

UPDATE:

Igor's solution is cleaner from architectural POV (except that I would remove _ in ViewModel), but I want to have a reusable Close command that is independent from what TabControl is bound to (since Close/Close All for tabs exist in all kinds of applications and aren't semantically linked to a specific model).

Also, I can not use custom DataTemplate since I already have a custom template, and subclassing it would make solution a bit overcomplicated.

A: 

It seems I have found the answer myself, however it is highly unelegant:

<Style TargetType="MenuItem">
  <Setter Property="CommandTarget">
    <Setter.Value>
      <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}"
               Path="(ContextMenu.PlacementTarget)" />
    </Setter.Value>
  </Setter>
</Style>

<Style TargetType="TabItem">
  <Setter Property="ContextMenu" Value="{StaticResource tabMenu}" />
</Style>

So I add the ContextMenu to TabItem instead of TabControl, and bind CommandTarget to the TabItem.

It is interesting whether there is a better answer.

Andrey Shchekin
+1  A: 

Sure, there is a better answer. You need to work with Model/ViewModel, not view. Here a simplified exmaple from my code:

        <TabControl Margin="3" Grid.Column="1" Name="tbPages"
                    ItemsSource="{Binding DsmProject.Pages}" 
                    ItemTemplate="{DynamicResource TabItemTemplate}"
                    IsSynchronizedWithCurrentItem="True">
        </TabControl>
<DataTemplate x:Key="TabItemTemplate">
    <StackPanel Orientation="Horizontal" ContextMenu="{DynamicResource cmPages}">
        <ContentPresenter Content="{Binding Path=Name}"/>
    </StackPanel>
</DataTemplate>
<ContextMenu x:Key="cmPages">
    <MenuItem Header="Close" Command="cmd:DSM2100Commands.ClosePage" CommandParameter="{Binding}" />
</ContextMenu>

Here is the code, which handles this command.

Region "Close Page"

    Private Sub ClosePageCmd(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
        ViewModel_.History.TakeCommmand(New cmdRemovePage(ViewModel_, e.Parameter))
    End Sub

    Private Sub CanClosePageCmd(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
        e.CanExecute = ViewModel_.DsmProject IsNot Nothing AndAlso ViewModel_.DsmProject.Pages.Count > 1
    End Sub

End Region

As you can see, my code don't have need to know which TabItem was clicked, just need to know which data object was bound to this TabItem. In anyway, if you need to know TabItem, which was clicked you can find it by Data object bounded to it using ContainerGenerator object and your datatemplate.

With best wishes from Russia!

Thanks, see my reply at the end of the original question. Still, +1.
Andrey Shchekin