原文: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. kvm 虚拟化概述及 virt-manager 安装虚拟机

    一.KVM定义 基于内核的虚拟机(英语:Kernel-based Virtual Machine,简称KVM),是一种用于Linux内核中的虚拟化基础设施. KVM眼下支持Intel VT及AMD-V ...

  2. [Most.js] Create Streams From Single Values With Most.js

    Most provides many means for creating streams, the simplest of which is the offunction. In this less ...

  3. SQLServer重建索引

    Use [数据库名称]Go DECLARE @DBCCString NVARCHAR(1000)DECLARE @TableName VARCHAR(100)DECLARE Cur_Index CUR ...

  4. 【codeforces 754D】Fedor and coupons

    time limit per test4 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  5. Codeforces Round #313 (Div. 2) 560C Gerald&#39;s Hexagon(脑洞)

    C. Gerald's Hexagon time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  6. js进阶 10-6 jquery中的属性选择器有哪些

    js进阶 10-6 jquery中的属性选择器有哪些 一.总结 一句话总结: 1.第一遍能学会么? 一遍是肯定学不会的,要多学几遍,所以想着怎么加快速度,减少学习的遍数 2.属性选择器是干嘛的? 选择 ...

  7. Qt的paint函数重写,以及QPaint给一条线绘制箭头

    直接代码: QPainter *painter; static const double Pi = 3.14159265358979323846264338327950288419717; stati ...

  8. ES6与React中this完全解惑

    计划写很长的篇幅,预计12月初完成. 这篇文章涉及的知识较多,可能一次消化不了,可以渐渐来. 先说结论: 无论是ES6还是React的this,相对于ES5,只是增加了箭头函数this绑定了其封闭上下 ...

  9. Android Studio运行main方法

    这样想做一些测试就很简单了 实现步骤如下: 1.当前项目右键->new->Module->Java Library 2.修改你创建javaLib的build.gradle文件 改为( ...

  10. R 语言文件读写

    1. working directory:工作目录 > getwd() > setwd("C:/data") # 设定当前工作目录 2. 读取格式化的 table &g ...