【问题标题】:WPF Binding from within a ItemsControl - FindAncestor not working从 ItemsControl 中的 WPF 绑定 - FindAncestor 不起作用
【发布时间】:2011-07-11 07:24:49
【问题描述】:

下午好,

我有一个似乎很常见的问题。我有一个用户控件,它有一个视图模型作为它的数据上下文。然后,此用户控件中的元素将此 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 对象。这个工单对象有一个附加的 cmets 集合,它们使用 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>  

您会注意到此 ItemsControl 呈现的每个 TextBox 都附加了一个 ContextMenu。我想要做的是将此 ContextMenuItem 的命令绑定到我的 ViewModel 中的 DelegateCommand。当然,简单地使用;

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

我们没有得到任何有用的东西,因为在这种情况下“绑定”等于 Ticket.Comment,因此不知道 HideSelectedText 实际上是什么。

似乎有 许多 问题与此类似,并且所有答案似乎都转向了 RelativeSource 解决方案。正如您在我的原始 XAML 代码中看到的那样,我已经尝试过这个以及它的 许多 其他版本(设置和不设置 AncestorLevel,AncestorType={x:Type ItemsControl},AncestorType={x :Type Window}, AncestorType={x:Type DataTemplate} ect) 和 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} 没有什么区别?

【问题讨论】:

    标签: wpf data-binding findancestor


    【解决方案1】:

    ContextMenus 实际上并不是 WPF 的 VisualTree 的一部分,因此绑定不会按预期工作。或者,试试这个绑定:

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

    【讨论】:

    • 嗨,Rachel,感谢您的想法,但遗憾的是,我仍然收到:System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“TicketComment”上找不到“HideSelectedText”属性(哈希码=49404432)'。 BindingExpression:Path=PlacementTarget.DataContext.HideSelectedText; DataItem='ContextMenu' (Name='');目标元素是'MenuItem'(名称='');目标属性是“Command”(输入“ICommand”)
    【解决方案2】:

    最好的解决方案是通过资源来完成。 如果将在资源中创建 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 上测试,但我认为它也适用于早期版本。

    【讨论】:

      【解决方案3】:

      由于上下文菜单不是可视化树的一部分,默认情况下它不继承 DataContext。您必须将上下文菜单的 DataContext 设置为 PlacementTarget,如下所示 -

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

      希望这会有所帮助。

      -VJ

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-02
        • 2011-03-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多