2008-10-03 20 views
26

Tengo un ListBox que hasta hace poco mostraba una lista plana de elementos. Pude usar myList.ItemContainerGenerator.ConainerFromItem (thing) para recuperar el "elemento" hosting de ListBoxItem en la lista.¿Cómo funciona ItemContainerGenerator.ContainerFromItem con una lista agrupada?

Esta semana modifiqué ligeramente el ListBox porque CollectionViewSource al que se une para sus elementos tiene habilitada la agrupación. Ahora los elementos dentro de ListBox están agrupados debajo de buenos encabezados.

Sin embargo, desde hacer esto, ItemContainerGenerator.ContainerFromItem ha dejado de funcionar, devuelve nulo incluso para los elementos que sé que están en el ListBox. Heck: ContainerFromIndex (0) devuelve null incluso cuando ListBox está lleno de muchos elementos.

¿Cómo recupero un ListBoxItem de un ListBox que muestra elementos agrupados?

Editar: Aquí está el XAML y el código subyacente para un ejemplo recortado. Esto genera una 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(); 
    } 
} 
+0

¿Qué haces con el contenedor? ¿Se puede explicar lo que hizo el código anterior que realmente funcionó? Hay algunas formas de obtener el contenedor ... ¿Depende de lo que quieras hacer con él? – rudigrobler

Respuesta

35

Usted tiene para escuchar y reaccionar al evento ItemsGenerator.StatusChanged y espere hasta que se generen los contenedores de elementos para poder acceder a ellos con ContainerFromElement.


Y buscando más, he encontrado a thread in the MSDN forum de alguien que tiene el mismo problema. Esto parece ser un error en WPF, cuando uno tiene un conjunto GroupStyle. La solución es cancelar el acceso de ItemGenerator después del proceso de renderizado. Debajo está el código para tu 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(); 
    } 
+0

Sí, estoy haciendo eso. Actualizaré la pregunta la próxima semana con algún código. –

+0

Acabo de probarlo yo mismo.Me ayuda en algo que tuve problemas con este fin de semana. –

+0

Impresionante - gracias David. No he tenido la oportunidad de probar esto todavía, pero lo aceptaré como "la" respuesta por ahora. –

0

Intenta analizar el VisualTree arriba de la 'cosa' hasta llegar a un tipo ListBoxItem

+0

Jobi - "cosa" en este caso no es un elemento visual; es una instancia de un objeto comercial. Parte de un IList al que está obligado el ItemsSource de ListBox. ¿Puedo hacer lo que describes bajo ese escenario? –

+0

Sí, te tengo. gracias por la aclaración. ¿De qué parte del código detrás de ti estás tratando de hacer esto? cualquier clic de DataTemplate o en algún otro controlador de eventos? –

+0

Cuando se carga la página (es una aplicación de estilo de navegación), deseo establecer el foco del teclado en un elemento específico. Entonces, en el controlador de eventos Loaded. –

0

Si el código anterior no funciona para usted, le daría prueba

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 en XAML

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