TreeViewItem 展开时调用命令

Posted

技术标签:

【中文标题】TreeViewItem 展开时调用命令【英文标题】:Invoke Command when TreeViewItem is Expanded 【发布时间】:2014-06-12 13:46:01 【问题描述】:

听起来很简单?我有一个 TreeView,我希望在展开其中一个节点时发生一些事情。我正在使用 MVVM,所以“某事”是 ViewModel 中的一个命令。

嗯,我发现这毕竟不是那么简单。我环顾四周并尝试了一些事情。例如,使用 MVVM Light 的 EventToCommand:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="TreeViewItem.Expanded">
        <cmd:EventToCommand Command="Binding Path=FolderNodeToggledCommand" />
    </i:EventTrigger>
</i:Interaction.Triggers>

此代码(基于this 和this)不起作用(没有任何触发;命令绑定在 ViewModel 中,但在展开节点时从不触发相应的方法)。我也试过用i:InvokeCommandAction 替换cmd:EventToCommand,结果是一样的。第二个链接中的“解决方案”显然是矫枉过正,我不想更改 ToggleButton,因为我想使用有自己的 ToggleButton 的WPF TreeView WinForms Style。第二个链接中的次要答案表明我可能正在尝试使用 TreeView 上不存在的事件。

另一个possible solution 可能是绑定TreeViewItem 的IsExpanded 属性。但是,我想保持我绑定到的对象干净 DTOs 并在 ViewModel 中执行操作,而不是在被绑定的对象中。

那么当 TreeViewItem 展开时,在 ViewModel 中调用命令需要什么?

【问题讨论】:

是的,请务必发布答案。我没有使用过 Prism,但我会看看我是否可以让它工作。 好的,我会发布一个逐步给出的答案,我们会看看它是否有用。 注意:由于该命令与行为绑定,因此您不应内联声明 内联?你是什​​么意思?顺便说一句,感谢您的详细回答 - 我将在接下来的几天内尝试一下,因为目前我对其他东西有点不知所措。 【参考方案1】:

要使其正常工作,您可以使用附加行为,您会发现这是一个干净的 MVVM 策略。

创建一个 WPF 应用并添加此 Xaml...

<Grid>
    <TreeView>
        <TreeView.Resources>
            <Style TargetType="TreeViewItem">
                <Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="Binding ExpandingCommand"/>
            </Style>
        </TreeView.Resources>
        <TreeViewItem Header="this" >
            <TreeViewItem Header="1"/>
            <TreeViewItem Header="2"><TreeViewItem Header="Nested"></TreeViewItem></TreeViewItem>
            <TreeViewItem Header="2"/>
            <TreeViewItem Header="2"/>
            <TreeViewItem Header="2"/>
        </TreeViewItem>
        <TreeViewItem Header="that" >
            <TreeViewItem Header="1"/>
            <TreeViewItem Header="2"/>
            <TreeViewItem Header="2"/>
            <TreeViewItem Header="2"/>
            <TreeViewItem Header="2"/>
        </TreeViewItem>        
    </TreeView>
</Grid>

然后像这样创建一个视图模型...

public class ViewModel : INotifyPropertyChanged

    public ICommand ExpandingCommand  get; set; 
    public ViewModel()
    
        ExpandingCommand = new RelayCommand(ExecuteExpandingCommand, CanExecuteExpandingCommand);
    
    private void ExecuteExpandingCommand(object obj)
    
        Console.WriteLine(@"Expanded");
    
    private bool CanExecuteExpandingCommand(object obj)
    
        return true;
    
    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string name)
    
        var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
        if (handler != null)
        
            handler(this, new PropertyChangedEventArgs(name));
        
    
    #endregion

我使用中继命令,但您可以交替使用代理命令。中继命令的来源是http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

然后创建一个单独的类,看起来像这样......

public static class Behaviours

    #region ExpandingBehaviour (Attached DependencyProperty)
    public static readonly DependencyProperty ExpandingBehaviourProperty =
        DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
            new PropertyMetadata(OnExpandingBehaviourChanged));
    public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
    
        o.SetValue(ExpandingBehaviourProperty, value);
    
    public static ICommand GetExpandingBehaviour(DependencyObject o)
    
        return (ICommand) o.GetValue(ExpandingBehaviourProperty);
    
    private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        TreeViewItem tvi = d as TreeViewItem;
        if (tvi != null)
        
            ICommand ic = e.NewValue as ICommand;
            if (ic != null)
            
                tvi.Expanded += (s, a) => 
                
                    if (ic.CanExecute(a))
                    
                        ic.Execute(a);

                    
                    a.Handled = true;
                ;
            
        
    
    #endregion

然后将这个类的命名空间导入你的 Xaml...

xmlns:bindTreeViewExpand="clr-namespace:BindTreeViewExpand"(你的命名空间会不一样!)

Resharper 会为你做这件事,或者给你一个智能提示。

最后连接视图模型。使用这种快速而肮脏的方法......

public partial class MainWindow : Window

    public MainWindow()
    
        InitializeComponent();
        DataContext = new ViewModel();
    

然后,在名称空间被解析并且接线正确之后,它将开始工作。在 Execute 方法中锚定您的调试器并观察您获得了一个 RoutedEvent 参数。您可以对其进行解析以获取展开的树视图项。

此解决方案的关键方面是在样式中指定的行为!所以它适用于每一个 TreeViewItem。两者都没有代码(除了行为)。

我在上面列出的行为将事件标记为已处理。您可能希望根据您所追求的行为来改变它。

【讨论】:

我试过了,效果很好。诚然,对于本应如此简单的事情来说,这是相当大的努力,但它确实完成了这项工作并且做得很好。 记住不要内联getter @GayotFow 为什么在我的项目中只调用代码`public static readonly DependencyProperty ExpandingBehaviourProperty = DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours), new PropertyMetadata(OnExpandingBehaviourChanged)); ` 但永远不要调用OnExpandingBehaviourChanged @xudong125,应用启动时在InitializeComponent方法中调用 @GayotFow 现在,我发现一些关于 [this][1] HierarchicalDataTemplates 没有“扩展”事件的文章,但我仍然希望 TreeView 中的每个项目都能够触发扩展事件。跨度>

以上是关于TreeViewItem 展开时调用命令的主要内容,如果未能解决你的问题,请参考以下文章

wpf中的treeview如何增加2级节点?在C#中如何添加?

wpf treeview节点前面添加图标

wpf treeview节点前面添加图标

如何在展开可选值时调试“nil 的致命错误”?

javascript在启动时调用托管bean的方法,而不是在命令按钮单击时调用它们

Blend 修改TreeViewItem样式