wpf的虚拟化技术会使UI的控件只初始化看的到的子元素, 而不是所有子元素都被初始化,这样会提高UI性能。
解决方法2:
但是我们经常会遇到一个问题:
应用虚拟化后看不见的子元素因为没有实际产生导致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)
{
//
}
}