wpf ItemContainerGenerator.ContainerFromItem() 返回 null?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6713365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:58:44  来源:igfitidea点击:

ItemContainerGenerator.ContainerFromItem() returns null?

wpflistboxcontainersitemssource

提问by Sonny Boy

I'm having a bit of weird behavior that I can't seem to work out. When I iterate through the items in my ListBox.ItemsSource property, I can't seem to get the container? I'm expecting to see a ListBoxItem returned, but I only get null.

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

Any ideas?

有任何想法吗?

Here's the bit of code I'm using:

这是我正在使用的代码:

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

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

The ItemsSource is currently set to a Dictionary and does contain a number of KVPs.

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

采纳答案by Sonny Boy

Finally sorted out the problem... By adding VirtualizingStackPanel.IsVirtualizing="False"into my XAML, everything now works as expected.

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

On the downside, I miss out on all the performance benefitst of the virtualization, so I changed my load routing to async and added a "spinner" into my listbox while it loads...

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

回答by Phred Menyhert

I found something that worked better for my case in this StackOverflow question:

我在这个 StackOverflow 问题中发现了一些更适合我的情况的东西:

Get row in datagrid

获取数据网格中的行

By putting in UpdateLayout and a ScrollIntoView calls before calling ContainerFromItem or ContainerFromIndex, you cause that part of the DataGrid to be realized which makes it possible for it return a value for ContainerFromItem/ContainerFromIndex:

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

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

If you don't want the current location in the DataGrid to change, this probably isn't a good solution for you but if that's OK, it works without having to turn off virtualizing.

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

回答by epox

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

回答by H.B.

Step through the code with the debugger and see if there is actually nothing retured or if the as-cast is just wrong and thus turns it to null(you could just use a normal cast to get a proper exception).

使用调试器单步执行代码,看看是否实际上没有返回任何内容,或者as-cast 是否只是错误并因此将其转换为null(您可以使用普通强制转换来获得正确的异常)。

One problem that frequently occurs is that when an ItemsControlis virtualizing for most of the items no container will exist at any point in time.

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

Also i would not recommend dealing with the item containers directly but rather binding properties and subscribing to events (via the ItemsControl.ItemContainerStyle).

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

回答by Aybe

I'm a bit late for the party but here's another solution that's fail-proof in my case,

我参加聚会有点晚了,但这是另一种在我的情况下防故障的解决方案,

After trying many solutions suggesting to add IsExpandedand IsSelectedto underlying object and binding to them in TreeViewItemstyle, while this mostly worksin some case it still fails ...

在尝试了许多建议添加IsExpanded和添加IsSelected到底层对象并以TreeViewItem样式绑定到它们的解决方案之后,虽然这在某些情况下主要有效,但它仍然失败......

Note:my objective was to write a mini/custom Explorer-like view where when I click a folder in the right pane it gets selected on the TreeView, just like in Explorer.

注意:我的目标是编写一个迷你/自定义的类似资源管理器的视图,当我在右窗格中单击一个文件夹时,它会在 上被选中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;
    }
}

Multiple tricks used in here:

这里使用的多个技巧:

  • a stack for expanding every item from top to bottom
  • ensure to use current level generatorto find the item (really important)
  • the fact that generator for top-level items never return null
  • 用于从上到下扩展每个项目的堆栈
  • 确保使用当前级别生成器来查找项目(非常重要)
  • 顶级项目的生成器永远不会返回的事实 null

So far it works very well,

到目前为止效果很好,

  • no need to pollute your types with new properties
  • no need to disable virtualization at all.
  • 无需用新属性污染您的类型
  • 根本不需要禁用虚拟化

回答by Amir

Use this subscription:

使用此订阅:

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

回答by Benoit Blanchon

Although disabling virtualization from XAML works, I think it's better to disable it from the .cs file which uses ContainerFromItem

尽管从 XAML 禁用虚拟化有效,但我认为最好从使用的 .cs 文件中禁用它 ContainerFromItem

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

That way, you reduce the coupling between the XAML and the code; so you avoid the risk of someone breaking the code by touching the XAML.

这样,您可以减少 XAML 和代码之间的耦合;这样您就可以避免有人通过接触 XAML 来破坏代码的风险。

回答by fvojvodic

Most probably this is a virtualization-related issue so ListBoxItemcontainers get generated only for currently visible items (see https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)

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

If you are using ListBoxI'd suggest switching to ListViewinstead - it inherits from ListBoxand it supports ScrollIntoView()method which you can utilize to control virtualization;

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

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

(the example above also utilizes the DoEvents()static method explained in more detail here; WPF how to wait for binding update to occur before processing more code?)

(上面的例子也使用了DoEvents()这里更详细解释的静态方法;WPF如何在处理更多代码之前等待绑定更新发生?

There are a few other minor differences between the ListBoxand ListViewcontrols (What is The difference between ListBox and ListView) - which should not essentially affect your use case.

ListBoxListView控件之间还有一些其他的细微差别(ListBox 和 ListView 之间的区别是什么) - 这基本上不会影响您的用例。

回答by Daniel Caban

For anyone still having issues with this, I was able to work around this issue by ignoring the first selection changed event and using a thread to basically repeat the call. Here's what I ended up doing:

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

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 10/29/2014: You actually don't even need the thread dispatcher code. You can set whatever you need to null to trigger the first selection changed event and then return out of the event so that future events work as expected.

编辑 10/29/2014:您实际上甚至不需要线程调度程序代码。您可以将需要的任何内容设置为 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
        }

回答by PropertyChangedEventHandler

VirtualizingStackPanel.IsVirtualizing="False" Makes the control fuzzy . See the below implementation. Which helps me to avoid the same issue. Set your application VirtualizingStackPanel.IsVirtualizing="True" always.

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

See the linkfor detailed info

查看链接了解详细信息

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