12

TreeView bir veri bağlı var ve ben SelectedItem bağlamak istiyorum. This attached behavior, HierarchicalDataTemplate olmadan mükemmel şekilde çalışır, ancak ilişikteki davranış, yalnızca bir yoldan (veriye UI) çalışır, çünkü artık e.NewValue, TreeViewItem değil TreeViewItem'dir.Bir HierarchicalDataTemplate uygulanmış WPF TreeView SelectedItem Bağlama

budur ekli davranışından bir kod parçacığı:

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

Bu benim TreeView tanımı şöyledir:

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

Ben ekli davranış yönteminde OnSelectedItemChanged yılında TreeView bir başvuru alabilirsiniz, belki de'u almak için cevapları this question'dan kullanabilirim ama nasıl gideceğimi bilmiyorum. Herkesin nasıl ve nasıl gideceğini biliyor mu?

cevap

18

Yukarıda belirtilen iliştirilmiş davranışların geliştirilmiş bir sürümü. Twoway bağlamayı tam olarak destekler ve ayrıca öğelerinin sanallaştırıldığı HeriarchicalDataTemplate ve TreeView s ile çalışır. Lütfen, seçilmesi gereken 'TreeViewItem' öğesini bulmak için, doğru olanı buluncaya kadar sanallaştırılmış TreeViewItem'ların farkına varacağını unutmayın. Bu büyük sanallaştırılmış ağaçlar ile potansiyel bir performans sorunu olabilir.

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

Ve tamlık hier uğruna

GetVisualDescentants uygulanmasıdır:

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

nasıl GetVisualDescendant yöntemini kullanabilirsiniz? PresentationFramework'a referans ekledim, ancak yine de kullanamadım? Benim neyim eksik? – Lukas

+4

GetVisualDescendant yöntemi, bir sürükle ve bırak yönteminde kullanılan bir uzantı yöntemidir [https://gong-wpf-dragdrop.googlecode.com/svn-history/r29/branches/jon/GongSolutions.Wpf.DragDrop /Utilities/VisualTreeExtensions.cs), yine de onu buldum. – Xtr

+0

Bir çekicilik gibi çalışır. TreeView Kontrolünün zayıf mvvm özelliklerini genişletmek için çok iyi bir çözüm. –

3

Bu eski bir soru olduğunu biliyorum, ama belki de başkaları için yararlı olacaktır. Ben Link

bir kod kombine Ve şimdi görünür:

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, birisi benim kodumu yararlı olarak değerlendirdi :-) –

+0

Bazen "var itemsHostPanel = (Panel) VisualTreeHelper.GetChild (itemsPresenter, 0);" ve itemPresenter "öğesi boş. Düşüncesi olan var mı? – MoonKnight

+0

@Killercam beni buna dövüyorsun :) Şu anda araştırdığım şu anda Gemini'de aynı hatayı buldunuz sanırım ... –

2

Eğer bulursanız, benim yaptığım gibi, itemPresenter boş olduğu için this answer bazen çöküyor ki, o zaman bu çözüm bu değişiklik sizin için işe yarayabilecek. Buna

Değişim OnSelectedItemChanged (Ağaç henüz yüklü değilse Ağacı yüklenene kadar, o zaman bekler ve tekrar deniyor):

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