原文: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. GCD网络多线程---同步运行,异步运行,串行队列,并行队列

    总结:同步(无论是串行还是并行)----不又一次开辟子线程 异步(无论是串行还是并行)----开辟子线程 GCD: dispatch queue 主线程的main queue 并行队列 global ...

  2. ExtJs中window用法

    1.显示html var htmlTitle = "<div style='width:100%;text-align:center'>"; var fruits = ...

  3. jsvc 启动java 在linux下的实现原理

    http://blog.csdn.net/raintungli/article/details/8265009 JSVC:http://commons.apache.org/proper/common ...

  4. 判断文件是否存在的另一种方法 _access 和 _waccess

    函数原型: int _access( const char *path, int mode ); int _waccess( const wchar_t *path, int mode ); 示例代码 ...

  5. 百度echart--Uncaught Error: Component series.wordCloud not exists. Load it first.

    百度echart--Uncaught Error: Component series.wordCloud not exists. Load it first. 一.总结 一句话总结:关注报的错.可以通 ...

  6. PHP怎么读写XML?(四种方法)

    PHP怎么读写XML?(四种方法) 一.总结 1.这四种方法中,字符串的方式是最原始的方法.SimpleXML和DOM扩展是属于基于树的解析器,把整个文档存储为树的数据结构中,需要把整个文档都加载到内 ...

  7. 避免if语句的深层次嵌套

    公司做了个抢红包的限制,然后ajax请求的返回字段,要进行多层逻辑的判断,想想是真恶心,虽然都是简单逻辑,而且看别人以前写的代码,发现,哎,注释能不能写上吶,像我写代码都是细致到,哪怕初学者也能看懂这 ...

  8. 小强的HTML5移动开发之路(28)—— JavaScript回顾3

    一.基本数据类型 number:数字类型 string:字符串 (注意s小写:string是基本类型) boolean:布尔类型   //前三个都有对应的包装类 null:空类型 undefined: ...

  9. PyCharm 重构(refactor)快捷键

    提取变量(比如一个函数会返回一个变量值):ctrl + alt + v(v:variable) 将某段代码封装为一个函数(函数+函数调用):ctrl + alt + m(m:method)

  10. Erlang入门

    Erlang简史(翻译) Erlang入门(二)—并发编程 Erlang入门(三)——分布式编程 Erlang入门(四)——错误处理和鲁棒性 Erlang入门(五)——补遗