12

Ich habe eine Daten gebunden TreeView und ich möchte SelectedItem binden. This attached behavior funktioniert perfekt ohne HierarchicalDataTemplate, aber damit funktioniert das angehängte Verhalten nur in eine Richtung (UI zu Daten) nicht die andere, denn jetzt e.NewValue ist MyViewModel nicht TreeViewItem.Binding SelectedItem in einem HierarchicalDataTemplate-WPF TreeView

Dies ist ein Code-Schnipsel aus dem beigefügten Verhalten:

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

Das ist meine TreeView Definition lautet:

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

Wenn ich einen Verweis auf die TreeView in der beigefügten Verhalten Methode erhalten kann OnSelectedItemChanged, vielleicht kann ich die Antworten in this question verwenden, um die TreeViewItem zu bekommen, aber ich weiß nicht, wie man dorthin kommt. Weiß jemand wie und ist es der richtige Weg?

Antwort

18

Hier ist eine verbesserte Version des oben genannten Verhaltens beigefügt. Es unterstützt vollständig Twoway-Bindung und funktioniert auch mit HeriarchicalDataTemplate und TreeView s, wo seine Elemente virtualisiert sind. Bitte beachten Sie jedoch, dass die virtualisierten TreeViewItem s erkannt (d. H. Erstellt) werden, bis das richtige "TreeViewItem" gefunden wird, bis es das richtige gefunden hat. Dies könnte möglicherweise ein Leistungsproblem bei großen virtualisierten Bäumen sein.

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

Und der Vollständigkeit halber hier ist die Umsetzung der 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

wie kann ich GetVisualDescendant Methode verwenden? Ich habe einen Verweis auf PresentationFramework hinzugefügt, kann aber immer noch nicht verwenden? Was ich vermisse? – Lukas

+4

Die GetVisualDescendant-Methode ist eine Erweiterungsmethode, die in den Utils einer Drag-and-Drop-Implementierung (https://gong-wpf-dragdrop.googlecode.com/svn-history/r29/branches/jon/GongSolutions.Wpf.DragDrop verwendet wird /Utilities/VisualTreeExtensions.cs), dort habe ich es trotzdem gefunden. – Xtr

+0

Funktioniert wie ein Charme. Sehr gute Lösung, um schlechte MVVM-Funktionen des TreeView Control zu erweitern. –

3

Ich weiß, diese alte Frage, aber vielleicht wird es für andere hilfreich sein. I kombiniert, um einen Code aus Link

Und es sieht jetzt:

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

Oh, jemand hielt meinen Code für hilfreich :-) –

+0

Manchmal kommen wir zu 'var itemsHostPanel = (Panel) VisualTreeHelper.GetChild (itemsPresenter, 0);' und der 'itemPresenter' ist null. Irgendwelche Gedanken? – MoonKnight

+0

@Killercam Sie schlagen mich zu :) Ich vermute, Sie haben den gleichen Fehler in Gemini gefunden, wie ich jetzt untersuche ... –

2

Wenn Sie feststellen, wie ich, dass this answer stürzt manchmal ab, weil itemPresenter null ist, dann ist diese Modifikation zu dieser Lösung könnte für Sie arbeiten.

ändern OnSelectedItemChanged auf diese (wenn der Baum noch nicht geladen ist, dann wartet sie, bis der Baum geladen wird und versucht es erneut):

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