WPF中ItemsControl应用虚拟化时找到子元素的方法

 wpf的虚拟化技术会使UI的控件只初始化看的到的子元素, 而不是所有子元素都被初始化,这样会提高UI性能。

但是我们经常会遇到一个问题:
应用虚拟化后看不见的子元素因为没有实际产生导致ItemContainerGenerator的查找元素方法( ContainerFromIndex /  ContainerFromItem)失效。

解决办法1:
(1)监听ItemsControl的ItemContainerGenerator的StatusChanged事件, 当GeneratorStatus为ContainerGenerated时再进行查找,
(2)遍历ItemsControl的Items,获取当前需要查找元素的Index,
(3)利用反射调用VirtualizingPanel的BringIndexIntoView,或者直接调用BringIntoView,然后强制滚动以产生Item(具体可以参考TreeViewItem的ExpandRecursicve的内部实现)。

需要注意的是:
(1)ItemContainerGenerator的StatuChanged事件会多次被触发, 原因是每次初始化的Item数量就是当前空间所能看到的数量,StatusChanged触发的次数就是总共Items除以每次初始的Item数量。
(2)调用BringIndexInToView不正确会导致InvalidOperationException,具体为“Cannot call StartAt when content generation is in progress.”  或者 ”无法在正在进行内容生成时调用StartAt。”。 可以用Dispatcher.BeginInvoke来解决, 如下面代码。
(3)当然ItemsControl中的虚拟化模式设置为Recycling, 即 VirtualizingStackPanel.VirtualizationMode ="Recycling"时,后端存储的子元素选中项会在ItermContainerGenerator重新产生子项时变为DisconnectedItem。可以把模式设置为Standard解决。


具体代码如下:
  • 1. 查找入口 

         private  ItemsControl  _currentSelectedItem  =  null ;

         private  void  BtnFind_Click (  object  sender  ,  System .  Windows . RoutedEventArgs  e )
        {
             if  ( string  . IsNullOrEmpty (  txtContent . Text  ))
            {
                 return ;
            }

             if  ( _currentSelectedItem  ==  null )
            {
                 _currentSelectedItem  =  _treeView  ;
            }
             else
            {
                 if  ( _currentSelectedItem  is  TreeViewItem )
                {
                    (  _currentSelectedItem  as  TreeViewItem ).  IsExpanded  =  true  ;
                }
            }

             if  ( _currentSelectedItem  . ItemContainerGenerator .  Status  !=  GeneratorStatus  . ContainersGenerated )
            {
                 _currentSelectedItem . ItemContainerGenerator  . StatusChanged  -=  new  EventHandler ( ItemContainerGenerator_StatusChanged  );
                 _currentSelectedItem . ItemContainerGenerator  . StatusChanged  +=  new  EventHandler ( ItemContainerGenerator_StatusChanged  );
            }
             else
            {
                 treeViewItem_BringIntoView ( txtContent  . Text );
            }
        }

  • 2.StatusChanged事件的处理
        void  ItemContainerGenerator_StatusChanged  ( object  sender ,  EventArgs  e )
        {
             var  generator  =  sender  as  ItemContainerGenerator  ;
             if  ( null  ==  generator )
            {
                 return ;
            }

             //once the children have been generated, expand those children's children then remove the event handler
             if  ( generator  . Status  ==  GeneratorStatus . ContainersGenerated  &&  _currentSelectedItem  . ItemContainerGenerator .  Status  ==  GeneratorStatus  . ContainersGenerated )
            {
                 treeViewItem_BringIntoView ( txtContent  . Text );
            }
        }

  • 3.具体虚拟化时的强制产生子元素及查找处理
  private  void  treeViewItem_BringIntoView( string  findItem)
        {
            System.Diagnostics.  Debug .WriteLine( "enter treeViewItem_BringIntoview"  );

             try
            {
                _currentSelectedItem.ApplyTemplate();
                 ItemsPresenter  itemsPresenter = ( ItemsPresenter )_currentSelectedItem.Template.FindName( "ItemsHost" , ( FrameworkElement )_currentSelectedItem);
                 if  (itemsPresenter !=  null  )
                    itemsPresenter.ApplyTemplate();
                 else
                    _currentSelectedItem.UpdateLayout();
                 VirtualizingPanel  virtualizingPanel = _currentSelectedItem.GetItemsHost()  as  VirtualizingPanel ;
                virtualizingPanel.CallEnsureGenerator();

                 int  selectedIndex = -1;
                 int  count1 = _currentSelectedItem.Items.Count;
                 for  ( int  i = 0; i < count1; i++)
                {
                     ItemsItem1  tviItem = _currentSelectedItem.Items.GetItemAt(i)  as  ItemsItem1 ;

                     if  ( null  != tviItem && tviItem.Label.Equals(findItem))
                    {
                        selectedIndex = i;

                         break ;
                    }
                }

                 if  (selectedIndex < 0)
                {
                     return ;
                }

                 Action  action = () =>
                {
                     TreeViewItem  itemSelected =  null  ;

                     //Force to generate every treeView item by using scroll item
                     if  (virtualizingPanel !=  null  )
                    {
                         try
                        {
                            virtualizingPanel.CallBringIndexIntoView(selectedIndex);
                        }
                         catch  (System. Exception  ex)
                        {
                            System.Diagnostics.  Debug .WriteLine( "CallBringIndexIntoView exception : "  + ex.Message);
                        }

                        itemSelected = ( TreeViewItem )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(selectedIndex);
                    }
                     else
                    {
                        itemSelected = ( TreeViewItem )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(selectedIndex);
                        itemSelected.BringIntoView();
                    }

                     if  ( null  != itemSelected)
                    {
                        _currentSelectedItem = itemSelected;
                        (_currentSelectedItem  as  TreeViewItem  ).IsSelected =  true ;
                        _currentSelectedItem.BringIntoView();
                    }
                };

                Dispatcher.BeginInvoke(  DispatcherPriority .Background, action);
            }
             catch  (System. Exception  ex)
            {
                 //
            }
        }

  • 4.xaml代码

< Window
     xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns : x  ="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns : d  ="http://schemas.microsoft.com/expression/blend/2008"
     xmlns : mc  ="http://schemas.openxmlformats.org/markup-compatibility/2006"
     mc : Ignorable  ="d"
     x : Class  ="WpfApplication1.MainWindow"
     x : Name  ="Window"
     Title ="MainWindow"
     Width ="640"
     Height ="480" >
     < Window.Resources >
         < HierarchicalDataTemplate
             x : Key  ="ItemsItem1Template"
             ItemsSource ="{ Binding  Items }" >
             < StackPanel >
                 < TextBlock
                     Text ="{ Binding  Label }" />
             </ StackPanel >
         </ HierarchicalDataTemplate >
     </ Window.Resources >

     < Grid
         x : Name  ="LayoutRoot" >
         < TreeView
             x : Name  ="_treeView"
             HorizontalAlignment ="Left"
             Width ="287"
             d : DataContext  ="{ Binding }"
             ItemsSource ="{ Binding  Items ,  Source  ={ StaticResource  SampleDataSource  }}"
             ItemTemplate ="{ DynamicResource  ItemsItem1Template }"
             VirtualizingStackPanel.IsVirtualizing  ="True"
              VirtualizingStackPanel.VirtualizationMode  ="Standard"
              />
         < Button
             x : Name  ="btnFind"
             Content ="Find"
             HorizontalAlignment ="Right"
             Margin ="0,8,8,0"
             VerticalAlignment ="Top"
             Width ="75"
             Click ="BtnFind_Click" />
         < TextBox
             x : Name  ="txtContent"
             Margin ="291,8,87,0"
             TextWrapping ="Wrap"
             VerticalAlignment ="Top"
             Height ="21.837" />
     </ Grid >
</ Window >

  • 5.反射系统控件私有方法的类

public  static  class  WPFUIElementExtension
    {
        #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 );

         public  static  Panel  GetItemsHost ( this  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  );

         public  static  void  CallEnsureGenerator ( this  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  );

         public  static  void  CallBringIndexIntoView ( this  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
    }

解决方法2:
(1)参考方法1的第一步解决方法
(2)遍历ItemsControl的Items, 根据ContainerFromIndex去找到当前可见的元素的index。
(3)利用BringInoView去滚动现有的Item以便UI产生后续的子元素, 然后循环直到找见要查找的子元素。(遍历分为2部分,向前遍历和向后遍历)

注意事项:
(1)参考方法1的第一注意事项
(2)因为比方法1多了一次循环遍历,当items很多时有点卡顿,不过还在可以忍受的范围。

具体代码:
1.参考方法1的代码,具体只有强制生成子元素的方法有区别, 即与方法1中的步骤3有区别。
2.如下:

   private   void  treeViewItem_BringIntoView2( string  findItem)
        {
            System.Diagnostics.  Debug  .WriteLine( "enter treeViewItem_BringIntoview"  );

             try
            {
                _currentSelectedItem.ApplyTemplate();
                 ItemsPresenter  itemsPresenter = ( ItemsPresenter  )_currentSelectedItem.Template.FindName(  "ItemsHost" , ( FrameworkElement  )_currentSelectedItem);
                 if  (itemsPresenter !=  null  )
                    itemsPresenter.ApplyTemplate();
                 else
                    _currentSelectedItem.UpdateLayout();
                 VirtualizingPanel  virtualizingPanel = _currentSelectedItem.GetItemsHost()  as   VirtualizingPanel  ;
                virtualizingPanel.CallEnsureGenerator();

                 TreeViewItem  itemTemp =  null  ;
                 ItemsItem1  objTemp =  null  ;
                 int  visiableIndex = -1;
                 int  findIndex = -1;
                 int  count1 = _currentSelectedItem.Items.Count;
                 for  ( int  i = 0; i < count1; i++)
                {
                    itemTemp = (  TreeViewItem  )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(i);
                     if  ( null  != itemTemp)
                    {
                        visiableIndex = i;
                    }

                    objTemp = _currentSelectedItem.Items.GetItemAt(i)  as   ItemsItem1  ;
                     if  ( null  != objTemp && objTemp.Label.Equals(findItem))
                    {
                        findIndex = i;
                    }
                }

                 if  (findIndex == -1 || visiableIndex == -1)
                {
                     return  ;
                }

                 if  (findIndex < visiableIndex)
                {
                     for  ( int  j = visiableIndex; j >= findIndex; j--)
                    {
                        itemTemp = ( TreeViewItem  )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(j);
                         if  ( null  != itemTemp)
                        {
                            itemTemp.BringIntoView();
                        }
                    }
                }
                 else   if  (findIndex > visiableIndex)
                {
                     for  ( int  j = visiableIndex; j <= findIndex; j++)
                    {
                        itemTemp = ( TreeViewItem  )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(j);
                         if  ( null  != itemTemp)
                        {
                            itemTemp.BringIntoView();
                        }
                    }
                }
                 else
                {
                    itemTemp = ( TreeViewItem  )_currentSelectedItem.ItemContainerGenerator.ContainerFromIndex(visiableIndex);
                     if  ( null  != itemTemp)
                    {
                        itemTemp.BringIntoView();
                    }
                }

                 if  ( null  != itemTemp)
                {
                    _currentSelectedItem = itemTemp;
                    (_currentSelectedItem  as   TreeViewItem  ).IsSelected =  true ;
                    _currentSelectedItem.BringIntoView();
                }

            }
             catch  (System. Exception  ex)
            {
                 //
            }
        }


你可能感兴趣的:(WPF,bringIntoView,itemsControl)