WPF

2008-10-08 18 views
17

साथ आभासी TreeView में एक नोड का चयन वहाँ TreeView virtualizing में मैन्युअल रूप से एक नोड का चयन करें और फिर इसे दृश्य में लाने के लिए एक तरीका है?WPF

मैं अपने TreeView के साथ उपयोग कर रहा हूँ डेटा मॉडल वी एम एम वी मॉडल के आधार पर कार्यान्वित किया जाता है। प्रत्येक TreeViewItem की Iseelected संपत्ति ViewModel में एक corresponing संपत्ति से जुड़ा हुआ है। मैंने TreeView के आइटम चयन किए गए ईवेंट के लिए श्रोता भी बनाया है जहां मैं चयनित TreeViewItem के लिए BringIntoView() को कॉल करता हूं।

इस दृष्टिकोण के साथ समस्या हो कि ItemSelected घटना जब तक वास्तविक TreeViewItem बनाई गई है उठाया नहीं किया जाएगा लगता है। इसलिए वर्चुअलाइजेशन सक्षम नोड चयन के साथ कुछ भी नहीं किया जाएगा जब तक कि TreeView पर्याप्त स्क्रॉल नहीं हो जाता है और फिर यह अंततः उठाए जाने पर चयनित नोड में "जादुई रूप से" कूदता है।

मैं वास्तव में क्योंकि मैं अपने पेड़ में नोड्स के हजारों है और जब वर्चुअलाइजेशन सक्षम किया गया है मैं पहले से ही काफी प्रभावशाली प्रदर्शन में सुधार देखा है वर्चुअलाइजेशन उपयोग करना चाहते हैं।

+0

हाय, मैं भी एक ही मुद्दे के साथ अटक कर रहा हूँ। आपको इस समस्या का कोई समाधान मिला है? – akjoshi

उत्तर

0

यहाँ एक उदाहरण एक MSDN Question सार्वजनिक शून्य ScrollToItem (पूर्णांक सूचकांक) से लिया

{ 

     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

मार्क ने कहा, यह केवल एक सूची बॉक्स व्युत्पन्न आइटम – arolson101

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 

} 

और एक TreeViewItem के लिए मेरे XAML शैली में, मैं सिर्फ संपत्ति सच

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

को HTH

+4

के लिए काम करता है जब आप वर्चुअलाइज्ड ट्री व्यू का उपयोग करते हैं तो TreeViewItem जिसे आप सबसे अधिक संभावना चुनना चाहते हैं अभी तक नहीं बनाया गया है - इसलिए TreeViewItem के लिए कोई भी शैली लागू करने के लिए बिल्कुल बेकार है। – springy76

12

लिंक Estifanos किडेन दिया टूट गया है निर्धारित किया है। वह शायद the "Changing selection in a virtualized TreeView" MSDN sample था। हालांकि, यह नमूना दिखाता है कि एक पेड़ में नोड का चयन कैसे करें, लेकिन कोड-बैक का उपयोग करके और एमवीवीएम और बाध्यकारी नहीं, इसलिए यह लापता SelectedItemChanged event को बाध्य नहीं करता है जब बाएं चयनित इटैम बदल जाता है।

एकमात्र समाधान जो मैं सोच सकता हूं वह एमवीवीएम पैटर्न को तोड़ना है, और जब व्यूमोडेल प्रॉपर्टी जो चयनित इटैम संपत्ति परिवर्तन से जुड़ी हुई है, तो देखें और कोड-बैक विधि (एमएसडीएन नमूना के समान) को कॉल करें सुनिश्चित करें कि पेड़ में वास्तव में नया मूल्य चुना गया है।

यहाँ कोड मैं इसे संभाल करने के लिए लिखा है। मान लीजिए कि आपके डेटा आइटम प्रकार 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: यह विंडोज 7/डॉटनेट 4.0 में मेरे लिए अच्छा काम कर रहा है। क्या किसी को पता है कि प्रतिबिंब के माध्यम से बुलाए गए तरीके dotNet 4.5 या 4.5.1 में उपलब्ध हैं। (मैं खुद को जांचूंगा लेकिन अपने ज्ञान को साझा करने के लिए किसी अन्य डेवलपर के लिए अवसर प्रदान करना चाहता था :) –

+2

@ कैमरॉन पीटर्स नेट 4.5 जोड़ा गया BringIndexIntoViewPublic (http://msdn.microsoft.com/en-us/library/system.windows.controls .virtualizingpanel.bringindexintoviewpublic.aspx), इसलिए यदि आप .NET 4.5 या बाद में उपयोग कर रहे हैं, तो आप प्रतिबिंब के माध्यम से इसे प्राप्त करने के बजाय इसे सीधे कॉल कर सकते हैं। – splintor

+0

आप लोग भयानक हैं - मैंने इस समाधान को लागू करने के लिए उलझाया - यह विंडोज 10 में काम करता है और यह ब्रिंगइंडेक्सइनो व्यूपब्लिक विधि के साथ काम करता है .Net 4.5 - cool :-) मैंने वास्तविक नोड के बजाय संदर्भ के रूप में एक इंटरफ़ेस का उपयोग किया कक्षा - तो, ​​मुझे लगता है कि पैटर्न पूरी तरह टूटा नहीं है। मुझे आश्चर्य है कि अन्य विधि के लिए कोई प्रतिस्थापन है: EnsureGenerator और Property: 4.5 या बाद में आइटमहोस्ट? कुछ ऐसा करने के लिए आपको छिपा वस्तुओं का उपयोग कैसे करना है? – user3313608

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 पर सेट किया जाता है दृश्य मॉडल अधिसूचित और एक घटना को जन्म देती है। दृश्य में संबंधित ईवेंट श्रोता 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> 
संबंधित मुद्दे