"No target found for method" thrown by Caliburn Message.Attach()
Asked Answered
H

2

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')
Hulse answered 27/11, 2012 at 15:21 Comment(0)
M
14

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>
Mckay answered 28/11, 2012 at 8:58 Comment(11)
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!Hulse
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)Mckay
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.Hulse
What binding errors do you get in your output window out of curiosity?Mckay
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?Mckay
Posted the Binding Error in Edit 3 in question.Hulse
Ah yeah, my bad you want to do a relativesource lookup to the contextmenu not 'self'.. I'll editMckay
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.Hulse
Thanks, this solution worked for me as well. I just wanted to add that you can put the cal:Action.TargetWithoutContext attribute on the <ContextMenu> tag to use that target for all menu items.Studied
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.Maggi
@Maggi thanks for the suggestion (about a year and a bit ago!) - I've updated the answer :)Mckay
P
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>
Pipsqueak answered 17/7, 2019 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.