2010-08-22 11 views
24

So, hier ändert, ist die XAML, die ich habe:WPF: Reapply Datatemplateselector, wenn ein bestimmter Wert

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

Hier meine ListTemplateSelector Klasse:

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

Die GroupTemplate Datenvorlage die ListTemplateSelector in sich selbst verweist Deshalb habe ich mich eingerichtet, wie ich es eingerichtet habe. Es ist der einzige rekursive Hack, den ich zusammenstellen könnte. Aber das ist nicht das Problem, das ich habe.

Mein Problem ist, ich möchte von ItemTemplate in GroupTemplate ändern, wenn die IsLeaf-Eigenschaft ändert. Dies funktioniert wunderbar zum allerersten Mal seit es das Anwesen das erste Mal liest. Sobald sich diese Eigenschaft ändert, wird der Vorlagenselektor nicht erneut angewendet. Jetzt könnte ich Trigger verwenden, um an den Wert zu binden und die Elementvorlage entsprechend zu setzen, aber ich muss in der Lage sein, für jedes Element eine andere Vorlage festzulegen, da sie sich in einem anderen Status befinden könnten.

Zum Beispiel sagen, dass ich eine Liste von Gruppen wie dieses:

Gruppe 1: IsLeaf = false, so template = GroupTemplate

Gruppe 2: IsLeaf = true, so template = ItemTemplate

Gruppe 3: IsLeaf = false, so template = GroupTemplate

Und sobald der Gruppe 1 Änderungen IsLeaf Eigenschaft auf true, die templat e muss automatisch zu ItemTemplate wechseln.

EDIT:

Hier ist meine vorübergehende Lösung. Gibt es einen besseren Weg?

+2

Haben Sie zur Verdeutlichung den DataTemplateSelector-Ansatz zugunsten von Triggern verworfen, oder haben Sie die Trigger auch mit dem DataTemplateSelector in die Lösung umgesetzt? – alastairs

+0

@alastairs Ich kann nicht für OP sprechen, aber die Trigger scheinen den DataTemplateSelector unnötig zu machen. – piedar

Antwort

16

In Bezug auf Ihre EDIT, wäre ein DataTemplate Trigger nicht genug, anstatt einen Style zu verwenden? Das ist:

<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

Ja, das wäre besser. Ich habe DataTemplate-Trigger vergessen. Ich werde das als meine Lösung verwenden, also danke! – Nick

+9

DataTemplateSelectors müssen verbessert werden, damit sie diese Art von Szenario ermöglichen, da diese Lösung, obwohl notwendig, eine viel hässlichere Syntax ist. Hoffentlich wird WPF-Team diese Adresse adressieren – Xcalibur

21

Ich fand diese Abhilfe, die mir leichter scheint. Rufen Sie innerhalb des TemplateSelectors die Eigenschaft auf, die Ihnen wichtig ist, und wenden Sie dann den Vorlagenselektor erneut an, um eine Aktualisierung zu erzwingen.

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

Das hat perfekt funktioniert! Das Beste von all den Problemumgehungen für dieses fehlende Feature in WPF! – Vaccano

+1

Vorsichtig mit diesem Code - Untersuchungen nach der Implementierung dieses als eine Lösung für meine eigene Template-Switching-Situation und bemerken einen Leistungsabfall entdeckt ein großes Speicherleck aufgrund der Größe des DataTemplate beim Wechsel beteiligt - viel bessere Idee, die DataTriggers-Methode zu verwenden scheint überhaupt nicht zu lecken. – toadflakz

+0

Schon lange her, aber ich musste diese Problemumgehung in einer Universal App implementieren, da WinRT nicht Style.Triggers hat ... –

2

zur ursprünglichen Lösung und das Problem der „die Vorlage Selektor erhält nicht erneut angewendet“ Rückkehr zurück: Sie die Ansicht wie die

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

wo der Kürze halber Itemscontrol verwiesen aktualisieren kann, ist durch seinen Namen („YourItemsControl“) hinzugefügt, um Ihre XAML:

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

Das einzige Problem könnte sein, wie die richtigen Ort, in Ihrem Projekt für diese Auffrischanweisungsrahmen zu wählen. Es könnte in eine Ansicht Code-Behind gehen, oder, wenn Ihre IsLeaf eine DP ist, wäre der richtige Ort eine Abhängigkeit-Eigenschaft geändert Callback.

0

Ich mache es mit einem verbindlichen Proxy.

Es funktioniert wie eine normale Bindung Proxy (aber mit 2 Props - kopiert Daten von DataIn zu DataOut), sondern setzt den DataOut auf den DataIn Wert auf NULL und zurück, wenn der Trigger-Wert ändert:

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

} 

verwenden Sie es wie folgt aus:

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

So binden Sie nicht auf Ihre Datacontext direkt, sondern auf die BindingProxy des DataOut, die die ursprüngliche Datacontext Spiegel, aber mit einem kleinen Unterschied: Wenn die Trigger-Änderungen (in diesem Beispiel ein bool Wert innerhalb des 'Item'), der TemplateSelector wird erneut ausgelöst.

Sie müssen dafür Ihren TemplateSelector nicht ändern.

Es ist auch möglich, weitere Trigger hinzuzufügen, fügen Sie einfach einen Trigger2 hinzu.