0
点赞
收藏
分享

微信扫一扫

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

小美人鱼失去的腿 2022-01-20 阅读 45
wpfmicrosoft

 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)

            {

                 //

            }

        }



举报

相关推荐

0 条评论