原文:
WPF中ItemsControl应用虚拟化时找到子元素的方法
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)
{
//
}
}