代码之家  ›  专栏  ›  技术社区  ›  Andy

强制WPF在ItemsControl中创建项

  •  6
  • Andy  · 技术社区  · 15 年前

    ListBox 在UI中正确显示。我想一种方法是通过所有的孩子 列表框 在可视化树中,获取他们的文本,然后将其与我期望的文本进行比较。

    这种方法的问题在于内部 列表框 VirtualizingStackPanel 以显示其项目,从而仅创建可见的项目。我最终遇到了 ItemContainerGenerator 类,看起来它应该强制WPF在可视树中为指定项创建控件。不幸的是,这给我带来了一些奇怪的副作用。下面是我的代码,用于生成 :

    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

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

    我做错了什么?是否有另一种/更好的方法来强制使用项目 ItemsControl

    使现代化 PrepareItemContainer() 项目控制 直到我向下滚动查看它,那时只有容器本身(即。 ListBoxItem 控件 将显示项目的文本)。

    如果我遍历传递给的控件的可视树 结果是一样的。在这两种情况下,只有 已创建,但未创建其子项。

    我找不到一个好方法来添加 到可视化树。我找到了 虚拟化钉板 在可视化树中,但调用其 Children.Add() 结果是 InvalidOperationException (无法将项目直接添加到 ItemPanel ,因为它为其 ).作为测试,我试着打电话给 AddVisualChild()

    6 回复  |  直到 15 年前
        1
  •  3
  •   user76035    15 年前

    如果ListBox使用VirtualzingStackPanel,只需快速查看,也许用StackPanel代替它就足够了

    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
          <StackPanel/>
      <ItemsPanelTemplate>
    <ListBox.ItemsPanel>
    
        2
  •  3
  •   David Turner    15 年前

    你可能走错了方向。我所做的是连接我的DataTemplate的[内容]的已加载事件:

    <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);
    }
    

    这种方法在第一次显示行时对其进行初始化/检查,因此不会在前面执行所有行。如果你能接受这一点,那么也许这是更优雅的方法。

        3
  •  1
  •   Andy    15 年前

    我想我知道怎么做了。问题是生成的项没有添加到可视化树中。经过一些搜索,我能想到的最好办法就是调用 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);
        }
    }
    
        4
  •  1
  •   MarkoW    11 年前

    安迪的解决方案是一个很好的主意,但并不完整。例如,将在面板中创建前5个容器。名单上有300个>项目。我请求使用此逻辑的最后一个容器ADD。然后我请求最后一个index-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;
                }
            }
        }
    
        5
  •  0
  •   Jordan0Day    13 年前

    对于任何想知道这一点的人来说,在Andy的例子中,也许用普通StackPanel替换VirtualzingStackPanel是最好的解决方案。

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

        6
  •  -1
  •   Glenn Slayden    9 年前

    就我而言,我发现 UpdateLayout() ItemsControl ( ListBox , ListView )启动了它的 ItemContainerGenerator ,使生成器的状态从“NotStarted”更改为“GeneratingContainers”,以及 null 集装箱已不再由海关归还 ItemContainerGenerator.ContainerFromItem 和/或 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);
        }