“No target found for method” thrown by Caliburn Me

2020-02-05 05:41发布

问题:

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')

回答1:

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>


回答2:

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>