10

I have a list box for which I am styling ItemContainer to include a context menu. Here is the xaml for the same.

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
    ...
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu>
                    <MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"/>
                </ContextMenu>
            </Setter.Value>
        </Setter>
    </Style>

I have coded the target method in ViewModel as given below.

public void DeleteGroup() { //ToDo
    ...
}

The ViewModel is set as the DataContext of the UserControl in which there is the ListBox.

The above code results in "no target found for method". I am not sure why this doesn't work. I have also tried the following variation

<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
          cal:Action.Target="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">

where UCRelayDispositionView is the name of the UserControl.

Why does the above code do not work?

Edit: 1 Also tried the following

<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
          cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">

and this

<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
          cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView}">

EDIT: 2 I have tried to use the Tag in the following way on ItemContainer but it doesn't work either.

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="Tag" Value="{Binding Path=DataContext, ElementName=UCRelayDispositionView}"/>
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu>
                    <MenuItem Header="Remove Group" 
                              cal:Message.Attach="DeleteGroup()" 
                              cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}"/>                                    
                    </ContextMenu>
            </Setter.Value>
    </Style>
</ListBox.ItemContainerStyle>

EDIT 3: Binding Errors

System.Windows.Data Error: 40 : BindingExpression path error: 'PlacementTarget' property not found on 'object' ''MenuItem' (Name='')'. BindingExpression:Path=PlacementTarget.Tag; DataItem='MenuItem' (Name=''); target element is 'MenuItem' (Name=''); target property is 'TargetWithoutContext' (type 'Object')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=UCRelayDispositionView'. BindingExpression:Path=DataContext; DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'Tag' (type 'Object')
Jatin
  • 4,023
  • 10
  • 60
  • 107

2 Answers2

13

Your problem lies in that you are trying to bind the target to an element which doesn't exist in the same visual tree e.g. you have a ContextMenu on which the item resides.

To correctly get an action target, you need to use the ContextMenus PlacementTarget property.

Check out the following answer on SO for the XAML

WPF Context Menus in Caliburn Micro

So the following XAML should work:

<MenuItem Header="Blah" cal:Message.Attach="SomeMethod()" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">

This should look for the PlacementTarget on the ContextMenu and set the target for the action to the value of PlacementTarget.Tag (which should be the ListBoxItem).

If you set ListBoxItem.Tag (as you have already done) to be the DataContext of the parent container (the ListBox) you should be ok

so the tag binding should be:

<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>

e.g. the whole thing should be:

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
                    <MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup()" />
                </ContextMenu>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>
Community
  • 1
  • 1
Charleh
  • 13,749
  • 3
  • 37
  • 57
  • Your code doesn't work. Still gives the same error. As far as using Tag is concerned, where do I place it? I have this context menu on ItemContainerStyle! – Jatin Nov 28 '12 at 14:11
  • If you have a look in the SO link I posted, there's an example of where to put it. Basically you have to use the `ContextMenu.PlacementTarget` property to get the item that spawned the context menu and bind to it's `DataContext`, though it might require that you hack the `DataContext` of the parent container into the `Tag` property. This works for me (it might be worth creating an event in the view just so you can get into the code-behind and debug when you click on the menu item. This way you can explore the object graph and discover the correct binding path) – Charleh Nov 28 '12 at 14:34
  • I have edited the question marked as **EDIT 2**. I am not sure of whether we can use Style to set the Tag like this, but the same error still remains. – Jatin Nov 28 '12 at 14:37
  • What binding errors do you get in your output window out of curiosity? – Charleh Nov 28 '12 at 14:37
  • Looking at EDIT 2 you don't want to set `Tag` on the current item container, the point is that the current item container exists in a different visual tree - you want to set `Tag` on the listbox control and you can use `ElementName` binding to set it. This won't work if your listbox is also the child of a contextmenu or something that can't resolve the element name binding. Can you post your binding error output? – Charleh Nov 28 '12 at 14:39
  • Posted the Binding Error in **Edit 3** in question. – Jatin Nov 28 '12 at 14:45
  • Ah yeah, my bad you want to do a relativesource lookup to the contextmenu not 'self'.. I'll edit – Charleh Nov 28 '12 at 14:50
  • Wonderful. Got it working. I had it correct earlier except the reference to Self, instead of ContextMenu as you pointed out in your earlier comment. Thanks. – Jatin Nov 28 '12 at 15:02
  • Thanks, this solution worked for me as well. I just wanted to add that you can put the `cal:Action.TargetWithoutContext` attribute on the `` tag to use that target for all menu items. – Paccc Mar 04 '14 at 17:20
  • 3
    This answer is confusing. If there is incorrect code please delete it and have only the correct code in the answer. People can look at the revision history if they want to see what edits you made. Keeping the incorrect code around with an "EDIT" section underneath doesn't help anyone. – MgSam Sep 30 '15 at 18:29
  • 1
    @MgSam thanks for the suggestion (about a year and a bit ago!) - I've updated the answer :) – Charleh Mar 08 '17 at 14:53
3

Adding to Charleh's answer, if you're going to be using the same data context as the control, then you can just bind the DataContext instead of a Tag. Makes it a bit cleaner too.

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" >
                    <MenuItem Header="Remove Group"
                              cal:Message.Attach="DeleteGroup()" />
                </ContextMenu>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>
Kao
  • 106
  • 5