ItemContainerGenerator.ContainerFromItem () возвращает ноль? - PullRequest
32 голосов
/ 16 июля 2011

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

Ответы [ 10 ]

45 голосов
/ 27 января 2013

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

Получить строку в сетке данных

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

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

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

14 голосов
/ 18 июля 2011

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

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

9 голосов
/ 02 февраля 2016
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");
}
6 голосов
/ 17 июля 2011

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

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

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

4 голосов
/ 05 января 2016

Я немного опоздал на вечеринку, но вот еще одно решение, которое в моем случае безотказно,

После попытки многих решений, предлагающих добавить IsExpanded и IsSelected к базовому объекту и привязку к ним в стиле 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

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

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

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

TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
  TheListBox.Dispatcher.Invoke(() =>
  {
     var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0);
     if (TheOne != null)
       // Use The One
  });
};
2 голосов
/ 02 сентября 2013

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

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

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

1 голос
/ 28 октября 2014

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

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}

РЕДАКТИРОВАТЬ 29.10.2014: На самом деле вам даже не нужен код диспетчера потоков.Вы можете установить все, что вам нужно, чтобы установить нулевое значение, чтобы инициировать первое измененное событие выбора, а затем вернуться из этого события, чтобы будущие события работали, как ожидалось.

1 голос
/ 23 июля 2013

VirtualizingStackPanel.IsVirtualizing = "False" Делает элемент управления нечетким.Смотрите ниже реализацию.Что помогает мне избежать той же проблемы.Установите ваше приложение VirtualizingStackPanel.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;
}
0 голосов
/ 13 января 2016

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

...