12

Mam powiązany z danymi TreeView i chcę powiązać SelectedItem. This attached behavior działa doskonale bez numeru HierarchicalDataTemplate, ale z tym związane zachowanie działa tylko w jedną stronę (interfejs do danych), a nie inne, ponieważ teraz e.NewValue jest MyViewModel nie TreeViewItem.Powiązanie elementu SelectedItem w pliku TreeView drzewa HF opartego na hierarchiiDataTemplate

Jest to fragment kodu z załączonym zachowań:

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

To mój TreeView definicja:

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

Jeśli uda mi się odniesienie do TreeView w załączonym sposobu zachowania OnSelectedItemChanged, może mogę użyć odpowiedzi w this question, aby uzyskać TreeViewItem, ale nie wiem jak się tam dostać. Czy ktoś wie, jak i czy jest to właściwa droga?

Odpowiedz

18

Oto ulepszona wersja wyżej wspomnianego załączonego zachowania. W pełni obsługuje podwójne wiązanie, a także współpracuje z HeriarchicalDataTemplate i TreeView s, gdzie jego elementy są zwirtualizowane. Należy jednak pamiętać, że aby znaleźć "TreeViewItem", który musi zostać wybrany, będzie on realizował (to znaczy tworzy) zwirtualizowane TreeViewItem s, dopóki nie znajdzie właściwego. Może to potencjalnie stanowić problem z wydajnością dużych zwirtualizowanych drzewek.

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

I przez wzgląd na kompletność hier jest realizacja 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; 
      } 
     } 
    } 
} 
+2

jak mogę użyć metody GetVisualDescendant? Dodałem odwołanie do PresentationFramework, ale nadal nie mogłem go użyć? Czego mi brakuje? – Lukas

+4

Metoda GetVisualDescendant jest metodą rozszerzenia używaną w utilities przeciągania i upuszczania [implementacja] (https://gong-wpf-dragdrop.googlecode.com/svn-history/r29/branches/jon/GongSolutions.Wpf.DragDrop /Utilities/VisualTreeExtensions.cs), to i tak znalazłem. – Xtr

+0

Działa jak urok. Bardzo dobre rozwiązanie rozszerzające słabe możliwości mVvm kontrolki TreeView. –

3

Wiem, że to stare pytanie, ale być może będzie to pomocne dla innych. I połączeniu kod z Link

I wygląda teraz:

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

Och, ktoś uważał mój kod za pomocny :-) –

+0

Czasami dostajemy do 'var itemsHostPanel = (Panel) VisualTreeHelper.GetChild (itemsPresenter, 0);' i 'itemPresenter' ma wartość null. jakieś pomysły? – MoonKnight

+0

@Killercam mnie pobiłeś :) Domyślam się, że znalazłeś ten sam błąd w Gemini, ponieważ teraz badam ... –

2

Jeśli okaże się, tak jak ja, że ​​this answer czasami wywala bo itemPresenter jest zerowy, to modyfikacja do tego roztworu może pracować dla Ciebie.

Zmień OnSelectedItemChanged do tego (jeśli drzewo nie jest jeszcze załadowany, to czeka, aż Drzewo jest załadowane i próbuje ponownie):

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