WPF: appliquer de nouveau DataTemplateSelector lorsqu'une certaine valeur change

alors voici le XAML que j'ai:

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

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

le modèle de données GroupTemplate fait référence au secteur ListTemplateSelector à l'intérieur de lui-même, c'est pourquoi j'ai mis en place comme je l'ai fait mettre en place. C'est le seul piratage récursif que j'ai pu assembler. Mais ce n'est pas le problème, je vais avoir.

mon problème est, je veux passer de ItemTemplate à GroupTemplate lorsque le Changement de propriété IsLeaf. Cela fonctionne à merveille la première fois depuis qu'il lit la propriété la première fois. Mais une fois que cette propriété change, le sélecteur de template n'est plus appliqué. Maintenant, je pourrais utiliser des triggers pour lier à la valeur et régler le modèle d'article de manière appropriée, mais je dois être en mesure de définir un modèle différent pour chaque article, car ils pourraient être dans un état différent.

par exemple, dites que j'ai une liste de groupes comme celui-ci:

Groupe 1: IsLeaf = faux, alors template = GroupTemplate

Groupe 2: IsLeaf = vrai, alors template = ItemTemplate

Groupe 3: IsLeaf = faux, alors template = GroupTemplate

et une fois que la propriété IsLeaf du Groupe 1 change pour true, le modèle doit automatiquement changer pour ItemTemplate.

EDIT:

voici ma solution temporaire. Une meilleure façon de faire il?

<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>
24
demandé sur Nick 2010-08-23 01:35:36

4 réponses

en ce qui concerne votre édition, un déclencheur Dataemplate ne serait-il pas suffisant au lieu d'utiliser un Style? C'est-à-dire:

<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>
19
répondu ASanch 2010-08-23 03:07:33

j'ai trouvé cette solution qui me semble plus facile. De L'intérieur du TemplateSelector, écoutez la propriété qui vous intéresse, puis appliquez de nouveau le sélecteur de template pour forcer un rafraîchissement.

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


}
21
répondu Jason Turan 2013-08-01 17:18:41

retour à votre solution d'origine et le problème de "le sélecteur de template n'est pas appliqué de nouveau": vous pouvez rafraîchir votre vue comme cela

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

où par souci de brièveté votre ItemsControl est référencé par son nom ("YourItemsControl") ajouté à votre XAML:

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

le seul problème peut être comment choisir la bonne place dans votre projet pour cette instruction de rafraîchissement. Il pourrait entrer dans un code de vue-derrière, ou, si votre IsLeaf est un DP, le bon endroit serait un callback de type dependency-property-changed.

2
répondu V.V.T 2014-10-10 06:10:54

je le fais par procuration.

fonctionne comme un proxy de liaison normal (mais avec 2 Props - copie les données de DataIn à DataOut), mais définit la DataOut à NULL et retour à la valeur de DataIn chaque fois que la valeur de déclenchement change:

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

}

utilisez - le comme ceci:

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

donc vous ne vous liez pas directement à votre Datacontexte, mais à la sortie de BindingProxy, qui reflète le Datacontexte original, mais avec un petite différence: lorsque le trigger change (dans cet exemple une valeur bool à l'intérieur de L'élément 'Item'), le TemplateSelector est réactivé.

vous n'avez pas à changer votre TemplateSelector pour cela.

il est également possible d'ajouter plus de Triggers, il suffit d'ajouter un Trigger2.

0
répondu JCH2k 2017-08-03 14:12:49