WPF Binding from within a ItemsControl – FindAncestor not working

data-bindingfindancestorwpf

Good afternoon,

I have what appear to be a very common issue. I have a user control which has a View Model as its data context. Elements within this user control then use this ViewModel for Binding purposes ect.

The ViewModel

public class TicketDetailsViewModel : ViewModelBase
{
    public DelegateCommand<object> HideSelectedText { get; private set; }

    private Ticket _ticket;
    public Ticket Ticket
    {
        get { return _ticket; }
        set
        {
            _ticket = value;
            this.RaisePropertyChanged(p => p.Ticket);
        }
    }
}

My ViewModel contains a single Ticket object. This ticket object has a collection of comments attached to it and these are rendered to the Ticket Display user control using an ItemsControl

 <ItemsControl ItemsSource="{Binding Path=Ticket.Comments}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Border CornerRadius="15" Background="{Binding Path=CommentType, ConverterParameter=CommentType, Converter={StaticResource ResourceKey=commentColorConverter}}" Padding="10" Margin="40,10,40,0">
                                <TextBox x:Name="tbComment" Text="{Binding CommentText}" IsReadOnly="True">
                                <TextBox.ContextMenu>
                                    <ContextMenu>
                                        <MenuItem Header="Spam" Command="{Binding Path=DataContext.HideSelectedText,RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type UserControl} }}">

                                        </MenuItem>
                                    </ContextMenu>
                                </TextBox.ContextMenu>
                            </TextBox>
                        </Border>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>  
                            <StackPanel Orientation="Vertical"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>  

You will notice each TextBox rendered by this ItemsControl has a ContextMenu attached to it. What I am trying to do is bind the command of this ContextMenuItem to the DelegateCommand in my ViewModel. Of course, simply using;

<MenuItem Header="Spam" Command="{Binding HideSelectedText}">

We dont get anything useful as 'Binding' in this context equals a Ticket.Comment and thus has no idea what the HideSelectedText actually is.

There seem to be many questions similar to this one and all answers seem to veer towards the RelativeSource solution. As you can see in my original XAML code, I have tried this as well as many other versions of it (with and without AncestorLevel set, with AncestorType={x:Type ItemsControl}, AncestorType={x:Type Window}, AncestorType={x:Type DataTemplate} ect) and ALL produce an output error similar to;

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=DataContext.HideSelectedText; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

or

System.Windows.Data Error: 40 : BindingExpression path error: 'HideSelectedText' property not found on 'object' ''TicketComment' (HashCode=49290260)'. BindingExpression:Path=DataContext.HideSelectedText; DataItem='ContextMenu' (Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

So why does this solution seem to work for so many people and yet for me, it makes no difference what so ever from simply typing {Binding HideSelectedText} ?

Best Answer

ContextMenus are not actually part of WPF's VisualTree so Bindings don't work as expeceted. As an alternative, try this binding:

<MenuItem Header="Spam" Command="{Binding PlacementTarget.DataContext.HideSelectedText,
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
Related Topic