2010-05-11 13 views
14

Tengo un problema bastante complicado:Cómo guardar el estado IsExpanded en encabezados de grupo de una vista de lista

estoy usando un control ListView con el ItemsSource establece en un CollectionViewSource incluyendo un PropertyGroupDescription agrupar los elementos de lista. El CollectionViewSource se ve así:

<CollectionViewSource x:Key="ListViewObjects"> 
    <CollectionViewSource.Source> 
     <Binding Path="CurrentListViewData"/> 
    </CollectionViewSource.Source> 
    <CollectionViewSource.GroupDescriptions> 
     <PropertyGroupDescription PropertyName="ObjectType" /> 
    </CollectionViewSource.GroupDescriptions> 
</CollectionViewSource> 

En el ListView utilizo personalizar los encabezados de grupo como éste:

<ListView.GroupStyle> 
    <GroupStyle> 
     <GroupStyle.ContainerStyle> 
     <Style TargetType="{x:Type GroupItem}"> 
      <Setter Property="Margin" Value="5"/> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type GroupItem}"> 
        <Expander IsExpanded="True"> 
         <Expander.Header> 
          <DockPanel> 
           <TextBlock Text="{Binding Path=Items[0].ObjectType /> 
          </DockPanel> 
         </Expander.Header> 
         <Expander.Content> 
          <ItemsPresenter /> 
         </Expander.Content> 
        </Expander> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     </GroupStyle.ContainerStyle> 
    </GroupStyle> 
</ListView.GroupStyle> 

Como se puede ver la propiedad IsExpanded del expansor se establece en true. Esto significa que cada vez que se actualiza el ListView, todos los controles del expansor se expanden.

Sin embargo, quiero guardar el último estado de cada expansor. No he podido encontrar una forma de guardar una lista de estados de expansión por tipo de objeto. Estaba experimentando con un HashTable encuadernado y un convertidor, pero no proporcioné ObjectType como ConverterParameter, porque siempre pasaba como una cadena. Pero esa puede no ser la solución de todos modos.

¿Alguien puede darme una pista o una idea para una solución, por favor? :)

+0

¿Seguro que su verdadero problema no es que a su diseño actuales fuerzas de actualizar la vista de lista? –

+0

ListView se actualiza llamando a NotifyPropertyChanged ("CurrentListViewData"). No sabría cómo hacerlo. –

Respuesta

10

Se puede crear una nueva clase con un diccionario (por ejemplo, ObjectType como una clave a bool valores), y darle un indexador:

Dictionary<ObjectType, bool> expandStates = new Dictionary<ObjectType, bool>(); 

    public bool this[ObjectType key] 
    { 
     get 
     { 
      if (!expandStates.ContainsKey(key)) return false; 
      return expandStates[key]; 
     } 
     set 
     { 
      expandStates[key] = value; 
     } 
    } 

A continuación, crear una instancia en un ResourceDictionary en algún lugar y se unen la IsExpanded de esta manera:

<Expander IsExpanded="{Binding Source={StaticResource myExpMgr}, Path=[Items[0].ObjectType]}"> 

Eso bien podría hacerlo: una buena manera de conseguir WPF para llamar a su código y pasar un parámetro solo cuando lo necesite. (Que WPF le deje poner subexpresiones en indexadores en una ruta de enlace fue una novedad para mí, ¡pero no es así!)

+0

Debo añadir que, en lugar de utilizar NotifyPropertyChanged(), podría vincularse a un objeto que implemente INotifyCollectionChanged (http://msdn.microsoft.com/en-us/library/system.collections.specialized.inotifycollectionchanged.aspx), o use una ObservableCollection. ¡Sin embargo, no estoy seguro de que no cause los mismos problemas! –

+0

¡Salsa impresionante! ¡Me gusta esto! ¡Gracias! Voy a usarlo para un TreeView para lo mismo. La mejor parte es que puedes almacenar mucho más que solo un IsExpanded. En realidad, voy a usarlo para almacenar la información de Viewmodel con respecto a los nodos creados automáticamente que se basan en la agrupación. – MarqueIV

+1

Aunque sería bueno si esto funcionara, desafortunadamente no, por lo que yo sé. Al menos, no en .NET 4.5. La sub-expresión en el indexador se pasa como una cadena al indexador y no se evalúa. – Mark

11

La respuesta aceptada es incorrecta, como se explica en los comentarios. Escribí el siguiente comportamiento que logra la funcionalidad deseada:

public class PersistGroupExpandedStateBehavior : Behavior<Expander> 
{ 
    #region Static Fields 

    public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register(
     "GroupName", 
     typeof(object), 
     typeof(PersistGroupExpandedStateBehavior), 
     new PropertyMetadata(default(object))); 

    private static readonly DependencyProperty ExpandedStateStoreProperty = 
     DependencyProperty.RegisterAttached(
      "ExpandedStateStore", 
      typeof(IDictionary<object, bool>), 
      typeof(PersistGroupExpandedStateBehavior), 
      new PropertyMetadata(default(IDictionary<object, bool>))); 

    #endregion 

    #region Public Properties 

    public object GroupName 
    { 
     get 
     { 
      return (object)this.GetValue(GroupNameProperty); 
     } 

     set 
     { 
      this.SetValue(GroupNameProperty, value); 
     } 
    } 

    #endregion 

    #region Methods 

    protected override void OnAttached() 
    { 
     base.OnAttached(); 

     bool? expanded = this.GetExpandedState(); 

     if (expanded != null) 
     { 
      this.AssociatedObject.IsExpanded = expanded.Value; 
     } 

     this.AssociatedObject.Expanded += this.OnExpanded; 
     this.AssociatedObject.Collapsed += this.OnCollapsed; 
    } 

    protected override void OnDetaching() 
    { 
     this.AssociatedObject.Expanded -= this.OnExpanded; 
     this.AssociatedObject.Collapsed -= this.OnCollapsed; 

     base.OnDetaching(); 
    } 

    private ItemsControl FindItemsControl() 
    { 
     DependencyObject current = this.AssociatedObject; 

     while (current != null && !(current is ItemsControl)) 
     { 
      current = VisualTreeHelper.GetParent(current); 
     } 

     if (current == null) 
     { 
      return null; 
     } 

     return current as ItemsControl; 
    } 

    private bool? GetExpandedState() 
    { 
     var dict = this.GetExpandedStateStore(); 

     if (!dict.ContainsKey(this.GroupName)) 
     { 
      return null; 
     } 

     return dict[this.GroupName]; 
    } 

    private IDictionary<object, bool> GetExpandedStateStore() 
    { 
     ItemsControl itemsControl = this.FindItemsControl(); 

     if (itemsControl == null) 
     { 
      throw new Exception(
       "Behavior needs to be attached to an Expander that is contained inside an ItemsControl"); 
     } 

     var dict = (IDictionary<object, bool>)itemsControl.GetValue(ExpandedStateStoreProperty); 

     if (dict == null) 
     { 
      dict = new Dictionary<object, bool>(); 
      itemsControl.SetValue(ExpandedStateStoreProperty, dict); 
     } 

     return dict; 
    } 

    private void OnCollapsed(object sender, RoutedEventArgs e) 
    { 
     this.SetExpanded(false); 
    } 

    private void OnExpanded(object sender, RoutedEventArgs e) 
    { 
     this.SetExpanded(true); 
    } 

    private void SetExpanded(bool expanded) 
    { 
     var dict = this.GetExpandedStateStore(); 

     dict[this.GroupName] = expanded; 
    } 

    #endregion 
} 

Se fija un diccionario a la ItemsControl que contiene lo que ahorra el estado expandido para cada elemento de grupo. Esto será permanente, incluso si los elementos en el control cambian.

Uso:

<Expander> 
    <i:Interaction.Behaviors> 
     <behaviors:PersistGroupExpandedStateBehavior GroupName="{Binding Name}" /> 
    </i:Interaction.Behaviors> 
    ... 
</Expander> 
+1

re: i: Interaction.Behavior - see: http://stackoverflow.com/questions/18778490/i-interaction-behavior-option-is-not- coming-for-applying-beahviour y también http://stackoverflow.com/questions/3059821/the-tag-interaction-behaviors-does-not-exist-in-vs2010-blend-3 –

+0

gracias por eso! solución elegante (me encanta)! – CaptainPlanet

+0

Esto funcionó bien, si por casualidad el GroupName es nulo, arrojará un error. ¿Modifiqué mi propio comportamiento para ser GroupName? string.Empty en los lugares que se estaba utilizando. Este fue un muy buen complemento sin embargo. ¡Gracias! – TravisWhidden

2

La respuesta marcada no funciona en .Net 4.5.

Una solución simple para esto es añadir una propiedad

private bool _isExpanded = true; 
    public bool IsExpanded 
    { 
     get { return _isExpanded; } 
     set { _isExpanded = value; } 
    } 

a su modelo de vista (en este caso cualquier objeto CurrentListViewData bodegas)

y luego hacer:

<Expander IsExpanded="{Binding Items[0].IsExpanded}"> 

en su modelo.

simple y eficaz al igual que WPF debe haber

+0

¿Entiendo correctamente que desea guardar la propiedad "IsExpanded" que pertenece a la lista misma en el primer elemento de la lista? ¿Qué ocurre si la lista está vacía o se elimina el primer elemento? – Woozar

Cuestiones relacionadas