2008-10-08 1 views
17

Есть ли способ вручную выбрать узел для виртуализации TreeView и затем привести его в поле зрения?Выбор узла в виртуализированном TreeView с WPF

Модель данных, которую я использую с моим TreeView, реализована на основе модели VM-M-V. Каждое свойство IsSelected TreeViewItem привязывается к соответствующему свойству в ViewModel. Я также создал прослушиватель для события ItemSelected TreeView, где я вызываю BringIntoView() для выбранного TreeViewItem.

Проблема с этим подходом заключается в том, что событие ItemSelected не будет создано до создания фактического TreeViewItem. Таким образом, при выборе узла с поддержкой виртуализации ничего не будет сделано, пока TreeView не будет прокручиваться достаточно, а затем он «волшебным образом» перейдет к выбранному узлу, когда событие наконец будет поднято.

Я бы очень хотел использовать виртуализацию, потому что у меня есть тысячи узлов в моем дереве, и я уже видел впечатляющие улучшения производительности, когда виртуализация включена.

+0

Привет, я тоже застрял с той же проблемой. У вас есть решение этой проблемы? – akjoshi 2010-07-02 07:40:27

ответ

0

Вот пример, взятый из ScrollToItem (INT индекса) MSDN Question общественных ничтожной

{ 

     Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, 

      (System.Windows.Threading.DispatcherOperationCallback)delegate(object arg) 

      { 

       int N = fileList.Items.Count; 

       if (N == 0) 

        return null; 

       if (index < 0) 

       { 

        fileList.ScrollIntoView(fileList.Items[0]); // scroll to first 

       } 

       else 

       { 

        if (index < N) 

        { 

         fileList.ScrollIntoView(fileList.Items[index]); // scroll to item 

        } 

        else 

        { 

         fileList.ScrollIntoView(fileList.Items[N - 1]); // scroll to last 

        } 

       } 

       return null; 

      }, null); 

    } 
+0

как сказал Марк, это работает только для элемента, полученного в ListBox. – arolson101 2011-10-10 19:47:04

1

Я использовал вложенное свойство, чтобы решить эту проблему.

public class TreeViewItemBehaviour 
{ 
    #region IsBroughtIntoViewWhenSelected 

    public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem) 
    { 
     return (bool)treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty); 
    } 

    public static void SetIsBroughtIntoViewWhenSelected(
     TreeViewItem treeViewItem, bool value) 
    { 
     treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value); 
    } 

    public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty = 
     DependencyProperty.RegisterAttached(
     "IsBroughtIntoViewWhenSelected", 
     typeof(bool), 
     typeof(TreeViewItemBehaviour), 
     new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged)); 

    static void OnIsBroughtIntoViewWhenSelectedChanged(
     DependencyObject depObj, DependencyPropertyChangedEventArgs e) 
    { 
     TreeViewItem item = depObj as TreeViewItem; 
     if (item == null) 
      return; 

     if (e.NewValue is bool == false) 
      return; 

     if ((bool)e.NewValue) 
     { 
      item.Loaded += item_Loaded; 
     } 
     else 
     { 
      item.Loaded -= item_Loaded; 
     } 
    } 

    static void item_Loaded(object sender, RoutedEventArgs e) 
    { 
     TreeViewItem item = e.OriginalSource as TreeViewItem; 
     if (item != null) 
      item.BringIntoView(); 
    } 

    #endregion // IsBroughtIntoViewWhenSelected 

} 

И в моем стиле XAML для TreeViewItem, я просто установить свойство истинного

<Setter Property="Behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="True" /> 

HTH

+4

Когда вы используете виртуализованное TreeView, TreeViewItem, который вы хотите выбрать, скорее всего, еще не создан - поэтому совершенно непригодно применять любой стиль для TreeViewItem. – springy76 2011-10-22 12:01:01

12

Ссылка Estifanos Кидане дал сломана. Вероятно, он имел в виду the "Changing selection in a virtualized TreeView" MSDN sample. однако в этом примере показано, как выбрать узел в дереве, но с использованием кода, а не MVVM и привязки, поэтому он также не обрабатывает отсутствующий SelectedItemChanged event при изменении связанного SelectedItem.

Единственное решение, о котором я могу думать, это разбить шаблон MVVM, и когда свойство ViewModel, связанное с свойством SelectedItem, изменится, получите представление и вызовите метод кода-кода (аналогичный образ MSDN), который делает убедитесь, что новое значение действительно выбрано в дереве.

Вот код, который я написал для его обработки. Предположим, что ваши элементы данных имеют тип Node, который имеет Parent свойство:

public class Node 
{ 
    public Node Parent { get; set; } 
} 

Я написал следующий класс поведения:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Reflection; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Interactivity; 

public class NodeTreeSelectionBehavior : Behavior<TreeView> 
{ 
    public Node SelectedItem 
    { 
     get { return (Node)GetValue(SelectedItemProperty); } 
     set { SetValue(SelectedItemProperty, value); } 
    } 

    public static readonly DependencyProperty SelectedItemProperty = 
     DependencyProperty.Register("SelectedItem", typeof(Node), typeof(NodeTreeSelectionBehavior), 
      new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged)); 

    private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var newNode = e.NewValue as Node; 
     if (newNode == null) return; 
     var behavior = (NodeTreeSelectionBehavior)d; 
     var tree = behavior.AssociatedObject; 

     var nodeDynasty = new List<Node> { newNode }; 
     var parent = newNode.Parent; 
     while (parent != null) 
     { 
      nodeDynasty.Insert(0, parent); 
      parent = parent.Parent; 
     } 

     var currentParent = tree as ItemsControl; 
     foreach (var node in nodeDynasty) 
     { 
      // first try the easy way 
      var newParent = currentParent.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem; 
      if (newParent == null) 
      { 
       // if this failed, it's probably because of virtualization, and we will have to do it the hard way. 
       // this code is influenced by TreeViewItem.ExpandRecursive decompiled code, and the MSDN sample at http://code.msdn.microsoft.com/Changing-selection-in-a-6a6242c8/sourcecode?fileId=18862&pathId=753647475 
       // see also the question at http://stackoverflow.com/q/183636/46635 
       currentParent.ApplyTemplate(); 
       var itemsPresenter = (ItemsPresenter)currentParent.Template.FindName("ItemsHost", currentParent); 
       if (itemsPresenter != null) 
       { 
        itemsPresenter.ApplyTemplate(); 
       } 
       else 
       { 
        currentParent.UpdateLayout(); 
       } 

       var virtualizingPanel = GetItemsHost(currentParent) as VirtualizingPanel; 
       CallEnsureGenerator(virtualizingPanel); 
       var index = currentParent.Items.IndexOf(node); 
       if (index < 0) 
       { 
        throw new InvalidOperationException("Node '" + node + "' cannot be fount in container"); 
       } 
       CallBringIndexIntoView(virtualizingPanel, index); 
       newParent = currentParent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem; 
      } 

      if (newParent == null) 
      { 
       throw new InvalidOperationException("Tree view item cannot be found or created for node '" + node + "'"); 
      } 

      if (node == newNode) 
      { 
       newParent.IsSelected = true; 
       newParent.BringIntoView(); 
       break; 
      } 

      newParent.IsExpanded = true; 
      currentParent = newParent; 
     } 
    } 

    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged; 
    } 

    protected override void OnDetaching() 
    { 
     base.OnDetaching(); 
     AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged; 
    } 

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

    #region Functions to get internal members using reflection 

    // Some functionality we need is hidden in internal members, so we use reflection to get them 

    #region ItemsControl.ItemsHost 

    static readonly PropertyInfo ItemsHostPropertyInfo = typeof(ItemsControl).GetProperty("ItemsHost", BindingFlags.Instance | BindingFlags.NonPublic); 

    private static Panel GetItemsHost(ItemsControl itemsControl) 
    { 
     Debug.Assert(itemsControl != null); 
     return ItemsHostPropertyInfo.GetValue(itemsControl, null) as Panel; 
    } 

    #endregion ItemsControl.ItemsHost 

    #region Panel.EnsureGenerator 

    private static readonly MethodInfo EnsureGeneratorMethodInfo = typeof(Panel).GetMethod("EnsureGenerator", BindingFlags.Instance | BindingFlags.NonPublic); 

    private static void CallEnsureGenerator(Panel panel) 
    { 
     Debug.Assert(panel != null); 
     EnsureGeneratorMethodInfo.Invoke(panel, null); 
    } 

    #endregion Panel.EnsureGenerator 

    #region VirtualizingPanel.BringIndexIntoView 

    private static readonly MethodInfo BringIndexIntoViewMethodInfo = typeof(VirtualizingPanel).GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic); 

    private static void CallBringIndexIntoView(VirtualizingPanel virtualizingPanel, int index) 
    { 
     Debug.Assert(virtualizingPanel != null); 
     BringIndexIntoViewMethodInfo.Invoke(virtualizingPanel, new object[] { index }); 
    } 

    #endregion VirtualizingPanel.BringIndexIntoView 

    #endregion Functions to get internal members using reflection 
} 

С помощью этого класса можно написать XAML, как следующее:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
      xmlns:local="clr-namespace:MyProject"> 
    <Grid> 
     <TreeView ItemsSource="{Binding MyItems}" 
        ScrollViewer.CanContentScroll="True" 
        VirtualizingStackPanel.IsVirtualizing="True" 
        VirtualizingStackPanel.VirtualizationMode="Recycling"> 
      <i:Interaction.Behaviors> 
       <local:NodeTreeSelectionBehavior SelectedItem="{Binding MySelectedItem}" /> 
      </i:Interaction.Behaviors> 
     </TreeView> 
    <Grid> 
<UserControl> 
+0

+1: Это хорошо работает для меня в Windows 7/dotNet 4.0. Кто-нибудь знает, доступны ли методы, вызванные отражением, в dotNet 4.5 или 4.5.1. (Я буду проверять себя, но хотел предоставить возможность другому разработчику поделиться своей мудростью :) – 2013-10-01 16:14:36

1

Я решил эту проблему, создав специальные элементы управления для TreeView, TreeViewItem и VirtualizingStackPanel. Часть решения от http://code.msdn.microsoft.com/Changing-selection-in-a-6a6242c8.

Для каждого элемента TreeItem (связанного элемента) требуется знать его родительский элемент (применяется в соответствии с ITreeItem).

public interface ITreeItem { 
    ITreeItem Parent { get; } 
    IList<ITreeItem> Children { get; } 
    bool IsSelected { get; set; } 
    bool IsExpanded { get; set; } 
} 

Когда IsSelected установлен на любом TreeItem модель вида уведомлена и вызывает событие. Соответствующий прослушиватель событий в представлении вызывает BringItemIntoView на TreeView.

TreeView находит все TreeViewItems на пути к выбранному товару и выводит их на экран.

А вот остальная часть кода:

public class SelectableVirtualizingTreeView : TreeView { 
    public SelectableVirtualizingTreeView() { 
     VirtualizingStackPanel.SetIsVirtualizing(this, true); 
     VirtualizingStackPanel.SetVirtualizationMode(this, VirtualizationMode.Recycling); 
     var panelfactory = new FrameworkElementFactory(typeof(SelectableVirtualizingStackPanel)); 
     panelfactory.SetValue(Panel.IsItemsHostProperty, true); 
     var template = new ItemsPanelTemplate { VisualTree = panelfactory }; 
     ItemsPanel = template; 
    } 

    public void BringItemIntoView(ITreeItem treeItemViewModel) { 
     if (treeItemViewModel == null) { 
      return; 
     } 
     var stack = new Stack<ITreeItem>(); 
     stack.Push(treeItemViewModel); 
     while (treeItemViewModel.Parent != null) { 
      stack.Push(treeItemViewModel.Parent); 
      treeItemViewModel = treeItemViewModel.Parent; 
     } 
     ItemsControl containerControl = this; 
     while (stack.Count > 0) { 
      var viewModel = stack.Pop(); 
      var treeViewItem = containerControl.ItemContainerGenerator.ContainerFromItem(viewModel); 
      var virtualizingPanel = FindVisualChild<SelectableVirtualizingStackPanel>(containerControl); 
      if (virtualizingPanel != null) { 
       var index = viewModel.Parent != null ? viewModel.Parent.Children.IndexOf(viewModel) : Items.IndexOf(treeViewItem); 
       virtualizingPanel.BringIntoView(index); 
       Focus(); 
      } 
      containerControl = (ItemsControl)treeViewItem; 
     } 
    } 

    protected override DependencyObject GetContainerForItemOverride() { 
     return new SelectableVirtualizingTreeViewItem(); 
    } 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { 
     base.PrepareContainerForItemOverride(element, item); 
     ((TreeViewItem)element).IsExpanded = true; 
    } 

    private static T FindVisualChild<T>(Visual visual) where T : Visual { 
     for (var i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++) { 
      var child = (Visual)VisualTreeHelper.GetChild(visual, i); 
      if (child == null) { 
       continue; 
      } 
      var correctlyTyped = child as T; 
      if (correctlyTyped != null) { 
       return correctlyTyped; 
      } 
      var descendent = FindVisualChild<T>(child); 
      if (descendent != null) { 
       return descendent; 
      } 
     } 
     return null; 
    } 
} 

public class SelectableVirtualizingTreeViewItem : TreeViewItem { 
    public SelectableVirtualizingTreeViewItem() { 
     var panelfactory = new FrameworkElementFactory(typeof(SelectableVirtualizingStackPanel)); 
     panelfactory.SetValue(Panel.IsItemsHostProperty, true); 
     var template = new ItemsPanelTemplate { VisualTree = panelfactory }; 
     ItemsPanel = template; 
     SetBinding(IsSelectedProperty, new Binding("IsSelected")); 
     SetBinding(IsExpandedProperty, new Binding("IsExpanded")); 
    } 

    protected override DependencyObject GetContainerForItemOverride() { 
     return new SelectableVirtualizingTreeViewItem(); 
    } 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { 
     base.PrepareContainerForItemOverride(element, item); 
     ((TreeViewItem)element).IsExpanded = true; 
    } 
} 

public class SelectableVirtualizingStackPanel : VirtualizingStackPanel { 
    public void BringIntoView(int index) { 
     if (index < 0) { 
      return; 
     } 
     BringIndexIntoView(index); 
    } 
} 

public abstract class TreeItemBase : ITreeItem { 
    protected TreeItemBase() { 
     Children = new ObservableCollection<ITreeItem>(); 
    } 

    public ITreeItem Parent { get; protected set; } 

    public IList<ITreeItem> Children { get; protected set; } 

    public abstract bool IsSelected { get; set; } 

    public abstract bool IsExpanded { get; set; } 

    public event EventHandler DescendantSelected; 

    protected void RaiseDescendantSelected(TreeItemViewModel newItem) { 
     if (Parent != null) { 
      ((TreeItemViewModel)Parent).RaiseDescendantSelected(newItem); 
     } else { 
      var handler = DescendantSelected; 
      if (handler != null) { 
       handler.Invoke(newItem, EventArgs.Empty); 
      } 
     } 
    } 
} 

public class MainViewModel : INotifyPropertyChanged { 
    private TreeItemViewModel _selectedItem; 

    public MainViewModel() { 
     TreeItemViewModels = new List<TreeItemViewModel> { new TreeItemViewModel { Name = "Item" } }; 
     for (var i = 0; i < 30; i++) { 
      TreeItemViewModels[0].AddChildInitial(); 
     } 
     TreeItemViewModels[0].IsSelected = true; 
     TreeItemViewModels[0].DescendantSelected += OnDescendantSelected; 
    } 

    public event EventHandler DescendantSelected; 

    public event PropertyChangedEventHandler PropertyChanged; 

    public List<TreeItemViewModel> TreeItemViewModels { get; private set; } 

    public TreeItemViewModel SelectedItem { 
     get { 
      return _selectedItem; 
     } 
     set { 
      if (_selectedItem == value) { 
       return; 
      } 
      _selectedItem = value; 
      var handler = PropertyChanged; 
      if (handler != null) { 
       handler.Invoke(this, new PropertyChangedEventArgs("SelectedItem")); 
      } 
     } 
    } 

    private void OnDescendantSelected(object sender, EventArgs eventArgs) { 
     var handler = DescendantSelected; 
     if (handler != null) { 
      handler.Invoke(sender, eventArgs); 
     } 
    } 
} 

public partial class MainWindow { 
    public MainWindow() { 
     InitializeComponent(); 
     var mainViewModel = (MainViewModel)DataContext; 
     mainViewModel.DescendantSelected += OnMainViewModelDescendantSelected; 
    } 

    private void OnAddButtonClick(object sender, RoutedEventArgs e) { 
     var mainViewModel = (MainViewModel)DataContext; 
     var treeItemViewModel = mainViewModel.SelectedItem; 
     if (treeItemViewModel != null) { 
      treeItemViewModel.AddChild(); 
     } 
    } 

    private void OnMainViewModelDescendantSelected(object sender, EventArgs eventArgs) { 
     _treeView.BringItemIntoView(sender as TreeItemViewModel); 
    } 

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { 
     if (e.OldValue == e.NewValue) { 
      return; 
     } 
     var treeView = (TreeView)sender; 
     var treeItemviewModel = treeView.SelectedItem as TreeItemViewModel; 
     var mainViewModel = (MainViewModel)DataContext; 
     mainViewModel.SelectedItem = treeItemviewModel; 
    } 
} 

И в XAML:

<controls:SelectableVirtualizingTreeView x:Name="_treeView" ItemsSource="{Binding TreeItemViewModels}" Margin="8" 
     SelectedItemChanged="OnTreeViewSelectedItemChanged"> 
    <controls:SelectableVirtualizingTreeView.ItemTemplate> 
     <HierarchicalDataTemplate ... /> 
    </controls:SelectableVirtualizingTreeView.ItemTemplate> 
</controls:SelectableVirtualizingTreeView> 
Смежные вопросы