本文介绍了在 HierarchicalDataTemplate 应用的 WPF TreeView 中绑定 SelectedItem的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个数据绑定的 TreeView,我想绑定 SelectedItem.此附加行为无需HierarchicalDataTemplate即可完美运行,但使用它附加行为只能以一种方式起作用(UI数据)而不是另一个,因为现在 e.NewValueMyViewModel 而不是 TreeViewItem.

I have a data-bound TreeView and I want to bind SelectedItem. This attached behavior works perfectly without HierarchicalDataTemplate but with it the attached behavior only works one way (UI to data) not the other because now e.NewValue is MyViewModel not TreeViewItem.

这是附加行为的代码片段:

This is a code snippet from the attached behavior:

private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var item = e.NewValue as TreeViewItem;
    if (item != null)
    {
        item.SetValue(TreeViewItem.IsSelectedProperty, true);
    }
}

这是我的TreeView定义:

<Window xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
    <TreeView ItemsSource="{Binding MyItems}" VirtualizingStackPanel.IsVirtualizing="True">
        <interactivity:Interaction.Behaviors>
            <behaviors:TreeViewSelectedItemBindingBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
        </interactivity:Interaction.Behaviors>
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type local:MyViewModel}" ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}"/>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
</Window>

如果我可以在附加的行为方法 OnSelectedItemChanged 中获得对 TreeView 的引用,也许我可以使用 这个问题 获得 TreeViewItem 但我不知道如何到达那里.有谁知道如何以及这是正确的方法吗?

If I can get a reference to the TreeView in the attached behavior method OnSelectedItemChanged, maybe I can use the answers in this question to get the TreeViewItem but I don't know how to get there. Does anyone know how and is it the right way to go?

推荐答案

这是上述附加行为的改进版本.它完全支持双向绑定,还可以与 HeriarchicalDataTemplateTreeView 一起使用,其中的项目是虚拟的.请注意,虽然要找到需要选择的TreeViewItem",但它会实现(即创建)虚拟化的 TreeViewItem ,直到找到正确的TreeViewItem.这可能是大型虚拟化树的潜在性能问题.

Here is an improved version of the above mentioned attached behavior. It fully supports twoway binding and also works with HeriarchicalDataTemplate and TreeViews where its items are virtualized. Please note though that to find the 'TreeViewItem' that needs to be selected, it will realize (i.e. create) the virtualized TreeViewItems until it finds the right one. This could potentially be a performance problem with big virtualized trees.

/// <summary>
///     Behavior that makes the <see cref="System.Windows.Controls.TreeView.SelectedItem" /> bindable.
/// </summary>
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
    /// <summary>
    ///     Identifies the <see cref="SelectedItem" /> dependency property.
    /// </summary>
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register(
            "SelectedItem",
            typeof(object),
            typeof(BindableSelectedItemBehavior),
            new UIPropertyMetadata(null, OnSelectedItemChanged));

    /// <summary>
    ///     Gets or sets the selected item of the <see cref="TreeView" /> that this behavior is attached
    ///     to.
    /// </summary>
    public object SelectedItem
    {
        get
        {
            return this.GetValue(SelectedItemProperty);
        }

        set
        {
            this.SetValue(SelectedItemProperty, value);
        }
    }

    /// <summary>
    ///     Called after the behavior is attached to an AssociatedObject.
    /// </summary>
    /// <remarks>
    ///     Override this to hook up functionality to the AssociatedObject.
    /// </remarks>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.SelectedItemChanged += this.OnTreeViewSelectedItemChanged;
    }

    /// <summary>
    ///     Called when the behavior is being detached from its AssociatedObject, but before it has
    ///     actually occurred.
    /// </summary>
    /// <remarks>
    ///     Override this to unhook functionality from the AssociatedObject.
    /// </remarks>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.SelectedItemChanged -= this.OnTreeViewSelectedItemChanged;
        }
    }

    private static Action<int> GetBringIndexIntoView(Panel itemsHostPanel)
    {
        var virtualizingPanel = itemsHostPanel as VirtualizingStackPanel;
        if (virtualizingPanel == null)
        {
            return null;
        }

        var method = virtualizingPanel.GetType().GetMethod(
            "BringIndexIntoView",
            BindingFlags.Instance | BindingFlags.NonPublic,
            Type.DefaultBinder,
            new[] { typeof(int) },
            null);
        if (method == null)
        {
            return null;
        }

        return i => method.Invoke(virtualizingPanel, new object[] { i });
    }

    /// <summary>
    /// Recursively search for an item in this subtree.
    /// </summary>
    /// <param name="container">
    /// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
    /// </param>
    /// <param name="item">
    /// The item to search for.
    /// </param>
    /// <returns>
    /// The TreeViewItem that contains the specified item.
    /// </returns>
    private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
    {
        if (container != null)
        {
            if (container.DataContext == item)
            {
                return container as TreeViewItem;
            }

            // Expand the current container
            if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
            {
                container.SetValue(TreeViewItem.IsExpandedProperty, true);
            }

            // Try to generate the ItemsPresenter and the ItemsPanel.
            // by calling ApplyTemplate.  Note that in the
            // virtualizing case even if the item is marked
            // expanded we still need to do this step in order to
            // regenerate the visuals because they may have been virtualized away.
            container.ApplyTemplate();
            var itemsPresenter =
                (ItemsPresenter)container.Template.FindName("ItemsHost", container);
            if (itemsPresenter != null)
            {
                itemsPresenter.ApplyTemplate();
            }
            else
            {
                // The Tree template has not named the ItemsPresenter,
                // so walk the descendents and find the child.
                itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
                if (itemsPresenter == null)
                {
                    container.UpdateLayout();
                    itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
                }
            }

            var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);

            // Ensure that the generator for this panel has been created.
#pragma warning disable 168
            var children = itemsHostPanel.Children;
#pragma warning restore 168

            var bringIndexIntoView = GetBringIndexIntoView(itemsHostPanel);
            for (int i = 0, count = container.Items.Count; i < count; i++)
            {
                TreeViewItem subContainer;
                if (bringIndexIntoView != null)
                {
                    // Bring the item into view so
                    // that the container will be generated.
                    bringIndexIntoView(i);
                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                                                ContainerFromIndex(i);
                }
                else
                {
                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                                                ContainerFromIndex(i);

                    // Bring the item into view to maintain the
                    // same behavior as with a virtualizing panel.
                    subContainer.BringIntoView();
                }

                if (subContainer == null)
                {
                    continue;
                }

                // Search the next level for the object.
                var resultContainer = GetTreeViewItem(subContainer, item);
                if (resultContainer != null)
                {
                    return resultContainer;
                }

                // The object is not under this TreeViewItem
                // so collapse it.
                subContainer.IsExpanded = false;
            }
        }

        return null;
    }

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var item = e.NewValue as TreeViewItem;
        if (item != null)
        {
            item.SetValue(TreeViewItem.IsSelectedProperty, true);
            return;
        }

        var behavior = (BindableSelectedItemBehavior)sender;
        var treeView = behavior.AssociatedObject;
        if (treeView == null)
        {
            // at designtime the AssociatedObject sometimes seems to be null
            return;
        }

        item = GetTreeViewItem(treeView, e.NewValue);
        if (item != null)
        {
            item.IsSelected = true;
        }
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }
}

为了完整起见,GetVisualDescentants 的实现是:

And for the sake of completeness hier is the implementation of GetVisualDescentants:

/// <summary>
///     Extension methods for the <see cref="DependencyObject" /> type.
/// </summary>
public static class DependencyObjectExtensions
{
    /// <summary>
    ///     Gets the first child of the specified visual that is of tyoe <typeparamref name="T" />
    ///     in the visual tree recursively.
    /// </summary>
    /// <param name="visual">The visual to get the visual children for.</param>
    /// <returns>
    ///     The first child of the specified visual that is of tyoe <typeparamref name="T" /> of the
    ///     specified visual in the visual tree recursively or <c>null</c> if none was found.
    /// </returns>
    public static T GetVisualDescendant<T>(this DependencyObject visual) where T : DependencyObject
    {
        return (T)visual.GetVisualDescendants().FirstOrDefault(d => d is T);
    }

    /// <summary>
    ///     Gets all children of the specified visual in the visual tree recursively.
    /// </summary>
    /// <param name="visual">The visual to get the visual children for.</param>
    /// <returns>All children of the specified visual in the visual tree recursively.</returns>
    public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject visual)
    {
        if (visual == null)
        {
            yield break;
        }

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            var child = VisualTreeHelper.GetChild(visual, i);
            yield return child;
            foreach (var subChild in GetVisualDescendants(child))
            {
                yield return subChild;
            }
        }
    }
}

这篇关于在 HierarchicalDataTemplate 应用的 WPF TreeView 中绑定 SelectedItem的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-24 07:46