企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
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)            {                //            }        }