Связывание WPF из ItemsControl - FindAncestor не работает - PullRequest
0 голосов
/ 08 марта 2011

Добрый день,

У меня есть очень распространенная проблема. У меня есть пользовательский элемент управления, который имеет модель представления в качестве контекста данных. Элементы в этом пользовательском элементе управления затем используют эту ViewModel для привязки и т. Д.

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);
        }
    }
}

Моя ViewModel содержит один объект Ticket. К этому объекту заявки прикреплена коллекция комментариев, которые передаются в пользовательский элемент управления Ticket Display с использованием 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>  

Вы заметите, что к каждому TextBox, отображаемому этим ItemsControl, прикреплено ContextMenu. То, что я пытаюсь сделать, это связать команду этого ContextMenuItem с DelegateCommand в моей ViewModel. Конечно, просто используя;

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

Мы не получаем ничего полезного, так как «Binding» в этом контексте равен Ticket.Comment и, следовательно, не имеет представления о том, что на самом деле представляет собой HideSelectedText.

Похоже, много вопросов, похожих на этот, и все ответы, похоже, отклоняются от решения RelativeSource. Как вы можете видеть в моем исходном коде XAML, я попробовал это, а также многие другие его версии (с набором AncestorLevel и без него, с AncestorType = {x: Type ItemsControl}, AncestorType = {x: Type Window}, AncestorType = {x: Type DataTemplate} и т. Д.) И ALL выдают ошибку вывода, аналогичную;

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

или

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

Так почему же это решение работает для стольких людей, и все же для меня нет никакой разницы, чем просто набирать {Binding HideSelectedText}?

Ответы [ 3 ]

3 голосов
/ 08 марта 2011

ContextMenus на самом деле не является частью VisualTree WPF, поэтому привязки не работают так, как ожидается. В качестве альтернативы попробуйте эту привязку:

<MenuItem Header="Spam" Command="{Binding PlacementTarget.DataContext.HideSelectedText,
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
1 голос
/ 28 апреля 2014

Лучшее решение - сделать это через ресурс. Если ContextMenu будет создано в ресурсе, он автоматически унаследует DataContext от родительского. Тогда нужно просто создать стиль со значением, передаваемым статическим ресурсом.

Пример:

<DataGrid.Resources>
    <ContextMenu x:Key="test_ContextMenu" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
        <MenuItem Header="Test Header" Command="{Binding Path=TestCommand, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
    </ContextMenu>
</DataGrid.Resources>

<DataGrid.ItemContainerStyle>
    <Style TargetType="DataGridRow">
        <Setter Property="ContextMenu" Value="{StaticResource test_ContextMenu}"/>
    </Style>
</DataGrid.ItemContainerStyle>

Этот код был протестирован на .NET 4.5, но я думаю, что он будет работать и на более ранних версиях.

0 голосов
/ 05 апреля 2013

Поскольку контекстное меню не является частью визуального дерева, оно не наследует DataContext по умолчанию.Вам нужно будет установить DataContext контекстного меню на PlacementTarget, как показано ниже -

<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext}">
   <ContextMenuItem Header="Item1" Command="{Binding YourCommand}" />
</ContextMenu>

Надеюсь, это поможет.

-VJ

...