it-swarm.com.de

 ItemContainerGenerator.ContainerFromItem () gibt null zurück?

Ich habe ein seltsames Verhalten, das mir scheinbar nicht gelingt. Wenn ich die Elemente in meiner ListBox.ItemsSource-Eigenschaft durchläuft, kann ich den Container scheinbar nicht abrufen. Ich erwarte, dass ein ListBoxItem zurückgegeben wird, aber ich bekomme nur null.

Irgendwelche Ideen?

Hier ist der Code, den ich verwende:

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

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

Die ItemsSource ist derzeit auf ein Dictionary eingestellt und enthält eine Reihe von KVPs.

32
Sonny Boy

Endlich das Problem gelöst ... Durch das Hinzufügen von VirtualizingStackPanel.IsVirtualizing="False" in meine XAML funktioniert alles jetzt wie erwartet.

Ein Nachteil ist, dass ich alle Performance-Vorteile der Virtualisierung verpasst habe. Deshalb habe ich mein Lastrouting in async geändert und einen "Spinner" in meine Listbox eingefügt, während es geladen wird ...

14
Sonny Boy

In dieser StackOverflow-Frage habe ich etwas gefunden, das für meinen Fall besser funktionierte:

Holen Sie sich eine Zeile in Datagrid

Durch das Einfügen von UpdateLayout und eines ScrollIntoView-Aufrufs vor dem Aufruf von ContainerFromItem oder ContainerFromIndex bewirken Sie, dass der Teil des DataGrid verwirklicht wird, der es ermöglicht, einen Wert für ContainerFromItem/ContainerFromIndex zurückzugeben:

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

Wenn Sie nicht möchten, dass sich der aktuelle Speicherort im DataGrid ändert, ist dies wahrscheinlich keine gute Lösung für Sie. Wenn dies jedoch in Ordnung ist, funktioniert es, ohne dass Sie die Virtualisierung deaktivieren müssen.

44
Phred Menyhert
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");
}
8
epox

Gehen Sie den Code mit dem Debugger durch und sehen Sie, ob tatsächlich nichts zurückgezogen wurde oder ob die as- Umwandlung falsch ist und daher in null umgewandelt wird (Sie könnten nur eine normale Umwandlung verwenden, um eine richtige Ausnahme zu erhalten).

Ein häufig auftretendes Problem besteht darin, dass bei der Virtualisierung einer ItemsControl für die meisten Elemente zu keinem Zeitpunkt ein Container vorhanden ist.

Außerdem würde ich nicht empfehlen, direkt mit den Artikelcontainern zu arbeiten, sondern Eigenschaften zu binden und Ereignisse zu abonnieren (über den ItemsControl.ItemContainerStyle).

6
H.B.

Verwenden Sie dieses Abonnement:

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

Ich bin ein bisschen spät für die Party, aber hier ist eine andere Lösung, die in meinem Fall ausfallsicher ist,

Nachdem Sie viele Lösungen ausprobiert hatten, in denen vorgeschlagen wurde, IsExpanded und IsSelected dem zugrunde liegenden Objekt hinzuzufügen und diese im TreeViewItem-Stil zu binden, während dieses meistens funktioniert, kann es in manchen Fällen immer noch fehlschlagen ...

Hinweis: Mein Ziel war es, eine Mini-/benutzerdefinierte Explorer-ähnliche Ansicht zu erstellen, in der ich, wenn ich auf einen Ordner im rechten Fensterbereich klicke, in der Variablen TreeView ausgewählt wird, genau wie im Explorer.

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

Mehrere Tricks hier verwendet:

  • ein Stapel zum Erweitern jedes Elements von oben nach unten
  • stellen Sie sicher, dass Sie den aktuellen Pegel generator verwenden, um den Artikel zu finden (wirklich wichtig).
  • die Tatsache, dass der Generator für Elemente der obersten Ebene niemals null zurückgibt

Bisher funktioniert es sehr gut,

  • keine Notwendigkeit, Ihre Typen mit neuen Eigenschaften zu verschmutzen  
  • überhaupt keine Notwendigkeit, die Virtualisierung zu deaktivieren .
3
Aybe

Obwohl die Virtualisierung von XAML aus deaktiviert ist, denke ich, ist es besser, sie aus der .cs-Datei zu deaktivieren, die ContainerFromItem verwendet.

 VirtualizingStackPanel.SetIsVirtualizing(listBox, false);

Auf diese Weise reduzieren Sie die Kopplung zwischen XAML und Code. Sie vermeiden also das Risiko, dass jemand den Code durch Berühren der XAML-Datei beschädigt.

2
Benoit Blanchon

VirtualizingStackPanel.IsVirtualizing = "False" Macht das Steuerelement unscharf. Siehe untenstehende Implementierung. Das hilft mir, das gleiche Problem zu vermeiden. Setzen Sie Ihre Anwendung VirtualizingStackPanel.IsVirtualizing = "True" immer.

Ausführliche Informationen finden Sie unter 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;
}

Für alle, die immer noch Probleme damit haben, konnte ich dieses Problem umgehen, indem ich das erste Auswahländerungsereignis ignorierte und einen Aufruf verwendete, um den Aufruf grundsätzlich zu wiederholen. Folgendes habe ich am Ende getan:

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: Sie benötigen eigentlich nicht einmal den Thread-Dispatcher-Code. Sie können alles, was Sie benötigen, auf null setzen, um das erste Auswahländerungsereignis auszulösen, und das Ereignis dann wieder verlassen, damit zukünftige Ereignisse wie erwartet funktionieren.

        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
        }
1
Daniel Caban

Wahrscheinlich handelt es sich hierbei um ein Problem im Zusammenhang mit der Virtualisierung, sodass ListBoxItem-Container nur für derzeit sichtbare Elemente generiert werden (siehe https://msdn.Microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.) .110) .aspx # Anchor_9 )

Wenn Sie ListBox verwenden, würde ich vorschlagen, stattdessen zu ListView zu wechseln - es erbt von ListBox und unterstützt die ScrollIntoView()-Methode, die Sie zur Steuerung der Virtualisierung verwenden können.

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

(Das obige Beispiel verwendet auch die DoEvents() statische Methode, die hier näher erläutert wird; WPF, wie auf das Aktualisieren der Bindung gewartet wird, bevor mehr Code verarbeitet wird? )

Es gibt noch einige kleinere Unterschiede zwischen den ListBox- und ListView-Steuerelementen ( Was ist der Unterschied zwischen ListBox und ListView ) -, die sich nicht wesentlich auf Ihren Anwendungsfall auswirken sollten.

0
fvojvodic