在应用于WPF TreeView的HierarchycalDataTemplate中绑定SelectedItem

在应用于WPF TreeView的HierarchycalDataTemplate中绑定SelectedItem,wpf,data-binding,treeview,treeviewitem,hierarchicaldatatemplate,Wpf,Data Binding,Treeview,Treeviewitem,Hierarchicaldatatemplate,我有一个数据绑定TreeView,我想绑定SelectedItem。如果不使用HierarchycalDataTemplate,则可以完美地工作,但是使用它,附加的行为只能以一种方式工作(UI到数据),而不能以另一种方式工作,因为现在e.NewValue是MyViewModel而不是TreeViewItem 这是附加行为的代码片段: private static void OnSelectedItemChanged(DependencyObject sender, DependencyPrope

我有一个数据绑定
TreeView
,我想绑定
SelectedItem
。如果不使用HierarchycalDataTemplate,则可以完美地工作,但是使用它,附加的行为只能以一种方式工作(UI到数据),而不能以另一种方式工作,因为现在
e.NewValue
MyViewModel
而不是
TreeViewItem

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

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>


如果我可以在SelectedItemChanged上的附加行为方法
中获取对
树视图的引用,也许我可以使用中的答案获取
树视图,但我不知道如何到达那里。有人知道怎么做吗?这是正确的方法吗?

下面是上述附加行为的改进版本。它完全支持双向绑定,还可以与
HeriarchicalDataTemplate
TreeView
s一起使用,其中的项目是虚拟化的。请注意,为了找到需要选择的“TreeView项目”,它将实现(即创建)虚拟化的
TreeView项目,直到找到正确的项目为止。这可能是大型虚拟化树的性能问题

/// <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;
    }
}

我知道这是个老问题,但也许对其他人会有帮助。我结合了来自

现在看来:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace Behaviors
{
    public class BindableSelectedItemBehavior : Behavior<TreeView>
    {
        #region SelectedItem Property

        public object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

        private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            // if binded to vm collection than this way is not working
            //var item = e.NewValue as TreeViewItem;
            //if (item != null)
            //{
            //    item.SetValue(TreeViewItem.IsSelectedProperty, true);
            //}

            var tvi = e.NewValue as TreeViewItem;
            if (tvi == null)
            {
                var tree = ((BindableSelectedItemBehavior)sender).AssociatedObject;
                tvi = GetTreeViewItem(tree, e.NewValue);
            }
            if (tvi != null)
            {
                tvi.IsSelected = true;
                tvi.Focus();
            }
        }

        #endregion

        #region Private

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

        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 = FindVisualChild<ItemsPresenter>(container);
                    if (itemsPresenter == null)
                    {
                        container.UpdateLayout();
                        itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                    }
                }

                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

                for (int i = 0, count = container.Items.Count; i < count; i++)
                {
                    var subContainer = (TreeViewItem)container.ItemContainerGenerator.
                                                          ContainerFromIndex(i);
                    if (subContainer == null)
                    {
                        continue;
                    }

                    subContainer.BringIntoView();

                    // Search the next level for the object.
                    var resultContainer = GetTreeViewItem(subContainer, item);
                    if (resultContainer != null)
                    {
                        return resultContainer;
                    }
                    else
                    {
                        // The object is not under this TreeViewItem
                        // so collapse it.
                        //subContainer.IsExpanded = false;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Search for an element of a certain type in the visual tree.
        /// </summary>
        /// <typeparam name="T">The type of element to find.</typeparam>
        /// <param name="visual">The parent element.</param>
        /// <returns></returns>
        private static T FindVisualChild<T>(Visual visual) where T : Visual
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
            {
                Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
                if (child != null)
                {
                    T correctlyTyped = child as T;
                    if (correctlyTyped != null)
                    {
                        return correctlyTyped;
                    }

                    T descendent = FindVisualChild<T>(child);
                    if (descendent != null)
                    {
                        return descendent;
                    }
                }
            }

            return null;
        }

        #endregion

        #region Protected

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (AssociatedObject != null)
            {
                AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
            }
        }

        #endregion
    }
}
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Interactive;
使用System.Windows.Media;
命名空间行为
{
公共类BindableSelectedItemBehavior:行为
{
#区域SelectedItem属性
公共对象SelectedItem
{
获取{return(object)GetValue(SelectedItemProperty);}
设置{SetValue(SelectedItemProperty,value);}
}
公共静态只读从属属性SelectedProperty=
DependencyProperty.Register(“SelectedItem”,typeof(object),typeof(BindableSelectedItemHavior),new UIPropertyMetadata(null,OnSelectedItemChanged));
SelectedItemChanged上的私有静态无效(DependencyObject发件人、DependencyPropertyChangedEventArgs e)
{
//如果绑定到vm集合,则此方法不起作用
//var item=e.NewValue作为TreeViewItem;
//如果(项!=null)
//{
//item.SetValue(TreeViewItem.IsSelectedProperty,true);
//}
var tvi=作为树项的新值;
如果(tvi==null)
{
变量树=((BindableSelectedItemBehavior)发送方)。关联对象;
tvi=GetTreeViewItem(树,例如NewValue);
}
如果(tvi!=null)
{
tvi.IsSelected=true;
tvi.Focus();
}
}
#端区
#地区私人
私有无效OnTreeViewSelectedItemChanged(对象发送方,RoutedPropertyChangedEventArgs e)
{
选择editem=e.NewValue;
}
私有静态TreeViewItem GetTreeViewItem(ItemsControl容器,对象项)
{
if(容器!=null)
{
if(container.DataContext==项)
{
将容器作为TreeViewItem返回;
}
//展开当前容器
if(容器是TreeViewItem&&!((TreeViewItem)容器).IsExpanded)
{
SetValue(treevieItem.IsExpandedProperty,true);
}
//尝试生成ItemsPresenter和ItemsPanel。
//通过调用ApplyTemplate
//虚拟化案例,即使项目已标记
//我们仍然需要执行此步骤,以便
//重新生成视觉效果,因为它们可能已被虚拟化。
container.ApplyTemplate();
var项目重置中心=
(ItemsPresenter)container.Template.FindName(“ItemsHost”,container);
if(itemsPresenter!=null)
{
itemsPresenter.ApplyTemplate();
}
其他的
{
//树模板尚未命名ItemsPresenter,
//所以,带着后代去找孩子。
itemsPresenter=FindVisualChild(容器);
if(itemsPresenter==null)
{
container.UpdateLayout();
itemsPresenter=FindVisualChild(容器);
}
}
var itemsHostPanel=(面板)VisualTreeHelper.GetChild(itemsPresenter,0);
//确保已创建此面板的生成器。
#pragma警告禁用168
var children=itemsHostPanel.children;
#pragma警告恢复168
for(int i=0,count=container.Items.count;iusing System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace Behaviors
{
    public class BindableSelectedItemBehavior : Behavior<TreeView>
    {
        #region SelectedItem Property

        public object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

        private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            // if binded to vm collection than this way is not working
            //var item = e.NewValue as TreeViewItem;
            //if (item != null)
            //{
            //    item.SetValue(TreeViewItem.IsSelectedProperty, true);
            //}

            var tvi = e.NewValue as TreeViewItem;
            if (tvi == null)
            {
                var tree = ((BindableSelectedItemBehavior)sender).AssociatedObject;
                tvi = GetTreeViewItem(tree, e.NewValue);
            }
            if (tvi != null)
            {
                tvi.IsSelected = true;
                tvi.Focus();
            }
        }

        #endregion

        #region Private

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

        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 = FindVisualChild<ItemsPresenter>(container);
                    if (itemsPresenter == null)
                    {
                        container.UpdateLayout();
                        itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                    }
                }

                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

                for (int i = 0, count = container.Items.Count; i < count; i++)
                {
                    var subContainer = (TreeViewItem)container.ItemContainerGenerator.
                                                          ContainerFromIndex(i);
                    if (subContainer == null)
                    {
                        continue;
                    }

                    subContainer.BringIntoView();

                    // Search the next level for the object.
                    var resultContainer = GetTreeViewItem(subContainer, item);
                    if (resultContainer != null)
                    {
                        return resultContainer;
                    }
                    else
                    {
                        // The object is not under this TreeViewItem
                        // so collapse it.
                        //subContainer.IsExpanded = false;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Search for an element of a certain type in the visual tree.
        /// </summary>
        /// <typeparam name="T">The type of element to find.</typeparam>
        /// <param name="visual">The parent element.</param>
        /// <returns></returns>
        private static T FindVisualChild<T>(Visual visual) where T : Visual
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
            {
                Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
                if (child != null)
                {
                    T correctlyTyped = child as T;
                    if (correctlyTyped != null)
                    {
                        return correctlyTyped;
                    }

                    T descendent = FindVisualChild<T>(child);
                    if (descendent != null)
                    {
                        return descendent;
                    }
                }
            }

            return null;
        }

        #endregion

        #region Protected

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (AssociatedObject != null)
            {
                AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
            }
        }

        #endregion
    }
}
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    Action<TreeViewItem> selectTreeViewItem = tvi2 =>
    {
        if (tvi2 != null)
        {
            tvi2.IsSelected = true;
            tvi2.Focus();
        }
    };

    var tvi = e.NewValue as TreeViewItem;

    if (tvi == null)
    {
        var tree = ((BindableTreeViewSelectedItemBehavior) sender).AssociatedObject;
        if (!tree.IsLoaded)
        {
            RoutedEventHandler handler = null;
            handler = (sender2, e2) =>
            {
                tvi = GetTreeViewItem(tree, e.NewValue);
                selectTreeViewItem(tvi);
                tree.Loaded -= handler;
            };
            tree.Loaded += handler;

            return;
        }
        tvi = GetTreeViewItem(tree, e.NewValue);
    }

    selectTreeViewItem(tvi);
}