25 votos

¿Cómo funciona la ItemContainerGenerator.ContainerFromItem con una lista agrupada?

Tengo un ListBox que hasta hace poco era mostrar una lista plana de elementos. Yo era capaz de utilizar milista.ItemContainerGenerator.ConainerFromItem(cosa) para recuperar el ListBoxItem hosting "cosa" en la lista.

Esta semana he modificado el cuadro de lista un poco en esa CollectionViewSource que se une a los de sus elementos ha agrupación habilitado. Ahora los elementos del ListBox se agrupan debajo de niza encabezados.

Sin embargo, ya que haciendo esto, ItemContainerGenerator.ContainerFromItem ha dejado de funcionar - devuelve null incluso para los artículos sé que están en el cuadro de lista. Heck - ContainerFromIndex(0) devuelve null incluso cuando el cuadro de lista se rellena con muchos artículos!

¿Cómo puedo recuperar un ListBoxItem de un ListBox que la visualización de elementos agrupados?

Edit: Aquí está el XAML y el código detrás de una reducida ejemplo. Esto plantea una excepción NullReferenceException porque ContainerFromIndex(1) devuelve null aunque hay cuatro elementos en la lista.

XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    Title="Window1">

    <Window.Resources>
        <XmlDataProvider x:Key="myTasks" XPath="Tasks/Task">
            <x:XData>
                <Tasks xmlns="">
                    <Task Name="Groceries" Type="Home"/>
                    <Task Name="Cleaning" Type="Home"/>
                    <Task Name="Coding" Type="Work"/>
                    <Task Name="Meetings" Type="Work"/>
                </Tasks>
            </x:XData>
        </XmlDataProvider>

        <CollectionViewSource x:Key="mySortedTasks" Source="{StaticResource myTasks}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="@Type" />
                <scm:SortDescription PropertyName="@Name" />
            </CollectionViewSource.SortDescriptions>

            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Type" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>

    <ListBox 
        x:Name="listBox1" 
        ItemsSource="{Binding Source={StaticResource mySortedTasks}}" 
        DisplayMemberPath="@Name"
        >
        <ListBox.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListBox.GroupStyle>
    </ListBox>
</Window>

CS:

public Window1()
{
    InitializeComponent();
    listBox1.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
    if (listBox1.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        listBox1.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;

        var i = listBox1.ItemContainerGenerator.ContainerFromIndex(1) as ListBoxItem;

        // select and keyboard-focus the second item
        i.IsSelected = true;
        i.Focus();
    }
}

32voto

David Schmitt Puntos 29384

Usted tiene para escuchar y reaccionar a la ItemsGenerator.StatusChanged Evento y esperar hasta que el ItemContainers se generan antes de que usted pueda acceder a ellos con ContainerFromElement.


Buscar más, he encontrado un hilo en el foro MSDN de alguien que tiene el mismo problema. Esto parece ser un error en WPF, cuando uno tiene una GroupStyle conjunto. La solución es montar en barca en el acceso de la ItemGenerator después de que el proceso de renderizado. A continuación está el código de la pregunta. He intentado esto, y funciona:

    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        if (listBox1.ItemContainerGenerator.Status
            == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
        {
            listBox1.ItemContainerGenerator.StatusChanged
                -= ItemContainerGenerator_StatusChanged;
            Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
                new Action(DelayedAction));
        }
    }

    void DelayedAction()
    {
        var i = listBox1.ItemContainerGenerator.ContainerFromIndex(1) as ListBoxItem;

        // select and keyboard-focus the second item
        i.IsSelected = true;
        i.Focus();
    }

0voto

Jobi Joy Puntos 20883

Tratar de analizar el VisualTree encima de la 'cosa' hasta llegar a un tipo de ListBoxItem

0voto

c_wiz_kid Puntos 81

Si el código anterior no funciona para usted, pruebe esto

public class ListBoxExtenders : DependencyObject
{
    public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

    public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
    }

    public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToSelectedItemProperty, value);
    }

    public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
    {
        var listBox = s as ListBox;
        if (listBox != null)
        {
            var listBoxItems = listBox.Items;
            if (listBoxItems != null)
            {
                var newValue = (bool)e.NewValue;

                var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));

                if (newValue)
                    listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
                else
                    listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
            }
        }
    }

    public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
    {
        if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
            listBox.ScrollIntoView(listBox.Items[index]);
    }

}

Uso de XAML

<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X