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)
{
//
}
}
- 前言
- win32与WPF的混合编程
- WPF: 一个可以用StoryBoard动态改变Grid行宽/列高的类
- MFC中调用WPF教程
- Expression Blend操作: 使用behavior来控制Storyboard
- WPF DatePicker 的textbox的焦点
- WPF 使用MultiBinding ,TwoWay ,ValidationRule ,需要注意的事项
- WPF TreeView 后台C#选中指定的Item, 需要遍历
- WPF GridViewColumn Sort DataTemplate
- DataGridColum的bug
- WPF Get Multibinding Expression, Update Source,
- WPF 后台触发 Validate UI‘s Element
- WPF ValidationRule 触发ErrorTemplate 的注意事项
- WPF DelegateCommand CanExecute
- WPF TextBox PreviewTextInput handle IME (chinese)
- No overload for &#39;OnStartup&#39; matches delegate &#39;System.Windows.StartupEventHandler&#39;
- WPF error: does not contain a static &#39;Main&#39; method suitable for an entry point
- WPF GridView中的CellTemplate失效的原因
- DataGrid 显示选中的item
- 如何得到WPF中控件绑定的EventTrigger
- 选中DataGrid的Cell而不是row
- ContextMenu的自定义
- 输入框只能输入英文
- TextBox的OnTextboxChanged事件里对Text重新赋值带中文, 导致崩溃
- DataGrid当列宽超出当前宽度时,没有数据也恒有滚动条
- wpf如何获取control template里的元素
- Set connectionId threw an exception.
- WPF中Visible设为Collapse时,VisualTreeHelper.GetChildrenCount为0
- XAML 编码规范 (思考)
- 如何为现有控件的DependencyProperty添加Value Changed事件?
- TreeView滚动TreeViewItem
- 为BindingList添加Sort
- WPF Background的设置有坑
- 自定义Panel中添加依赖属性需要注意的问题
- TextBlock截断字符显示为....
- DataGrid 支持字符截断显示
- TreeView控件实践
- WPF如何更改系统控件的默认高亮颜色 (Highlight brush)
- ViewModel中C# Property自动添加OnPropertyChanged处理的小工具, 以及相应Python知识点
- WPF中Xaml编译正常而Designer Time时出错的解决办法
- 关于Snoop的用法
- wpf中为DataGrid添加checkbox支持多选全选
- WPF中DataGrid控件的过滤(Filter)性能分析及优化
- wpf控件提示Value ‘’ can not convert
- DropShadowEffect导致下拉框控件抖动
- 再论WPF中的UseLayoutRounding和SnapsToDevicePixels
- WPF案例:如何设计历史记录查看UI
- WPF案例:如何设计搜索框(自定义控件的原则和方法)
- WPF基本概念入门
- WPF开发中Designer和码农之间的合作
- 聊聊WPF中的Dispatcher
- 聊聊WPF中字体的设置
- Bug:DataGridCell的显示不完整
- WPF中ToolTip的自定义
- WPF中ItemsControl绑定到Google ProtocolBuffer的结构体时的性能问题
- TreeView的性能问题
- Xaml中string(字符串)常量的定义以及空格的处理
- 依赖属性
- WPF中的CheckBox的_ (underscore / 下划线)丢失
- WPF错误:必须使“Property”具有非 null 值。
- WPF中ItemsControl应用虚拟化时找到子元素的方法
- WPF毫秒级桌面时钟的实现-C#中Hook(钩子)的应用
- KB2464222导致IsNonIdempotentProperty方法找不见
- WPF中PreviewMouseDownEvent的系统处理:TabItem的PreviewMouseDown 事件弹框后不切换的问题调查
- WPF文字渲染相关的问题及解决
- wpf中的默认右键菜单中的复制、粘贴、剪贴等没有本地化的解决方案
- WPF内部DeliverEvent读锁和PrivateAddListener写锁导致死锁
- Windbg调试WPF的依赖属性
- WPF 后台Render线程崩溃, Exception from HRESULT: 0x88980406
- WPF中DependencyObject与DependencyProperty的源码简单剖析
- 禁用WPF中DataGrid默认的鼠标左键拖动多选行的效果
- wpf工程中在Xaml文件下添加多个cs文件
- ScrollViewer滚动到底来触发加载数据的Behavior