我想验证我的ListBox
中的项目是否在UI中正确显示。我认为这样做的一种方法是遍历视觉树中ListBox
的所有孩子,获取他们的文本,然后将其与我期望的文本进行比较。
此方法存在的问题是内部ListBox
使用VirtualizingStackPanel
来显示其项目,因此只会创建可见的项目。我最终遇到了ItemContainerGenerator
类,它看起来应该强制WPF在可视树中为指定项创建控件。不幸的是,这对我造成了一些奇怪的副作用。以下是我生成ListBox
中所有项目的代码:
List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
bool isNewlyRealized;
for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
{
isNewlyRealized = false;
DependencyObject cntr = generator.GenerateNext(out isNewlyRealized);
if(isNewlyRealized)
{
generator.PrepareItemContainer(cntr);
}
string itemText = GetControlText(cntr);
generatedItems.Add(itemText);
}
}
(如果您愿意,我可以提供GetItemText()
的代码,但它只是遍历可视树,直到找到TextBlock
。我意识到这是其他方法来获取文本item,但是一旦我让项目生成正常,我就会解决这个问题。)
在我的应用中,ItemsListBox
包含20个项目,前12个项目最初可见。前14项的文本是正确的(可能是因为它们的控件已经生成)。但是,对于第15-20项,我根本没有任何文字。此外,如果我滚动到ItemsListBox
的底部,则项目15-20的文本也是空白的。所以我似乎在干扰WPF的正常生成控件的机制。
我做错了什么?是否有一种不同/更好的方法可以强制ItemsControl
中的项目添加到可视树中?
更新:我认为我已经找到了为什么会发生这种情况,虽然我不知道如何修复它。我假设调用PrepareItemContainer()
会生成任何必要的控件来显示项目,然后将容器添加到可视树中的正确位置。事实证明,它没有做这两件事。在我向下滚动查看它之前,容器没有添加到ItemsControl
,那时只创建了容器本身(即ListBoxItem
) - 它的子节点没有创建(应该有一个这里添加了一些控件,其中一个应该是显示项目文本的TextBlock
。
如果我遍历我传递给PrepareItemContainer()
的控件的可视树,结果是相同的。在这两种情况下,只创建ListBoxItem
,并且不创建任何子项。
我找不到将ListBoxItem
添加到可视化树的好方法。我在可见树中找到VirtualizingStackPanel
,但调用其Children.Add()
会产生InvalidOperationException
(无法直接向ItemPanel
添加项目,因为它会为其ItemsControl
生成项目1}})。就像测试一样,我尝试使用Reflection调用它的AddVisualChild()
(因为它受到保护),但这也不起作用。
答案 0 :(得分:3)
快速查看,如果ListBox使用VirtualizingStackPanel - 也许它足以用StackPanel代替它
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
<ItemsPanelTemplate>
<ListBox.ItemsPanel>
答案 1 :(得分:3)
你可能会以错误的方式解决这个问题。我所做的是连接我的DataTemplate的[内容]的Loaded事件:
<DataTemplate DataType="{x:Type local:ProjectPersona}">
<Grid Loaded="Row_Loaded">
<!-- ... -->
</Grid>
</DataTemplate>
...然后处理事件处理程序中新显示的行:
private void Row_Loaded(object sender, RoutedEventArgs e)
{
Grid grid = (Grid)sender;
Carousel c = (Carousel)grid.FindName("carousel");
ProjectPersona project = (ProjectPersona)grid.DataContext;
if (project.SelectedTime != null)
c.ScrollItemIntoView(project.SelectedTime);
}
这种方法在首次显示行时进行行的初始化/检查,因此它不会预先完成所有行。如果你可以忍受,那么也许这是更优雅的方法。
答案 2 :(得分:1)
我想我想出了如何做到这一点。问题是生成的项目未添加到可视树中。经过一些搜索,我能想到的最好的方法是在VirtualizingStackPanel
中调用ListBox
的一些受保护方法。虽然这不是理想的,因为它仅用于测试,我认为我将不得不忍受它。
这对我有用:
VirtualizingStackPanel itemsPanel = null;
FrameworkElementFactory factory = control.ItemsPanel.VisualTree;
if(null != factory)
{
// This method traverses the visual tree, searching for a control of
// the specified type and name.
itemsPanel = FindNamedDescendantOfType(control,
factory.Type, null) as VirtualizingStackPanel;
}
List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
bool isNewlyRealized;
for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
{
isNewlyRealized = false;
UIElement cntr = generator.GenerateNext(out isNewlyRealized) as UIElement;
if(isNewlyRealized)
{
if(i >= itemsPanel.Children.Count)
{
itemsPanel.GetType().InvokeMember("AddInternalChild",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
Type.DefaultBinder, itemsPanel,
new object[] { cntr });
}
else
{
itemsPanel.GetType().InvokeMember("InsertInternalChild",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
Type.DefaultBinder, itemsPanel,
new object[] { i, cntr });
}
generator.PrepareItemContainer(cntr);
}
string itemText = GetControlText(cntr);
generatedItems.Add(itemText);
}
}
答案 3 :(得分:1)
安迪的解决方案是一个非常好的主意,但不完整。例如,创建前5个容器并在面板中创建。该列表有300&gt;项目。我用这个逻辑请求最后一个容器ADD。然后我请求最后一个索引 - 1个容器,这个logis ADD!那就是问题所在。面板内儿童的顺序无效。
解决方案:
private FrameworkElement GetContainerForIndex(int index)
{
if (ItemsControl == null)
{
return null;
}
var container = ItemsControl.ItemContainerGenerator.ContainerFromIndex(index -1);
if (container != null && container != DependencyProperty.UnsetValue)
{
return container as FrameworkElement;
}
else
{
var virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);
if (virtualizingPanel == null)
{
// do something to load the (perhaps currently unloaded panel) once
}
virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);
IItemContainerGenerator generator = ItemsControl.ItemContainerGenerator;
using (generator.StartAt(generator.GeneratorPositionFromIndex(index), GeneratorDirection.Forward))
{
bool isNewlyRealized = false;
container = generator.GenerateNext(out isNewlyRealized);
if (isNewlyRealized)
{
generator.PrepareItemContainer(container);
bool insert = false;
int pos = 0;
for (pos = virtualizingPanel.Children.Count - 1; pos >= 0; pos--)
{
var idx = ItemsControl.ItemContainerGenerator.IndexFromContainer(virtualizingPanel.Children[pos]);
if (!insert && idx < index)
{
////Add
virtualizingPanel.GetType().InvokeMember("AddInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { container });
break;
}
else
{
insert = true;
if (insert && idx < index)
{
break;
}
}
}
if (insert)
{
virtualizingPanel.GetType().InvokeMember("InsertInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { pos + 1, container });
}
}
return container as FrameworkElement;
}
}
}
答案 4 :(得分:0)
对于其他任何想知道这一点的人,在Andy的情况下,或许用普通的StackPanel交换VirtualizingStackPanel将是最好的解决方案。
调用ItemContainerGenerator上的PrepareItemContainer不起作用的原因是项目必须位于可视化树中才能使PrepareItemContainer工作。使用VirtualizingStackPanel,在VirtualizingStackPanel确定它/将要在屏幕上之前,该项目不会被设置为面板的可视子项。
另一种解决方案(我使用的解决方案)是创建您自己的VirtualizingPanel,以便您可以控制何时将项目添加到可视树中。
答案 5 :(得分:-1)
就我而言,我发现在UpdateLayout()
(ItemsControl
,ListBox
等)上调用ListView
会启动其ItemContainerGenerator
,以便生成器的状态从&#34; NotStarted&#34; null
和/或ItemContainerGenerator.ContainerFromItem
不再返回&#34; GeneratingContainers&#34;和ItemContainerGenerator.ContainerFromIndex
个容器。
例如:
public static bool FocusSelectedItem(this ListBox listbox)
{
int ix;
if ((ix = listbox.SelectedIndex) < 0)
return false;
var icg = listbox.ItemContainerGenerator;
if (icg.Status == GeneratorStatus.NotStarted)
listbox.UpdateLayout();
var el = (UIElement)icg.ContainerFromIndex(ix);
if (el == null)
return false;
listbox.ScrollIntoView(el);
return el == Keyboard.Focus(el);
}