如何在属性更改时触发DataTemplateSelector?

31

我有一个带有DataTemplateSelector的ContentPresenter:

    ...

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var model = item as ItemControlViewModel;

        if (model.CurrentStatus == PrerequisitesStatus.Required)
        {
            return RequiredTemplate;
        }

        if (model.CurrentStatus == PrerequisitesStatus.Completed)
        {
            return FinishedTemplate;
        }

        ...

        return InProgressTemplate;
    }
当CurrentStatus被改变时,会调用OnPropertyChanged方法。
我需要在属性改变时触发DataTemplateSelector并更改ContentPresenter的DataTemplate。有什么建议吗?
有类似的问题: 1 2,但我不想使用任何DataTriggers,因为状态太多了。
尝试使用DataTriggers进行操作。
    <ContentPresenter
        Grid.Column="1"
        Height="16"
        Width="16"
        Margin="3">
        <ContentPresenter.Triggers>
            <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
                <Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
            </DataTrigger>
        </ContentPresenter.Triggers>
    </ContentPresenter>

但是我遇到了一个错误: 触发器集合成员必须是EventTrigger类型 :(


@apt0r 你考虑过使用VisualStateManager而不是模板吗? - user572559
不,我认为这里不适合。我需要更改模板,而不是属性。 - mxpv
3
我曾经遇到过同样的问题,后来改用了DataTriggers来解决它,我认为这是更好的解决方案,没有比这更好的了。 - SvenG
1
@SvenG,你能发一些例子吗? - mxpv
4个回答

40

根据你在评论中提出的datatriggers示例要求,下面是一个示例:

FrameworkElement只能具有EventTriggers,因此您会收到错误消息"Triggers collection members must be of type EventTrigger"

同时不要直接使用ContentPresenter,它应该在ControlTemplate内使用。当您想拥有动态内容时,请使用ContentControl。 请参见ContentControl和ContentPresenter之间的区别是什么?

最后,这里有一个建议来解决您的DataTrigger问题。我将它放在样式中以供复用...

XAML :

<Window x:Class="WpfApplication88.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>

    <DataTemplate x:Key="requiredTemplate">
      <TextBlock Text="requiredTemplate"></TextBlock>
      <!--your stuff here-->
    </DataTemplate>

    <DataTemplate x:Key="completedTemplate">
      <TextBlock Text="CompletedTemplate"></TextBlock>
      <!--your stuff here-->
    </DataTemplate>

    <Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
          <Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
          <Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
        </DataTrigger>
        <!--  your other Status' here -->
      </Style.Triggers>
    </Style>

  </Window.Resources>

  <Grid>
    <ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
  </Grid>

</Window>

注意,在使用DataTriggers时,xaml引擎始终会创建所有可能的数据模板,即使它们没有被使用,这可能会导致在使用复杂GUI时出现一些性能问题。 - Lukáš Koten
根据链接的问题:ContentPresenter被设计为按原样使用,而ContentControl被设计为可扩展(继承自)。在这种特定情况下推荐使用ContentControl是不好的编码建议。 - Imagin

6

我可能说错了,但我认为DataTemplateSelector仅在ItemContainerGenerator为添加到集合中的项目创建容器时使用。因为当属性值更改时不会生成新的容器,所以选择器永远不会应用新的DataTemplate

正如评论中建议的那样,我建议您查看VisualStateManager或数据触发器,否则当一个或多个属性更改时,您将不得不为每个项目重新创建容器。


2

作为额外的选择 - 如果您想坚持使用模板,只需使用带有转换器的 s 绑定。


2

我想出了一个理论上可以实现这个功能的行为。

C#:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
    public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
    public object Content
    {
        get => GetValue(ContentProperty);
        set => SetValue(ContentProperty, value);
    }
    static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is UpdateTemplateBehavior behavior)
            behavior.Update();
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
    public object Value
    {
        get => GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is UpdateTemplateBehavior behavior)
            behavior.Update();
    }

    public UpdateTemplateBehavior() : base() { }

    protected override void OnAttached()
    {
        base.OnAttached(); 
        Update();
    }

    void Update()
    {
        if (Content != null)
        {
            BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
            AssociatedObject.Content = null;

            BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
        }
    }
}

XAML:

<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
    <i:Interaction.Behaviors>
        <Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
            Value="{Binding SomeValue}"/>
    </i:Interaction.Behaviors>
</ContentPresenter>

当内容(在本例中为“SomeContent”)和任意值(在本例中为“SomeValue”)发生更改以及首次附加行为时,通过清除然后重置绑定来“更新”内容。

仅当内容不为空(我的项目特定要求)时才进行更新。在附加时不进行更新可能会避免意外同时更新两次,但如果值最初为null,则不会发生更新,直到值至少更改一次。

注意:在上面的示例中,我不确定行为是否具有与ContentPresenter相同的数据上下文。出于简洁起见,我没有在此处包含帮助程序类。在测试时请记住这一点...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接