ItemContainerGenerator.ContainerFromItem()返回null?

时间:2011-07-15 21:31:45

标签: wpf listbox containers itemssource

我有一些奇怪的行为,我似乎无法解决。当我遍历ListBox.ItemsSource属性中的项目时,我似乎无法获取容器?我期待看到一个ListBoxItem返回,但我只得到null。

有什么想法吗?

以下是我正在使用的代码:

this.lstResults.ItemsSource.ForEach(t =>
    {
        ListBoxItem lbi = this.lstResults.ItemContainerGenerator.ContainerFromItem(t) as ListBoxItem;

        if (lbi != null)
        {
            this.AddToolTip(lbi);
        }
    });

ItemsSource当前设置为Dictionary并且包含许多KVP。

10 个答案:

答案 0 :(得分:44)

在StackOverflow问题中,我发现了一些对我的案例更有效的方法:

Get row in datagrid

通过在调用ContainerFromItem或ContainerFromIndex之前放入UpdateLayout和ScrollIntoView调用,可以实现DataGrid的那部分,这使得它可以返回ContainerFromItem / ContainerFromIndex的值:

dataGrid.UpdateLayout();
dataGrid.ScrollIntoView(dataGrid.Items[index]);
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);

如果您不希望DataGrid中的当前位置发生变化,这对您来说可能不是一个好的解决方案,但如果没有问题,则无需关闭虚拟化即可。

答案 1 :(得分:14)

最后解决了问题...通过将VirtualizingStackPanel.IsVirtualizing="False"添加到我的XAML中,现在一切都按预期工作。

在缺点方面,我错过了虚拟化的所有性能优势,因此我将负载路由更改为异步,并在加载时将“微调器”添加到我的列表框中...

答案 2 :(得分:9)

object viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
if (viewItem == null)
{
    list.UpdateLayout();
    viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
    Debug.Assert(viewItem != null, "list.ItemContainerGenerator.ContainerFromItem(item) is null, even after UpdateLayout");
}

答案 3 :(得分:6)

使用调试器逐步执行代码,看看实际上没有任何内容被撤消,或者as - cast是否错误,从而将其转换为null(您可以使用普通的强制转换来得到一个适当的例外)。

经常出现的一个问题是,当ItemsControl虚拟化大多数项目时,任何时间点都不会存在容器。

此外,我不建议直接处理项容器,而是绑定属性和订阅事件(通过ItemsControl.ItemContainerStyle)。

答案 4 :(得分:3)

使用此订阅:

TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
  TheListBox.Dispatcher.Invoke(() =>
  {
     var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0);
     if (TheOne != null)
       // Use The One
  });
};

答案 5 :(得分:3)

我参加派对有点晚了,但这是另一个在我的案例中证明是失败的解决方案,

尝试了许多解决方案,建议将IsExpandedIsSelected添加到基础对象并以TreeViewItem样式绑定到它们,而在某些情况下此主要用于它仍然失败......

注意:我的目标是编写一个迷你/自定义类似资源管理器的视图,当我点击右侧窗格中的文件夹时,它会在TreeView上被选中,就像在资源管理器中一样

private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var item = sender as ListViewItem;
    var node = item?.Content as DirectoryNode;
    if (node == null) return;

    var nodes = (IEnumerable<DirectoryNode>)TreeView.ItemsSource;
    if (nodes == null) return;

    var queue = new Stack<Node>();
    queue.Push(node);
    var parent = node.Parent;
    while (parent != null)
    {
        queue.Push(parent);
        parent = parent.Parent;
    }

    var generator = TreeView.ItemContainerGenerator;
    while (queue.Count > 0)
    {
        var dequeue = queue.Pop();
        TreeView.UpdateLayout();
        var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
        if (queue.Count > 0) treeViewItem.IsExpanded = true;
        else treeViewItem.IsSelected = true;
        generator = treeViewItem.ItemContainerGenerator;
    }
}

此处使用的多项技巧:

  • 用于从顶部到底部扩展每个项目的堆栈
  • 确保使用当前级别的生成器来查找项目(非常重要)
  • 顶级项目的生成器永远不会返回null

到目前为止它的效果非常好,

  • 无需使用新属性污染您的类型
  • 根本无需停用虚拟化

答案 6 :(得分:2)

虽然从XAML禁用虚拟化可行,但我认为最好从使用ContainerFromItem的.cs文件中禁用它

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

这样,减少了XAML和代码之间的耦合;因此,您可以通过触摸XAML来避免某人违反代码的风险。

答案 7 :(得分:1)

VirtualizingStackPanel.IsVirtualizing =“False”使控件模糊。请参阅以下实施。这有助于我避免同样的问题。 始终设置您的应用程序VirtualizingStackPanel.IsVirtualizing =“True”。

有关详细信息,请参阅link

/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
    if (container != null)
    {
        if (container.DataContext == item)
        {
            return container as TreeViewItem;
        }

        // Expand the current container
        if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
        {
            container.SetValue(TreeViewItem.IsExpandedProperty, true);
        }

        // Try to generate the ItemsPresenter and the ItemsPanel.
        // by calling ApplyTemplate.  Note that in the 
        // virtualizing case even if the item is marked 
        // expanded we still need to do this step in order to 
        // regenerate the visuals because they may have been virtualized away.

        container.ApplyTemplate();
        ItemsPresenter itemsPresenter = 
            (ItemsPresenter)container.Template.FindName("ItemsHost", container);
        if (itemsPresenter != null)
        {
            itemsPresenter.ApplyTemplate();
        }
        else
        {
            // The Tree template has not named the ItemsPresenter, 
            // so walk the descendents and find the child.
            itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            if (itemsPresenter == null)
            {
                container.UpdateLayout();

                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            }
        }

        Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);


        // Ensure that the generator for this panel has been created.
        UIElementCollection children = itemsHostPanel.Children; 

        MyVirtualizingStackPanel virtualizingPanel = 
            itemsHostPanel as MyVirtualizingStackPanel;

        for (int i = 0, count = container.Items.Count; i < count; i++)
        {
            TreeViewItem subContainer;
            if (virtualizingPanel != null)
            {
                // Bring the item into view so 
                // that the container will be generated.
                virtualizingPanel.BringIntoView(i);

                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);
            }
            else
            {
                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);

                // Bring the item into view to maintain the 
                // same behavior as with a virtualizing panel.
                subContainer.BringIntoView();
            }

            if (subContainer != null)
            {
                // Search the next level for the object.
                TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                if (resultContainer != null)
                {
                    return resultContainer;
                }
                else
                {
                    // The object is not under this TreeViewItem
                    // so collapse it.
                    subContainer.IsExpanded = false;
                }
            }
        }
    }

    return null;
}

答案 8 :(得分:1)

对于仍然遇到此问题的任何人,我能够通过忽略第一个选择更改事件并使用线程基本上重复调用来解决此问题。这就是我最终做的事情:

private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX:Hacky workaround for an api issue
        //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason.  Basically we ignore the
        //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
        //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need I ignore the event twice just in case but I think you can get away with ignoring only the first one.
        if (_hackyfix == 0 || _hackyfix == 1)
        {
            _hackyfix++;
            Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            OnMediaSelectionChanged(sender, e);
        });
        }
        //END OF HACKY FIX//Actual code you need to run goes here}

EDIT 2014年10月29日:您实际上甚至不需要线程调度程序代码。您可以设置null所需的任何内容以触发第一个选择更改事件,然后返回事件,以便将来事件按预期工作。

        private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX: Daniel note:  Very hacky workaround for an api issue
        //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason.  Basically we ignore the
        //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
        //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need
        if (_hackyfix == 0)
        {
            _hackyfix++;
            /*
            Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            OnMediaSelectionChanged(sender, e);
        });*/
            return;
        }
        //END OF HACKY FIX
        //Your selection_changed code here
        }

答案 9 :(得分:0)

这很可能是与虚拟化相关的问题,因此仅为当前可见的项目生成ListBoxItem个容器(请参阅https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9

如果您使用的是ListBox,我建议您切换到ListView - 它继承自ListBox并且支持您可以用来控制的ScrollIntoView()方法虚拟化;

targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;

(上面的示例还使用了此处详细说明的DoEvents()静态方法; WPF how to wait for binding update to occur before processing more code?

ListBoxListView控件(What is The difference between ListBox and ListView)之间存在一些其他细微差别 - 这不一定会影响您的使用案例。