原文: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. 【u006】海战

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 在峰会期间,武装部队得处于高度戒备.警察将监视每一条大街,军队将保卫建筑物,领空将布满了F-2003飞 ...

  2. [NPM] Use package.json variables in npm scripts

    In this lesson we will show that you can leverage values that you already have provided in your pack ...

  3. .net core ——微服务内通信Thrift和Http客户端响应比较

    原文:.net core --微服务内通信Thrift和Http客户端响应比较 目录 1.Benchmark介绍 2.测试下微服务访问效率 3.结果 引用链接 1.Benchmark介绍 wiki中有 ...

  4. Ambari——大数据平台的搭建利器(一)

    Ambari 跟 Hadoop 等开源软件一样,也是 Apache Software Foundation 中的一个项目,并且是**项目.目前最新的发布版本是 2.0.1,未来不久将发布 2.1 版本 ...

  5. NOIP模拟 cube - 数学

    题目原文: 豆豆还是觉得自己智商太低了,就又去做数学题了.一看到题,他就觉得自己可能真的一点智商都没有.便哭着跑来像 dalao 求教:如果存在正整数 A,B ,满足 A3 - B3 = x ,则称质 ...

  6. 【codeforces 785A】Anton and Polyhedrons

    [题目链接]:http://codeforces.com/contest/785 [题意] 给你各种形状的物体; 然后让你计算总的面数; [题解] 用map来记录各种物体本该有的面数; 读入各种物体; ...

  7. TensorFlow 学习(八)—— 梯度计算(gradient computation)

    maxpooling 的 max 函数关于某变量的偏导也是分段的,关于它就是 1,不关于它就是 0: BP 是反向传播求关于参数的偏导,SGD 则是梯度更新,是优化算法: 1. 一个实例 relu = ...

  8. 怎样获取android手机联系人并按字母展示(三)

    假设获取contact的头像信息并展示: 怎样依据photoId来获取bitmap: public static Bitmap getContactPhoto(Context context, lon ...

  9. 一个简易版的Function.prototype.bind实现

    重新看<JavaScript设计模式与开发实践>一书,第32页发现个简易版的Function.prototype.bind实现,非常容易理解,记录在这了. Function.prototy ...

  10. Formview单文档或对话框项目接受不到按键消息的解决办法

    当对话框或formview界面上有控件时,由于焦点在控件上,因此wm_char,wm_keydown等按键消息会被控件捕获,而导致对话框或formview无法接受该类按键消息.这时候通常的解决方法是在 ...