2011-10-20 20 views
8

Necesito estilizar los elementos primero y último de una vista de lista de forma diferente. Para lograr eso, comencé a trabajar en una solución basada en esa respuesta: Use different template for last item in a WPF itemscontrolPlantilla de elemento distintivo para el primer y último elemento en un ListView

Básicamente, tengo un ItemsTemplateSelector personalizado que decide sobre la plantilla para aplicar en función del índice del elemento en los elementos de la vista de lista (código a continuación).

Funciona correctamente, excepto que cuando se actualiza la lista (se agrega o elimina un elemento), las plantillas no se seleccionan de nuevo (por ejemplo, inicialmente, se selecciona SingleItemTemplate porque hay un solo elemento. Cuando agrego un elemento a la lista, la plantilla de ese primer elemento no cambia a FirstItemTemplate). ¿Cómo forzar la selección de plantilla para todos los artículos?

public class FirstLastTemplateSelector : DataTemplateSelector 
{ 
    public DataTemplate DefaultTemplate { get; set; } 
    public DataTemplate FirstItemTemplate { get; set; } 
    public DataTemplate LastItemTemplate { get; set; } 
    public DataTemplate SingleItemTemplate { get; set; } 

    public override DataTemplate SelectTemplate(object item, DependencyObject container) 
    { 
     ListView lv = VisualTreeHelperEx.FindParentOfType<ListView>(container); 
     if (lv != null) 
     { 
      if (lv.Items.Count == 1) 
      { 
       return SingleItemTemplate; 
      } 

      int i = lv.Items.IndexOf(item); 
      if (i == 0) 
      { 
       return FirstItemTemplate; 
      } 
      else if (i == lv.Items.Count - 1) 
      { 
       return LastItemTemplate; 
      } 
     } 
     return DefaultTemplate; 
    } 
} 

Respuesta

14

Como un enfoque alternativo, sugeriría la unión AlternationCount de su ItemsControl al número de artículos en su colección (por ejemplo, la propiedad Count). Esto asignará a cada contenedor en su ItemsControl un único AlternationIndex (0, 1, 2, ... Count-1). Ve aquí para más información:

http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.alternationcount.aspx

Una vez que cada contenedor tiene una única AlternationIndex puede utilizar un DataTrigger en su contenedor Style para establecer el ItemTemplate con sede fuera del índice. Esto se puede hacer utilizando un MultiBinding con un convertidor que devuelve True si el índice es igual al recuento, False de lo contrario. Por supuesto, también podría construir un selector alrededor de este enfoque. Con la excepción del convertidor, este enfoque es bueno ya que es una solución única de XAML.

Un ejemplo utilizando un ListBox:

<Window x:Class="WpfApplication4.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" 
     xmlns:System="clr-namespace:System;assembly=mscorlib" 
     xmlns:l="clr-namespace:WpfApplication4" 
     Title="MainWindow" Height="350" Width="525"> 
    <Grid> 
     <Grid.Resources> 
      <Collections:ArrayList x:Key="MyCollection"> 
       <System:String>Item One</System:String> 
       <System:String>Item Two</System:String> 
       <System:String>Item Three</System:String> 
      </Collections:ArrayList> 

      <l:MyAlternationEqualityConverter x:Key="MyAlternationEqualityConverter" /> 

      <Style x:Key="MyListBoxItemStyle" TargetType="{x:Type ListBoxItem}"> 
       <Style.Triggers> 
        <DataTrigger Value="True"> 
         <DataTrigger.Binding> 
          <MultiBinding Converter="{StaticResource MyAlternationEqualityConverter}"> 
           <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="Items.Count" /> 
           <Binding RelativeSource="{RelativeSource Self}" Path="(ItemsControl.AlternationIndex)" /> 
          </MultiBinding> 
         </DataTrigger.Binding> 
         <!-- Could set the ItemTemplate instead --> 
         <Setter Property="Background" Value="Red"/> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 
     </Grid.Resources> 

     <ListBox ItemsSource="{Binding Source={StaticResource MyCollection}}" 
       AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}" 
       ItemContainerStyle="{StaticResource MyListBoxItemStyle}" /> 
    </Grid> 

Cuando el convertidor podría ser algo como:

class MyAlternationEqualityConverter : IMultiValueConverter 
{ 
    #region Implementation of IMultiValueConverter 

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (values != null && values.Length == 2 && 
      values[0] is int && values[1] is int) 
     { 
      return Equals((int) values[0], (int) values[1] + 1); 
     } 

     return DependencyProperty.UnsetValue; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotSupportedException(); 
    } 

    #endregion 
} 
+0

Me pregunto por qué no se ha comparado el uso de DataTrigger La unión a ItemsCount y Valor a AlternationCount? ¿Es porque ItemCount no es una propiedad de dependencia y arrojará un error? ¿O hay alguna otra razón? Como veo que va a ser un booleano igual a la comparación y por qué necesitamos un convertidor – CarbineCoder

+0

@ ramb00 Lo intenté y obtuve: "No se puede establecer un 'Enlace' en la propiedad 'Valor' de tipo 'DataTrigger'" . Parece que Value no es una dependenciaProp. –

Cuestiones relacionadas