Подтвердить что ты не робот

ItemContainerGenerator.ContainerFromItem() возвращает null?

У меня немного странное поведение, которое, похоже, не получается. Когда я повторяю элементы в свойстве 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 установлен в словарь и содержит несколько KVP.

4b9b3361

Ответ 1

Наконец, разобрался с проблемой... Добавив VirtualizingStackPanel.IsVirtualizing="False" в свой XAML, все теперь работает так, как ожидалось.

С другой стороны, я пропустил все преимущества производительности виртуализации, поэтому я изменил маршрутизацию нагрузки на async и добавил "прядильщик" в свой список, пока он загружается...

Ответ 2

Я нашел кое-что, что сработало лучше для моего случая в этом вопросе StackOverflow:

Получить строку в datagrid

Вставляя вызовы UpdateLayout и ScrollIntoView перед вызовом ContainerFromItem или ContainerFromIndex, вы вызываете реализацию части DataGrid, которая позволяет вернуть значение для ContainerFromItem/ContainerFromIndex:

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

Если вы не хотите, чтобы текущее местоположение в DataGrid изменилось, это, вероятно, не очень хорошее решение для вас, но если это нормально, оно работает без необходимости отключения виртуализации.

Ответ 3

Пройдите код с помощью отладчика и посмотрите, нет ли в действительности ничего, или если as -cast просто ошибается и, таким образом, превращает его в null (вы можете просто использовать обычный бросок, чтобы получить правильное исключение).

Одной из проблем, которая часто возникает, является то, что когда ItemsControl является виртуализацией для большинства элементов, контейнер не будет существовать в любой момент времени.

Также я бы не рекомендовал напрямую обращаться к контейнерам элементов, а скорее связывать свойства и подписываться на события (через ItemsControl.ItemContainerStyle).

Ответ 4

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

Ответ 5

Используйте эту подписку:

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

Ответ 6

Я немного опаздываю на вечеринку, но здесь другое решение, которое отказывается в моем случае,

Попробовав множество решений, предлагающих добавить IsExpanded и IsSelected к базовому объекту и привязку к ним в стиле TreeViewItem, в то время как это в основном работает в некоторых случаях, он все равно не работает...

Примечание.. Моя цель состояла в том, чтобы написать мини-/пользовательский вид, похожий на 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;
    }
}

Несколько трюков, используемых здесь:

  • стек для расширения каждого элемента сверху вниз
  • обеспечить использование генератора текущего уровня для поиска элемента (действительно важно)
  • тот факт, что генератор для элементов верхнего уровня никогда не возвращает null

Пока это работает очень хорошо,

  • не нужно загрязнять ваши типы новыми свойствами
  • не нужно вообще отключать виртуализацию.

Ответ 7

Хотя отключение виртуализации от XAML работает, я думаю, что лучше отключить его из файла .cs, который использует ContainerFromItem

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

Таким образом, вы уменьшаете связь между XAML и кодом; поэтому вы избегаете риска того, что кто-то нарушит код, коснувшись XAML.

Ответ 8

VirtualizingStackPanel.IsVirtualizing = "False" Делает управление нечетким. См. Нижеследующую реализацию. Это помогает мне избежать такой же проблемы. Всегда устанавливайте приложение VirtualizationStackPanel.IsVirtualizing = "True".

Подробнее см. ссылку .

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

Ответ 9

Для всех, у кого есть проблемы с этим, я смог обойти эту проблему, проигнорировав первое событие с измененным выбором и используя поток, чтобы в основном повторить вызов. Вот что я сделал:

private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX:Hacky workaround for an api issue
        //Microsoft 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: на самом деле вам даже не нужен код диспетчера потоков. Вы можете установить все, что вам нужно, чтобы ввести null для запуска первого события с измененным выбором и затем вернуться из события, чтобы будущие события работали должным образом.

        private int _hackyfix = 0;
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //HACKYFIX: Daniel note:  Very hacky workaround for an api issue
        //Microsoft 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
        }

Ответ 10

Скорее всего, это проблема, связанная с виртуализацией, поэтому контейнеры 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, как ждать обновления привязки до обработки большего количества кода?)

Есть несколько других незначительных различий между элементами управления ListBox и ListView (В чем разница между ListBox и ListView), что не должно существенно влиять на ваш прецедент.