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)
{
//
}
}