2010-08-22 7 views
24

Così qui è il codice XAML che ho:WPF: Riapplicare DataTemplateSelector quando un certo valore cambia

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

Ecco la mia classe 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; 
} 
} 

Il modello di dati GroupTemplate fa riferimento al ListTemplateSelector al suo interno , quindi questo è il motivo per cui mi sono configurato come se lo avessi impostato. È l'unico trucco ricorsivo che potrei mettere insieme. Ma questo non è il problema che sto avendo.

Il mio problema è, voglio passare da ItemTemplate a GroupTemplate quando la proprietà IsLeaf cambia. Funziona magnificamente la prima volta da quando legge la proprietà la prima volta. Ma una volta modificata questa proprietà, il selettore modello non viene riapplicato. Ora, potrei usare i trigger per associare il valore e impostare il modello dell'articolo in modo appropriato, ma devo essere in grado di impostare un modello diverso per ciascun elemento, in quanto potrebbero trovarsi in uno stato diverso.

Ad esempio, dire che ho un elenco di gruppi come questo:

Gruppo 1: IsLeaf = false, in modo da template = GroupTemplate

Gruppo 2: IsLeaf = true, in modo da template = ItemTemplate

gruppo 3: IsLeaf = false, in modo da template = GroupTemplate

E una volta del gruppo 1 di IsLeaf modifiche di proprietà su true, il templat e deve cambiare automaticamente in ItemTemplate.

MODIFICA:

Ecco la mia soluzione temporanea. Qual è il modo migliore per farlo?

<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

Per chiarezza, hai scartato l'approccio DataTemplateSelector in favore di trigger o hai utilizzato i trigger nella soluzione anche con DataTemplateSelector? – alastairs

+0

@alastairs Non posso parlare per OP, ma i trigger sembrano rendere inutile il DataTemplateSelector. – piedar

risposta

16

Per quanto riguarda l'EDIT, non sarebbe sufficiente un trigger DataTemplate anziché utilizzare uno stile? Quello è:

<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ì, sarebbe meglio. Ho dimenticato i trigger DataTemplate. Userò questa come soluzione, quindi grazie! – Nick

+9

DataTemplateSelector deve essere migliorato in modo che consentano questo tipo di scenario perché questa soluzione, sebbene necessaria, è una sintassi molto più brutta. Speriamo che il team WPF risolverà questo – Xcalibur

21

Ho trovato questa soluzione che sembra più facile per me. Dall'interno di TemplateSelector, ascolta la proprietà a cui tieni e riapplica il selettore di modelli per forzare un aggiornamento.

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

Questo ha funzionato perfettamente! La cosa migliore di tutte le soluzioni alternative per questa caratteristica mancante in WPF! – Vaccano

+1

Attento con questo codice: dopo aver implementato questa soluzione come una soluzione alla mia situazione di commutazione del modello e notando un calo delle prestazioni, si è verificata una perdita di memoria dovuta alla dimensione del DataTemplate coinvolto durante il passaggio: un'idea molto migliore per utilizzare il metodo DataTriggers che non sembra perdersi affatto. – toadflakz

+0

È passato molto tempo, ma ho dovuto implementare questa soluzione alternativa in un'App universale in quanto WinRT non ha Style.Triggers ... –

2

Tornando indietro alla soluzione originale e il problema di "selettore modello non viene riapplicata": è possibile aggiornare la vostra vista come quella

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

dove per brevità il tuo ItemsControl viene fatto riferimento con il suo nome ("YourItemsControl") ha aggiunto al tuo XAML:

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

L'unico problema potrebbe essere come scegliere posto giusto nel progetto per questa istruzione di aggiornamento. Potrebbe andare in una vista code-behind, o, se il tuo IsLeaf è un DP, il posto giusto sarebbe un callback modificato dalla proprietà di dipendenza.

0

Lo faccio con un proxy vincolante.

Funziona come una normale procura vincolante (ma con 2 puntelli - copia i dati dal DataIn a DataOut), ma imposta il DataOut NULL e di nuovo al valore DataIn ogni volta che il valore cambia trigger:

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

} 

utilizzare in questo modo:

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

Quindi non si legano al vostro DataContext direttamente, ma per DataOut del BindingProxy, che rispecchia il DataContext originale, ma con una piccola differenza: quando i cambiamenti di riferimento (in questo esempio, un valore bool all'interno di "Item"), TemplateSelector si riattiva.

Non è necessario modificare TemplateSelector per questo.

È anche possibile aggiungere più trigger, basta aggiungere un trigger2.