2010-08-22 2 views
24

Так вот XAML, что у меня есть:WPF: Повторно DataTemplateSelector, когда определенное значение изменяется

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

Вот мой 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; 
} 
} 

Шаблон данных GroupTemplate ссылается на ListTemplateSelector внутри себя , поэтому именно поэтому я настроен так, как будто у меня он настроен. Это единственный рекурсивный хак, который я мог бы собрать вместе. Но это не проблема, которую я испытываю.

Моя проблема в том, что я хочу изменить с ItemTemplate на GroupTemplate, когда изменяется свойство IsLeaf. Это прекрасно работает в первый раз, так как он читает свойство в первый раз. Но как только это свойство изменится, селектор шаблонов не будет повторно применен. Теперь я мог бы использовать триггеры для привязки к значению и правильно установить шаблон элемента, но мне нужно иметь возможность устанавливать другой шаблон для каждого элемента, так как они могут находиться в другом состоянии.

Например, скажем, у меня есть список групп, как это:

Группа 1: IsLeaf = ложь, поэтому шаблон = GroupTemplate

Группа 2: IsLeaf = верно, так что шаблон = ItemTemplate

Группа 3: IsLeaf = ложь, поэтому шаблон = GroupTemplate

И когда изменения свойств 1-й группы в IsLeaf к истинным, templat e необходимо автоматически переключиться на ItemTemplate.

EDIT:

Это мое временное решение. Любой лучший способ сделать это?

<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

Для ясности вы отбросили подход DataTemplateSelector в пользу триггеров, или вы тоже работали с триггерами в решении с помощью DataTemplateSelector? – alastairs

+0

@alastairs Я не могу говорить для OP, но триггеры, похоже, не делают DataTemplateSelector ненужным. – piedar

ответ

16

Что касается вашего EDIT, не было бы триггером DataTemplate достаточно вместо использования стиля? То есть:

<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

Да, было бы лучше. Я забыл о триггерах DataTemplate. Я буду использовать это как свое решение, поэтому спасибо! – Nick

+9

DataTemplateSelectors необходимо улучшить, чтобы они допускали такой сценарий, потому что это решение, хотя и необходимо, является гораздо более уродливым синтаксисом. Надеюсь, команда WPF обратится к этому – Xcalibur

21

Я нашел это обходное решение, которое мне кажется более легким. Из TemplateSelector выслушайте свойство, о котором вы заботитесь, а затем повторно примените селектор шаблонов, чтобы принудительно обновить.

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

Это сработало отлично! Лучший из всех обходных путей для этой недостающей функции в WPF! – Vaccano

+1

Осторожно с помощью этого кода - исследования после внедрения этого решения в качестве решения моей ситуации переключения шаблонов и замещения падения производительности обнаружили массивную утечку памяти из-за размера DataTemplate, участвовавшего при переключении, - гораздо лучше использовать метод DataTriggers, который похоже, совсем не течет. – toadflakz

+0

Прошло много времени, но мне пришлось реализовать это обходное решение в универсальном приложении, так как у WinRT нет Style.Triggers ... –

2

Возвращаясь к исходному решению, и проблема «селектор шаблона не получает повторно»: вы можете обновить ваш взгляд, как этот

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

где для краткости ссылается ваш ItemsControl по его имени («YourItemsControl») добавил в свой XAML:

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

Единственная проблема может быть как выбрать правильное место в проекте для этой команды обновления. Это может привести к обратному коду представления, или, если ваш IsLeaf является DP, правильным местом будет обратный вызов с измененной зависимостью.

0

Я делаю это со связующим прокси.

Он работает как обычный связывающим прокси (но с 2-мя Реквизит - копирует данные из DataIN в DATAOUT), но устанавливает DATAOUT на NULL и возвращается к значению DataIN всякий раз, когда изменяется значение триггера:

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; 
    } 

} 

Используйте его так:

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

Таким образом, вы не связываться с вашей DataContext непосредственно, но DATAOUT в BindingProxy, который повторяющие оригинал DataContext, но с небольшой разницей: При изменении триггера (в данном примере bool внутри «Item»), TemplateSelector получает перезапуск.

Для этого вам не нужно менять свой TemplateSelector.

Также возможно добавить еще триггеры, просто добавьте триггер2.