c# - WPF: vuelva a aplicar DataTemplateSelector cuando un cierto valor cambia
binding (4)
Con respecto a su EDITAR, ¿no sería suficiente un desencadenador DataTemplate 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>
Así que aquí está el XAML que tengo:
<ItemsControl ItemsSource="{Binding Path=Groups}" ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/>
Aquí está 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 al ListTemplateSelector dentro de sí mismo, por lo que esta es la razón por la que lo he configurado como lo he configurado. Es el único truco recursivo que podría 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 plantillas no se vuelve a aplicar. Ahora, podría usar los desencadenantes para enlazar con 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, digamos que tengo una lista de grupos como este:
Grupo 1: IsLeaf = false, entonces template = GroupTemplate
Grupo 2: IsLeaf = true, entonces template = ItemTemplate
Grupo 3: IsLeaf = false, entonces template = GroupTemplate
Y una vez que la propiedad IsLeaf del grupo 1 cambia a verdadero, la plantilla debe cambiar automáticamente a ItemTemplate.
EDITAR:
Aquí está mi solución temporal. ¿Alguna mejor manera 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>
Encontré esta solución que me parece más fácil. Desde dentro de TemplateSelector, escuche la propiedad que le interesa y luego vuelva a aplicar el selector de plantillas 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());
}
}
}
Lo hago con un proxy vinculante.
Funciona como un proxy de enlace normal (pero con 2 accesorios: copia datos de DataIn a DataOut), pero establece el DataOut en NULL y vuelve al valor de DataIn cuando el valor de Trigger cambia:
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;
}
}
Úsalo así:
<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>
Por lo tanto, no se enlaza directamente con su DataContext, sino con el DataOut de BindingProxy, que refleja el DataContext original, pero con una pequeña diferencia: cuando el activador cambia (en este ejemplo, un valor bool dentro del "Elemento"), el selector de plantilla obtiene reactivado
No tienes que cambiar tu TemplateSelector para esto.
También es posible agregar más disparadores, solo agrega un disparador2.
Volviendo a su solución original y el problema de "el selector de plantilla no se vuelve a aplicar": puede actualizar su vista de esa manera
CollectionViewSource.GetDefaultView(YourItemsControl.ItemsSource).Refresh();
donde, por 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 cómo elegir el lugar correcto en su proyecto para esta instrucción de actualización. Podría entrar en una vista de código subyacente o, si su IsLeaf es un DP, el lugar correcto sería una devolución de llamada de la propiedad de dependencia cambiada.