原文: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中ItemsControl应用虚拟化时找到子元素的方法的更多相关文章

  1. WPF中的ListBox实现按块显示元素的方法

    本文实例讲述了WPF中的ListBox实现按块显示元素的方法.分享给大家供大家参考,具体如下: 注意:需要设置ListBox的属性 ScrollViewer.HorizontalScrollBarVi ...

  2. jQuery获取所有父级元素及同级元素及子元素的方法

    jQuery获取所有父级元素及同级元素及子元素的方法 1.获取父级元素 $("#id").parent() 获取其父级元素 $("#id").parents() ...

  3. WPF ListBox/ListView/DataGrid 虚拟化时的滚动方式

    ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...

  4. 关于WPF中ItemsControl系列控件中Item不能继承父级的DataContext的解决办法

    WPF中所有的集合类控件,子项都不能继承父级的DataContext,需要手动将绑定的数据源指向到父级控件才可以. <DataGridTemplateColumn Header="操作 ...

  5. 在WPF中一种较好的绑定Enums数据方法

    引言 在你使用wpf应用程序开发的时候,是否需要进行数据绑定到Enum数据呢?在这篇文章中,我将向你展示在WPF中处理Enum数据绑定的方法. 假设存在一个这样的Enum数据的定义,具体内容如下文代码 ...

  6. js 下获取子元素的方法

    笔记核心: firstElementChild只会获取元素节点对象,从名称就可以看出来,firstChild则可以获取文本节点对象(当然也可以获取元素节点对象),比如空格和换行都被当做文本节点. js ...

  7. 分析轮子(八)- List.java 各种遍历方式及遍历时移除元素的方法

    注:玩的是JDK1.7版本 1:先尝栗子,再分析,代码简单,注释清晰,可自玩一下 /** * @description:测试集合遍历和移除元素的方式 * @author:godtrue * @crea ...

  8. WPF中ItemsControl绑定到Google ProtocolBuffer的结构体时的性能问题

    背景: 最近遇到一个DataGrid的性能问题:里面大概有4000个数据, 绑定的ItemSource的类也只有一层数据,即简单的List(里面每个是Protocol Buffer自动产生的一个类,1 ...

  9. WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)

    不知从什么时候开始,头像流行使用圆形了,于是各个平台开始追逐显示圆形裁剪图像的技术.WPF 作为一个优秀的 UI 框架,当然有其内建的机制支持这种圆形裁剪. 不过,内建的机制仅支持画刷,而如果被裁剪的 ...

随机推荐

  1. 【机器学习实战】第3章 决策树(Decision Tree)

    第3章 决策树 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/ ...

  2. Android 添加常驻图标到状态栏

    / * * 如果没有从状态栏中删除ICON,且继续调用addIconToStatusbar,则不会有任何变化.如果将notification中的resId设置不同的图标,则会显示不同的图标 * / p ...

  3. ant脚本中设置环境变量

    http://blog.csdn.net/quqi99/article/details/5329841

  4. Android组件——使用DrawerLayout仿网易新闻v4.4侧滑菜单

    摘要: 转载请注明出处:http://blog.csdn.net/allen315410/article/details/42914501 概述        今天这篇博客将记录一些关于DrawerL ...

  5. [Ramda] Convert a Promise.all Result to an Object with Ramda's zip and zipObj

    In this lesson, we'll use Promise.all to get an array that contains the resolved values from multipl ...

  6. Qt 静态函数QMetaObject::connectSlotsByName(QObject * object)

    看别人代码看到void on_MyWidget_slotTest(); 就郁闷了,没看到他代码里有connect 却能把信号和槽可以连接起来. 今日回顾书本发现该函所的nb之处. QMetaObjec ...

  7. one hot 编码的实现

    one hot 编码,针对的是类别性属性(categorical),类别型属性可以为特征向量中的任一属性,比如性别(one hot 编码的意义在于,属性之间不具有数值上大小的区别,在对最后结果的影响上 ...

  8. 【t067】补充装备

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] Mini进入洞口前自然要先来到镇里的装备店买些装备.买每件装备都需要付出一定的体力点,同时也会获得一定 ...

  9. CentOS使用(一)-----各种环境搭建

    使用virtual box虚拟机,镜像为CentOS-6.9-x86_64-LiveDVD.iso,使用SecureCRT远程连接. 安装CentOS系统之后,选择host-only连接模式,修改虚拟 ...

  10. 自定义view布局过程详解

    布局过程,就是程序在运行时利用布局文件的代码来计算出实际尺寸的过程. 布局分为两个阶段:测量阶段和布局阶段. 测量阶段:从上到下递归地调用每个 View 或者 ViewGroup 的 measure( ...