【问题标题】:How do you set a CommandBinding on a TreeViewItem in a DataBound TreeView?如何在 DataBound TreeView 中的 TreeViewItem 上设置 CommandBinding?
【发布时间】:2013-06-06 09:01:19
【问题描述】:

为了简化我遇到的问题,请考虑这个假设场景。

在我的控件的 XAML 中,我定义了一个 TreeView 和两个固定根 TreeViewItem 节点,“AItemsNode”和“BItemsNode”。在它的 ViewModel 中,我公开了两个子集合:AItems 和 BItems。然后,我将每个根节点的 ItemsSource 属性绑定到 ViewModel 上的相应集合。这很好用,并且完全显示了我想要的内容。

现在我想做的是为 ApplicationCommands.Open 添加一个 CommandBinding 到两个根节点的 child TreeViewItem 节点。具体来说,我希望根“AItemsNode”下的子节点将其“Open”CommandBinding 指向“OpenAItem_Executed”,而第二个根节点的子节点将其指向“OpenBItem_Executed”。

我遇到的问题是我不知道如何在 TreeViewItem 对象本身上设置 CommandBindings。我根本无法通过 XAML 解决这个问题,如果我要在代码隐藏中执行此操作,我必须与 ItemContainerGenerator 集成,检查正在生成的内容并添加绑定,基本上我编写了这么多代码不妨将“打开”CommandBinding 添加到 TreeView 本身并检查 SelectedObject 并从那里开始。不是最优的,因为我现在有一个 Open_Executed 处理程序,可以根据数据类型在所有地方进行委托,但它确实有效!

不过,我希望有人能告诉我如何将 CommandBinding 直接添加到生成的 TreeViewItem 以避免这种情况并更干净地隔离代码。

那么...如何通过样式(或 XAML 中的任何其他方式)为 TreeViewItem 应用特定的 CommandBindings?

【问题讨论】:

    标签: wpf xaml commandbinding


    【解决方案1】:

    已实现 mvvm,建议使用交互触发器。

    希望这可以帮助您朝着正确的方向前进。

    Xaml:

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    
    <TreeView >
        <TreeView.Resources>
            <DataTemplate x:Key="ItemATemplate">
                <TextBlock Text="{Binding MyNodeTitle}">
                                    <i:Interaction.Triggers>
                                    <i:EventTrigger EventName="PreviewMouseDown">
                                        <i:InvokeCommandAction Command="{Binding AChildCommand}" CommandParameter="{Binding}"/>
                                    </i:EventTrigger>
                                </i:Interaction.Triggers>
                </TextBlock>
            </DataTemplate>
            <DataTemplate x:Key="ItemBTemplate">
                <TextBlock Text="{Binding MyNodeTitle}">
                                    <i:Interaction.Triggers>
                                    <i:EventTrigger EventName="PreviewMouseDown">
                                        <i:InvokeCommandAction Command="{Binding BChildCommand}" CommandParameter="{Binding}"/>
                                    </i:EventTrigger>
                                </i:Interaction.Triggers>
                </TextBlock>
            </DataTemplate>
        </TreeView.Resources>
        <TreeView.Items>
            <TreeViewItem Header="AItemRootNode" ItemsSource="{Binding SomeAStuff}" ItemTemplate="{StaticResource ItemATemplate}"/>
            <TreeViewItem Header="BItemRootNode" ItemsSource="{Binding SomeBStuff}" ItemTemplate="{StaticResource ItemBTemplate}"/>
        </TreeView.Items>
    </TreeView>
    

    视图模型:

    private ICommand _aChildCommand;
    public ICommand AChildCommand
    {
        get
        {
            if (_aChildCommand == null)
                _aChildCommand = new DelegateCommand(OnAChild);
            return _aChildCommand;
        }
    }
    
    private void OnAChild(object obj)
    {
    }
    

    【讨论】:

    • 我认为你的情况倒退了。只有一个命令.. 标准的 Application.Open 命令。我想要每个 TreeViewItem 相同的命令但不同的 CommandBindings(因此,默认情况下,它的 DataTemplate 中的每个子项)。另外,对于由键盘快捷方式触发的命令,您的方式根本不起作用,因为它是具有焦点的 TreeViewItem,因此是命令的源,并且您已将绑定设置在该值之下,因此它永远不会被击中.
    【解决方案2】:

    我认为您还没有实现 MVVM (通过使用 RelayCommands / DelegateCommands 而不是 RoutedCommands 可以直观地为您解决此问题)

    但是,在您的情况下,您需要通过定位到TreeViewItemStyle 处理TreeViewItemInitialized 事件。在Style 中使用EventSetter。并将您的将CommandBinding 添加到e.OriginalSource 值(这是正在初始化的TreeViewItem 本身)的代码传入TreeViewItem.Initialized 事件处理程序。

    让我知道这是否有意义。

    编辑

    我了解您有 x 个键盘快捷键,用于 x 个不同的 TreeViewItems。如果是这样,x 种样式对我来说是有意义的。

    但是,如果您想在树视图级别的单个处理程序中执行此操作,那么您可能必须在该级别处理附加事件......我不认为 Initialised / Loaded 事件泡沫从 indv treeviewitem 一直到父树视图,但检查一下会很有趣,我很高兴证明是错误的!

    另外,这可能不适用于折叠的树视图项。

    因此,假设您为特定树视图项目定义“Ctrl + o”快捷方式,该树视图项目位于折叠的树分支内,或者位于打开的分支中,但它位于滚动视图之外,基于路由事件的机制(因此命令绑定)不会工作,因为它们需要将项目去虚拟化(在 UI 内存中)才能路由工作......

    因此,在这种情况下,我的解决方案(据我所知)变得有点不切实际。

    解决方案

    什么是一直可见的?

    树视图!

    所以利用它来发挥你的优势。将所有命令绑定(和快捷方式)添加到 TreeView 本身!

    在处理程序中(可能在所有命令绑定中都很常见)设计一种机制来使用TreeViewItemViewModelsItemsSource 来识别单个项目,方法是在它们内部搜索,然后在相关的TreeViewItemViewModel 内部具有@ 987654341@ branch hierarchy 通过通用样式将IsOpen 属性(INotifyable)绑定到TreeViewItemIsExpanded 属性。

    【讨论】:

    • 它(中继/委托命令)实际上没有(对我来说)。你能解释一下你会怎么做吗?请记住,可以从其键盘快捷键触发命令,因此 TreeViewItem 是命令“开始”的位置。
    • 另外,根据您的第二个建议,似乎我必须定义“x”个样式(我想要事件的每个 TreeViewNode 类型一个),以及“x”个处理程序我将 CommandBindings 连接到的地方,在这一点上,在 TreeView 上定义绑定并在命令处理程序中对其进行排序不是更简单吗? ...还是我也错过了什么?也许如果你用代码更新你的答案会有所帮助。
    • 我不确定我们在这里说的是同一种语言。没有“x”命令。让我更简化一下。考虑具有三个静态定义的根节点(即在 XAML 中定义)的 TreeView:RootNodeA、RootNodeB 和 RootNodeC,它们的 ItemsSources 指向 AItems、BItems 和 CItems。我希望标准 ApplicationCommands.Delete 命令映射到“DeleteAItem_Executed”仅适用于 RootNodeA 的子节点!!如果直接选择 RootNodeA,则不会发生任何事情,因为 DeleteAItem_CanExecute 将返回 false。我希望 RootNodeB 和 RootNodeC 也一样。希望这更有意义。
    • 如果我将 Delete 命令映射到 TreeView 本身,那么我必须编写一个 if/case 开关来检查所选对象,然后分支到各个 CanExecute/Execute 命令。如果我们添加了一个 RootNodeD,那么我们将不得不再次修改该代码隐藏。但是,如果我们可以通过 XAML 模板将绑定直接映射到 TreeViewItems,那么我们可以简单地交换新资源来映射新事物,而无需重新编译任何东西。这使得 UI 更具可扩展性和数据驱动性。希望现在更有意义。
    • 顺便说一句,我们想出的解决方案是添加一个名为 CommandBindingsToAdd 的新附加属性,并通过样式应用它。然后在附加属性的更改处理程序中,我们遍历传入的值并将它们添加到目标对象的 CommandBindings 属性。它有效,但要求我们将其创建为附加行为。我们认为这应该是您应该能够直接在 XAML 中设置的内容。唉,显然情况并非如此。正如我所说,希望这一切现在都有意义。
    猜你喜欢
    • 1970-01-01
    • 2010-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 2012-02-07
    • 1970-01-01
    相关资源
    最近更新 更多