wpf data-binding treeview treeviewitem hierarchicaldatatemplate

Enlazar SelectedItem en una WPF TreeView aplicada a HierarchicalDataTemplate



data-binding treeviewitem (3)

Sé que esta es una vieja pregunta, pero tal vez será útil para otros. Combiné un código de Link

Y parece ahora:

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

Tengo un TreeView enlazado a datos y quiero enlazar SelectedItem . Este comportamiento adjunto funciona perfectamente sin HierarchicalDataTemplate pero con él, el comportamiento adjunto solo funciona de una manera (UI a datos) y no del otro porque ahora e.NewValue es MyViewModel no TreeViewItem .

Este es un fragmento de código del comportamiento adjunto:

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

Esta es mi definición 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>

Si puedo obtener una referencia al TreeView en el método de comportamiento adjunto OnSelectedItemChanged , tal vez pueda usar las respuestas en esta pregunta para obtener TreeViewItem pero no sé cómo llegar allí. ¿Alguien sabe cómo y es la forma correcta de ir?


Aquí hay una versión mejorada del comportamiento adjunto mencionado anteriormente. Admite completamente el enlace twoway y también funciona con HeriarchicalDataTemplate y TreeView s donde sus elementos están virtualizados. Sin embargo, tenga en cuenta que para encontrar el ''TreeViewItem'' que debe seleccionarse, se realizará (es decir, creará) TreeViewItem s virtualizado hasta que encuentre el correcto. Esto podría ser un problema de rendimiento con grandes árboles virtualizados.

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

Y en aras de la integridad, aquí está la implementación de 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; } } } }


Si encuentra, como yo lo hice, que esta respuesta a veces falla porque itemPresenter es nulo, entonces esta modificación a esa solución podría funcionar para usted.

Cambie OnSelectedItemChanged a esto (si el Árbol aún no está cargado, espera hasta que se cargue el Árbol y lo intenta de nuevo):

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