当前位置:  开发笔记 > 编程语言 > 正文

使用WPF在虚拟化TreeView中选择节点

如何解决《使用WPF在虚拟化TreeView中选择节点》经验,为你挑选了1个好方法。

有没有办法在虚拟化TreeView中手动选择节点然后将其带入视图?

我在TreeView中使用的数据模型是基于VM-MV模型实现的.每个TreeViewItem的IsSelected属性绑定到ViewModel中的对应属性.我还为TreeView的ItemSelected事件创建了一个监听器,我为所选的TreeViewItem调用了BringIntoView().

这种方法的问题似乎是在创建实际的TreeViewItem之前不会引发ItemSelected事件.因此,启用虚拟化后,节点选择将无法执行任何操作,直到TreeView足够滚动,然后在最终引发事件时"神奇地"跳转到所选节点.

我真的很喜欢使用虚拟化,因为我的树中有数千个节点,并且在启用虚拟化时我已经看到了相当令人印象深刻的性能改进.



1> splintor..:

Estifanos Kidane给出的链接破了.他可能意味着"在虚拟化TreeView中更改选择"MSDN示例.但是,此示例显示了如何在树中选择节点,但使用代码隐藏而不是MVVM和绑定,因此在更改绑定的SelectedItem时,它也不会处理缺少的SelectedItemChanged事件.

我能想到的唯一的解决办法就是打破MVVM模式,当确保新值绑定到SelectedItem属性的变化,得到了查看和调用代码隐藏方法(类似于MSDN样品)视图模型属性实际上是在树中选择的.

这是我编写的代码来处理它.假设您的数据项属于Node具有Parent属性的类型:

public class Node
{
    public Node Parent { get; set; }
}

我写了以下行为类:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

public class NodeTreeSelectionBehavior : Behavior
{
    public Node SelectedItem
    {
        get { return (Node)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(Node), typeof(NodeTreeSelectionBehavior),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));

    private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var newNode = e.NewValue as Node;
        if (newNode == null) return;
        var behavior = (NodeTreeSelectionBehavior)d;
        var tree = behavior.AssociatedObject;

        var nodeDynasty = new List { newNode };
        var parent = newNode.Parent;
        while (parent != null)
        {
            nodeDynasty.Insert(0, parent);
            parent = parent.Parent;
        }

        var currentParent = tree as ItemsControl;
        foreach (var node in nodeDynasty)
        {
            // first try the easy way
            var newParent = currentParent.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
            if (newParent == null)
            {
                // if this failed, it's probably because of virtualization, and we will have to do it the hard way.
                // this code is influenced by TreeViewItem.ExpandRecursive decompiled code, and the MSDN sample at http://code.msdn.microsoft.com/Changing-selection-in-a-6a6242c8/sourcecode?fileId=18862&pathId=753647475
                // see also the question at http://stackoverflow.com/q/183636/46635
                currentParent.ApplyTemplate();
                var itemsPresenter = (ItemsPresenter)currentParent.Template.FindName("ItemsHost", currentParent);
                if (itemsPresenter != null)
                {
                    itemsPresenter.ApplyTemplate();
                }
                else
                {
                    currentParent.UpdateLayout();
                }

                var virtualizingPanel = GetItemsHost(currentParent) as VirtualizingPanel;
                CallEnsureGenerator(virtualizingPanel);
                var index = currentParent.Items.IndexOf(node);
                if (index < 0)
                {
                    throw new InvalidOperationException("Node '" + node + "' cannot be fount in container");
                }
                CallBringIndexIntoView(virtualizingPanel, index);
                newParent = currentParent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
            }

            if (newParent == null)
            {
                throw new InvalidOperationException("Tree view item cannot be found or created for node '" + node + "'");
            }

            if (node == newNode)
            {
                newParent.IsSelected = true;
                newParent.BringIntoView();
                break;
            }

            newParent.IsExpanded = true;
            currentParent = newParent;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e)
    {
        SelectedItem = e.NewValue as Node;
    }

    #region Functions to get internal members using reflection

    // Some functionality we need is hidden in internal members, so we use reflection to get them

    #region ItemsControl.ItemsHost

    static readonly PropertyInfo ItemsHostPropertyInfo = typeof(ItemsControl).GetProperty("ItemsHost", BindingFlags.Instance | BindingFlags.NonPublic);

    private static Panel GetItemsHost(ItemsControl itemsControl)
    {
        Debug.Assert(itemsControl != null);
        return ItemsHostPropertyInfo.GetValue(itemsControl, null) as Panel;
    }

    #endregion ItemsControl.ItemsHost

    #region Panel.EnsureGenerator

    private static readonly MethodInfo EnsureGeneratorMethodInfo = typeof(Panel).GetMethod("EnsureGenerator", BindingFlags.Instance | BindingFlags.NonPublic);

    private static void CallEnsureGenerator(Panel panel)
    {
        Debug.Assert(panel != null);
        EnsureGeneratorMethodInfo.Invoke(panel, null);
    }

    #endregion Panel.EnsureGenerator

    #region VirtualizingPanel.BringIndexIntoView

    private static readonly MethodInfo BringIndexIntoViewMethodInfo = typeof(VirtualizingPanel).GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic);

    private static void CallBringIndexIntoView(VirtualizingPanel virtualizingPanel, int index)
    {
        Debug.Assert(virtualizingPanel != null);
        BringIndexIntoViewMethodInfo.Invoke(virtualizingPanel, new object[] { index });
    }

    #endregion VirtualizingPanel.BringIndexIntoView

    #endregion Functions to get internal members using reflection
}


使用此类,您可以编写如下的XAML:


    
        
            
                
            
        
    


@CameronPeters .Net 4.5添加了BringIndexIntoViewPublic(http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.bringindexintoviewpublic.aspx),因此,如果您使用.Net 4.5或更高版本,则可以直接调用它而不是通过反射获取它.
推荐阅读
跟我搞对象吧
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有