Подтвердить что ты не робот

WPF: повторное использование DataTemplateSelector при изменении определенного значения

Итак, вот XAML, который у меня есть:

<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 = false, поэтому template = GroupTemplate

Группа 2: IsLeaf = true, поэтому шаблон = ItemTemplate

Группа 3: IsLeaf = false, поэтому template = GroupTemplate

И как только свойство 1 группы IsLeaf изменится на true, шаблон должен автоматически измениться на 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>
4b9b3361

Ответ 1

Что касается вашего 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>

Ответ 2

Я нашел это обходное решение, которое мне кажется более легким. Изнутри 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());
        }
    }


}

Ответ 3

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

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, правильным местом будет обратный вызов с измененной зависимостью.

Ответ 4

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

Он работает как обычный связующий прокси (но с 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, а к BindingProxy DataOut, который отражает исходный DataContext, но с небольшой разницей: когда триггер изменяется (в этом примере значение bool внутри "Item" ) TemplateSelector будет перезагружен.

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

Также возможно добавить больше триггеров, просто добавьте Trigger2.