2010-08-22 15 views
24

Así que aquí está el XAML que tengo:WPF: Volver a aplicar DataTemplateSelector cuando un cierto valor cambia

<ItemsControl ItemsSource="{Binding Path=Groups}" ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/> 

Aquí es mi clase ListTemplateSelector:

public class ListTemplateSelector : DataTemplateSelector { 
public DataTemplate GroupTemplate { get; set; } 
public DataTemplate ItemTemplate { get; set; } 
public override DataTemplate SelectTemplate(object item, DependencyObject container) { 
    GroupList<Person> list = item as GroupList<Person>; 
    if (list != null && !list.IsLeaf) 
     return GroupTemplate; 
    return ItemTemplate; 
} 
} 

La plantilla de datos GroupTemplate hace referencia a la ListTemplateSelector dentro de la propia , así que esta es la razón por la que configuré como lo tengo configurado. Es el único truco recursivo que pude armar. Pero ese no es el problema que estoy teniendo.

Mi problema es que quiero cambiar de ItemTemplate a GroupTemplate cuando la propiedad IsLeaf cambia. Esto funciona maravillosamente la primera vez, ya que lee la propiedad la primera vez. Pero una vez que esta propiedad cambia, el selector de plantilla no se vuelve a aplicar. Ahora, podría usar activadores para vincular el valor y establecer la plantilla del elemento de forma adecuada, pero necesito poder establecer una plantilla diferente para cada elemento, ya que podrían estar en un estado diferente.

Por ejemplo, decir que tengo una lista de grupos de la siguiente manera:

Grupo 1: isLeaf = false, por lo que la plantilla = GroupTemplate

Grupo 2: isLeaf = true, por lo que la plantilla = ItemTemplate

grupo 3: isLeaf = false, por lo que la plantilla = GroupTemplate

Y una vez isLeaf cambios en las propiedades del grupo de 1 a verdadero, el templat e necesita cambiar automáticamente a ItemTemplate.

EDITAR:

Aquí está mi solución temporal. ¿Alguna mejor forma de hacerlo?

<ItemsControl ItemsSource="{Binding Path=Groups}"> 
<ItemsControl.ItemTemplate> 
    <DataTemplate> 
     <ContentControl Content="{Binding}"> 
      <ContentControl.Style> 
       <Style TargetType="{x:Type ContentControl}"> 
        <Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplate}"/> 
        <Style.Triggers> 
         <DataTrigger Binding="{Binding Path=IsLeaf}" Value="False"> 
          <Setter Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/> 
         </DataTrigger> 
        </Style.Triggers> 
       </Style> 
      </ContentControl.Style> 
     </ContentControl> 
    </DataTemplate> 
</ItemsControl.ItemTemplate> 
</ItemsControl> 
+2

Para mayor claridad, ¿descartó el enfoque de DataTemplateSelector a favor de desencadenantes, o trabajó los factores desencadenantes en la solución con el DataTemplateSelector también? – alastairs

+0

@alastairs No puedo hablar por OP, pero los desencadenadores parecen hacer innecesario el DataTemplateSelector. – piedar

Respuesta

16

En cuanto a su EDIT, ¿no bastaría un DataTemplate Trigger en lugar de usar un estilo? Es decir:

<ItemsControl ItemsSource="{Binding Path=Groups}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
      <ContentControl x:Name="cc" Content="{Binding}" ContentTemplate="{DynamicResource ItemTemplate}"/> 

      <DataTemplate.Triggers> 
       <DataTrigger Binding="{Binding Path=IsLeaf}" Value="False"> 
        <Setter TargetName="cc" Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/> 
       </DataTrigger> 
      </DataTemplate.Triggers> 

     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl> 
+0

Sí, eso sería mejor. Me olvidé de los disparadores de DataTemplate. Usaré esto como mi solución, ¡así que gracias! – Nick

+9

DataTemplateSelectors necesita mejorarse para que permitan este tipo de escenario porque esta solución, aunque necesaria, es una sintaxis mucho más fea. Con suerte, el equipo de WPF abordará esto – Xcalibur

21

Encontré esta solución que me parece más fácil. Desde TemplateSelector, escuche la propiedad que le interesa y luego vuelva a aplicar el selector de plantilla para forzar una actualización.

public class DataSourceTemplateSelector : DataTemplateSelector 
{ 

    public DataTemplate IA { get; set; } 
    public DataTemplate Dispatcher { get; set; } 
    public DataTemplate Sql { get; set; } 

    public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) 
    { 
     var ds = item as DataLocationViewModel; 
     if (ds == null) 
     { 
      return base.SelectTemplate(item, container); 
     } 
     PropertyChangedEventHandler lambda = null; 
     lambda = (o, args) => 
      { 
       if (args.PropertyName == "SelectedDataSourceType") 
       { 
        ds.PropertyChanged -= lambda; 
        var cp = (ContentPresenter)container; 
        cp.ContentTemplateSelector = null; 
        cp.ContentTemplateSelector = this;       
       } 
      }; 
     ds.PropertyChanged += lambda; 

     switch (ds.SelectedDataSourceType.Value) 
     { 
      case DataSourceType.Dispatcher: 
       return Dispatcher; 
      case DataSourceType.IA: 
       return IA; 
      case DataSourceType.Sql: 
       return Sql; 
      default: 
       throw new NotImplementedException(ds.SelectedDataSourceType.Value.ToString()); 
     } 
    } 


} 
+0

¡Esto funcionó perfectamente! ¡Lo mejor de todas las soluciones para esta característica que falta en WPF! – Vaccano

+1

Cuidado con este código - investigaciones después de implementar esto como una solución a mi propia situación de cambio de plantilla y notando una caída en el rendimiento descubrió una pérdida de memoria masiva debido al tamaño de DataTemplate involucrado al cambiar - una idea mucho mejor para usar el método DataTriggers que no parece filtrarse en absoluto. – toadflakz

+0

Ha pasado mucho tiempo, pero tuve que implementar esta solución en una aplicación universal, ya que WinRT no tiene Style.Triggers ... –

2

Volviendo de nuevo a su solución original y el problema de "el selector de plantilla no consigue volver a aplicar": se puede refrescar la vista como la

CollectionViewSource.GetDefaultView(YourItemsControl.ItemsSource).Refresh(); 

donde por causa brevedad se hace referencia a su ItemsControl por su nombre ("YourItemsControl") agregado a su XAML:

<ItemsControl x:Name="YourItemsControl" ItemsSource="{Binding Path=Groups}" 
ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/> 

El único problema puede ser la forma de elegir el lugar correcto en su proyecto para esta instrucción de actualización. Podría entrar en un código subyacente de la vista o, si su IsLeaf es un DP, el lugar correcto sería una devolución de llamada con propiedad de dependencia.

0

Lo hago con un proxy de enlace.

Funciona como un proxy unión normal (pero con 2 Atrezzo - copia datos de DataIn a DATAOUT), pero establece el DATAOUT en NULL y de nuevo al valor DataIn cada vez que cambia el valor de disparo:

public class BindingProxyForTemplateSelector : Freezable 
{ 
    #region Overrides of Freezable 

    protected override Freezable CreateInstanceCore() 
    { 
     return new BindingProxyForTemplateSelector(); 
    } 

    #endregion 

    public object DataIn 
    { 
     get { return (object)GetValue(DataInProperty); } 
     set { SetValue(DataInProperty, value); } 
    } 

    public object DataOut 
    { 
     get { return (object) GetValue(DataOutProperty); } 
     set { SetValue(DataOutProperty, value); } 
    } 

    public object Trigger 
    { 
     get { return (object) GetValue(TriggerProperty); } 
     set { SetValue(TriggerProperty, value); } 
    } 


    public static readonly DependencyProperty TriggerProperty = DependencyProperty.Register(nameof(Trigger), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object), OnTriggerValueChanged)); 

    public static readonly DependencyProperty DataInProperty = DependencyProperty.Register(nameof(DataIn), typeof(object), typeof(BindingProxyForTemplateSelector), new UIPropertyMetadata(null, OnDataChanged)); 

    public static readonly DependencyProperty DataOutProperty = DependencyProperty.Register(nameof(DataOut), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object))); 



    private static void OnTriggerValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     // this does the whole trick 

     var sender = d as BindingProxyForTemplateSelector; 
     if (sender == null) 
      return; 

     sender.DataOut = null; // set to null and then back triggers the TemplateSelector to search for a new template 
     sender.DataOut = sender.DataIn; 
    } 



    private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var sender = d as BindingProxyForTemplateSelector; 
     if (sender == null) 
      return; 

     sender.DataOut = e.NewValue; 
    } 

} 

utilizar de esta manera:

<Grid> 
    <Grid.Resources> 
     <local:BindingProxyForTemplateSelector DataIn="{Binding}" Trigger="{Binding Item.SomeBool}" x:Key="BindingProxy"/> 
    </Grid.Resources> 
    <ContentControl Content="{Binding Source={StaticResource BindingProxy}, Path=DataOut.Item}" ContentTemplateSelector="{StaticResource TemplateSelector}"/> 
</Grid> 

Así que no se unen a su DataContext directamente, sino a DATAOUT del BindingProxy, que refleja la DataContext original, pero con una pequeña diferencia: Cuando los cambios de disparo (en este ejemplo, una valor bool dentro del 'Artículo'), el TemplateSelector se reactiva.

No necesita cambiar su TemplateSelector para esto.

También es posible agregar más Triggers, solo agregue un Trigger2.

Cuestiones relacionadas