Frage

Ich möchte sicherstellen, dass die Einzelteile in meinem ListBox korrekt in der Benutzeroberfläche angezeigt. Ich dachte, ein Weg, dies zu tun, ist durch alle Kinder des ListBox in der visuellen Struktur zu gehen, bekommen ihren Text, und dann vergleichen Sie das mit, was ich den Text erwarten zu sein.

Das Problem bei diesem Ansatz ist, dass intern ListBox eine VirtualizingStackPanel verwendet seine Elemente anzuzeigen, so dass nur die Elemente, die sichtbar sind, erstellt. Ich kam schließlich über die ItemContainerGenerator-Klasse, die wie es aussieht, sollte WPF zwingen, die Kontrollen in der visuellen Struktur für das angegebene Element zu erstellen. Leider ist das verursacht einige seltsame Seite für mich beeinflusst. Hier ist mein Code alle Elemente in der ListBox zu generieren:

List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
    bool isNewlyRealized;
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
    {
        isNewlyRealized = false;
        DependencyObject cntr = generator.GenerateNext(out isNewlyRealized);
        if(isNewlyRealized)
        {
            generator.PrepareItemContainer(cntr);
        }

        string itemText = GetControlText(cntr);
        generatedItems.Add(itemText);
    }
}

(Ich kann den Code für GetItemText() liefern, wenn Sie möchten, aber es fährt nur die visuelle Struktur, bis ein TextBlock gefunden wird. Ich weiß, dass ther gibt andere Möglichkeiten, um Text in einem Element zu haben, aber ich werde das in Ordnung bringen up, wenn ich Artikel Generation erhalten einwandfrei funktioniert.)

In meiner app, ItemsListBox enthält 20 Artikel, mit den ersten 12 Artikeln zunächst sichtbar. Der Text für die ersten 14 Artikel korrekt ist (wahrscheinlich, weil ihre Kontrollen bereits erzeugt worden ist). Doch für die Artikel 15 bis 20, ich bekomme keinen Text überhaupt. Darüber hinaus, wenn ich auf dem Boden des ItemsListBox bewegen, ist der Text von Artikel 15 bis 20 auch leer. So scheint es, als ob ich mit WPF normalen Mechanismus bin störende zur Erzeugung von Kontrollen, wie einige.

Was mache ich falsch? Gibt es eine andere / bessere Möglichkeit, die Elemente in einem ItemsControl zwingen zu der visuellen Struktur hinzugefügt werden?

Aktualisieren : Ich denke, dass ich gefunden habe, warum dies geschieht, obwohl ich weiß nicht, wie es zu beheben. Meine Vermutung, dass der Anruf alle erforderlichen Kontrollen erzeugen würde PrepareItemContainer() das Elemente anzuzeigen, und dann den Behälter mit der visuellen Struktur an der richtigen Stelle hinzufügen. Es stellt sich heraus, dass es nicht eine dieser beiden Dinge tut. Der Behälter ist mit dem ItemsControl nicht hinzugefügt, bis ich nach unten scrollen, um es zu sehen, und zu diesem Zeitpunkt nur der Behälter selbst (dh ListBoxItem) erstellt - seine Kinder werden nicht erstellt (es sollen ein paar Kontrollen hier hinzugefügt werden, von denen ein sein sollte, die TextBlock, die den Text des Elements angezeigt wird).

Wenn ich durchquere die visuelle Struktur der Kontrolle, die ich bestehen die Ergebnisse die gleichen sind PrepareItemContainer(). In beiden Fällen wird nur die ListBoxItem geschaffen, und keines seiner Kinder geschaffen werden.

Ich kann keinen guten Weg finden, die ListBoxItem zu der visuellen Struktur hinzuzufügen. Ich fand die VirtualizingStackPanel in der visuellen Struktur, aber seine Children.Add() führt zu einem InvalidOperationException Aufruf (können keine Elemente direkt mit dem ItemPanel hinzufügen, da es Elemente für seine ItemsControl erzeugt). So wie ein Test, habe ich versucht, seine AddVisualChild() Aufruf Reflexion mit (da es geschützt ist), aber das hat nicht funktioniert, auch nicht.

War es hilfreich?

Lösung 3

Ich glaube, ich herausgefunden, wie dies zu tun. Das Problem war, dass die erzeugten Elemente der visuellen Struktur nicht hinzugefügt wurden. Nach einiger Suche, mit dem Beste was ich tun konnte, einige geschützten Methoden des VirtualizingStackPanel im ListBox zu nennen. Dies ist zwar nicht ideal, da es nur zum Testen ist ich glaube, ich werde damit zu leben haben.

Das ist was für mich gearbeitet:

VirtualizingStackPanel itemsPanel = null;
FrameworkElementFactory factory = control.ItemsPanel.VisualTree;
if(null != factory)
{
    // This method traverses the visual tree, searching for a control of
    // the specified type and name.
    itemsPanel = FindNamedDescendantOfType(control,
        factory.Type, null) as VirtualizingStackPanel;
}

List<string> generatedItems = new List<string>();
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1);
using(generator.StartAt(pos, GeneratorDirection.Forward))
{
    bool isNewlyRealized;
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++)
    {
        isNewlyRealized = false;
        UIElement cntr = generator.GenerateNext(out isNewlyRealized) as UIElement;
        if(isNewlyRealized)
        {
            if(i >= itemsPanel.Children.Count)
            {
                itemsPanel.GetType().InvokeMember("AddInternalChild",
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
                    Type.DefaultBinder, itemsPanel,
                    new object[] { cntr });
            }
            else
            {
                itemsPanel.GetType().InvokeMember("InsertInternalChild",
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember,
                    Type.DefaultBinder, itemsPanel,
                    new object[] { i, cntr });
            }

            generator.PrepareItemContainer(cntr);
        }

        string itemText = GetControlText(cntr);
        generatedItems.Add(itemText);
    }
}

Andere Tipps

Nur schnell suchen, wenn die ListBox VirtualizingStackPanel verwendet - vielleicht wird es genug sein, es mit Stackpanel zu ersetzen wie

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
      <StackPanel/>
  <ItemsPanelTemplate>
<ListBox.ItemsPanel>

Sie können über das der falsche Weg gehen. Was ich tat, ist das Loaded-Ereignis von hook up [den Inhalt] meine Datatemplate:

<DataTemplate DataType="{x:Type local:ProjectPersona}">
  <Grid Loaded="Row_Loaded">
    <!-- ... -->
  </Grid>
</DataTemplate>

... und dann verarbeitet die neu angezeigt Zeile in den Event-Handler:

private void Row_Loaded(object sender, RoutedEventArgs e)
{
    Grid grid = (Grid)sender;
    Carousel c = (Carousel)grid.FindName("carousel");
    ProjectPersona project = (ProjectPersona)grid.DataContext;
    if (project.SelectedTime != null)
        c.ScrollItemIntoView(project.SelectedTime);
}

Dieser Ansatz funktioniert die Initialisierung / Überprüfung der Reihe, wenn es zum ersten Mal angezeigt wird, so wird es alle Zeilen nicht up-front. Wenn Sie damit leben können, dann vielleicht ist dies die elegantere Methode.

Die Lösung von Andy ist eine sehr gute Idee, aber unvollständig. Beispielsweise sind die ersten 5 Behälter erzeugt und in dem Panel. Die Liste hat 300> Gegenstände. Ich bitte die letzten Behälter, mit dieser Logik ADD. Dann fordere ich den letzten Index - 1 Behälter, mit diesem Logis ADD! Das ist das Problem. Die Reihenfolge der Kinder, die innerhalb der Platte ist nicht gültig.

Eine Lösung für diese:

    private FrameworkElement GetContainerForIndex(int index)
    {
        if (ItemsControl == null)
        {
            return null;
        }

        var container = ItemsControl.ItemContainerGenerator.ContainerFromIndex(index -1);
        if (container != null && container != DependencyProperty.UnsetValue)
        {
            return container as FrameworkElement;
        }
        else
        {

            var virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);
            if (virtualizingPanel == null)
            {
                // do something to load the (perhaps currently unloaded panel) once
            }
            virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl);

            IItemContainerGenerator generator = ItemsControl.ItemContainerGenerator;
            using (generator.StartAt(generator.GeneratorPositionFromIndex(index), GeneratorDirection.Forward))
            {
                bool isNewlyRealized = false;
                container = generator.GenerateNext(out isNewlyRealized);
                if (isNewlyRealized)
                {
                    generator.PrepareItemContainer(container);
                    bool insert = false;
                    int pos = 0;
                    for (pos = virtualizingPanel.Children.Count - 1; pos >= 0; pos--)
                    {
                        var idx = ItemsControl.ItemContainerGenerator.IndexFromContainer(virtualizingPanel.Children[pos]);
                        if (!insert && idx < index)
                        {
                            ////Add
                            virtualizingPanel.GetType().InvokeMember("AddInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { container });
                            break;
                        }
                        else
                        {
                            insert = true;
                            if (insert && idx < index)
                            {
                                break;
                            }
                        }
                    }

                    if (insert)
                    {
                        virtualizingPanel.GetType().InvokeMember("InsertInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { pos + 1, container });
                    }
                }

                return container as FrameworkElement;
            }
        }
    }

Für alle anderen über diese fragen, in Andys Fall vielleicht die VirtualizingStackPanel Auslagern mit einem normalen Stackpanel würde hier die beste Lösung sein.

Der Grund Aufruf PrepareItemContainer auf dem ItemContainerGenerator nicht arbeitet, dass ein Element in der visuellen Struktur muss für PrepareItemContainer arbeiten. Mit einem VirtualizingStackPanel, wird das Element nicht als visuelles Kind der Platte eingestellt werden, bis die VirtualizingStackPanel feststellt, dass es / ist etwa auf dem Bildschirm sein.

Eine andere Lösung (die ich verwende) ist Ihre eigene VirtualizingPanel zu erstellen, so können Sie steuern, wenn Elemente der visuellen Struktur hinzugefügt werden.

In meinem Fall fand ich, dass UpdateLayout() Aufruf auf dem ItemsControl (ListBox, ListView, etc.) begann seinen ItemContainerGenerator oben, so dass der Status von „NotStarted“ geändert des Generators auf „GeneratingContainers“ und null Behälter waren nicht mehr zu sein zurückgegeben durch ItemContainerGenerator.ContainerFromItem und / oder ItemContainerGenerator.ContainerFromIndex.

Zum Beispiel:

    public static bool FocusSelectedItem(this ListBox listbox)
    {
        int ix;
        if ((ix = listbox.SelectedIndex) < 0)
            return false;

        var icg = listbox.ItemContainerGenerator;
        if (icg.Status == GeneratorStatus.NotStarted)
            listbox.UpdateLayout();

        var el = (UIElement)icg.ContainerFromIndex(ix);
        if (el == null)
            return false;

        listbox.ScrollIntoView(el);

        return el == Keyboard.Focus(el);
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top