6

我想验证我的项目在ListBoxUI 中正确显示。我想一种方法是遍历ListBox视觉树中的所有孩子,获取他们的文本,然后将其与我期望的文本进行比较。

这种方法的问题在于内部ListBox使用 aVirtualizingStackPanel来显示其项目,因此仅创建可见的项目。我最终遇到了这个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找到 a。我意识到还有其他方法可以在项目中包含文本,但一旦我得到项目,我会修复一代工作正常。)

在我的应用程序中,ItemsListBox包含 20 个项目,前 12 个项目最初可见。前 14 个项目的文本是正确的(可能是因为它们的控件已经生成)。但是,对于第 15-20 项,我根本没有收到任何文本。另外,如果我滚动到底部ItemsListBox,第 15-20 项的文本也是空白的。因此,我似乎在干扰 WPF 生成控件的正常机制。

我究竟做错了什么?是否有一种不同/更好的方法可以强制将 anItemsControl中的项目添加到可视化树中?

更新:我认为我已经找到了发生这种情况的原因,尽管我不知道如何解决它。我假设调用PrepareItemContainer()将生成任何必要的控件来显示项目,然后将容器添加到正确位置的可视化树中。事实证明,它没有做这两件事。直到我向下滚动查看它,容器才被添加到ItemsControl,当时只有容器本身(即ListBoxItem)被创建 - 它的子级没有被创建(这里应该添加一些控件,其中一个应该是将TextBlock显示项目的文本)。

如果我遍历我传递给PrepareItemContainer()结果的控件的可视化树是相同的。在这两种情况下,都只ListBoxItem创建了 ,而没有创建它的任何子项。

我找不到将其添加ListBoxItem到可视化树的好方法。我VirtualizingStackPanel在可视化树中找到了 ,但是Children.Add()在 an 中调用了它的结果InvalidOperationException(不能直接将项目添加到ItemPanel,因为它会为其生成项目ItemsControl)。作为测试,我尝试AddVisualChild()使用反射调用它(因为它受到保护),但这也不起作用。

4

6 回答 6

3

快速浏览一下,如果 ListBox 使用 VirtualizingStackPanel - 也许用 StackPanel 代替它就足够了

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
      <StackPanel/>
  <ItemsPanelTemplate>
<ListBox.ItemsPanel>
于 2009-03-10T13:27:40.590 回答
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);
}

这种方法在第一次显示行时对其进行初始化/检查,因此它不会预先执行所有行。如果您可以忍受,那么也许这是更优雅的方法。

于 2010-01-20T12:23:25.380 回答
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);
    }
}
于 2009-03-13T17:24:01.437 回答
1

Andy 的解决方案是一个非常好的主意,但并不完整。例如,前 5 个容器已创建并在面板中。该列表有 300 > 个项目。我用这个逻辑请求最后一个容器,添加。然后我请求最后一个索引 - 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;
            }
        }
    }
于 2013-12-03T20:24:25.137 回答
0

对于其他对此感到疑惑的人,在 Andy 的情况下,将 VirtualizingStackPanel 换成普通的 StackPanel 可能是最好的解决方案。

在 ItemContainerGenerator 上调用 PrepareItemContainer 不起作用的原因是项目必须位于可视树中, PrepareItemContainer 才能工作。使用 VirtualizingStackPanel,在 VirtualizingStackPanel 确定它正在/即将出现在屏幕上之前,该项目不会被设置为面板的可视子项。

另一种解决方案(我使用的那个)是创建您自己的 VirtualizingPanel,这样您就可以控制何时将项目添加到可视化树中。

于 2011-06-08T16:15:48.223 回答
-1

就我而言,我发现调用( ,UpdateLayout()等) 会启动它,这样生成器的状态就会从“NotStarted”变为“GeneratingContainers”,并且容器不再由and/or返回。ItemsControlListBoxListViewItemContainerGeneratornullItemContainerGenerator.ContainerFromItemItemContainerGenerator.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);
    }
于 2015-05-14T23:05:26.770 回答