简体   繁体   English

属性更改时如何触发 DataTemplateSelector?

[英]How to trigger DataTemplateSelector when property changes?

I have ContentPresenter with DataTemplateSelector:我有带有 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;
    }

When CurrentStatus is changed, OnPropertyChanged is called.当 CurrentStatus 更改时,将调用 OnPropertyChanged。

I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate.当属性更改并更改 ContentPresenter DataTemplate 时,我需要以某种方式触发此 DataTemplateSelector。 Any suggestions?有什么建议?

Threre are similar questions: 1 2 , but I don't want to use any DataTriggers, because of too much states. Threre 有类似的问题: 1 2 ,但我不想使用任何 DataTriggers,因为状态太多。

Tried to play with 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>

But got an error: Triggers collection members must be of type EventTrigger :(但出现错误:触发器集合成员必须是 EventTrigger 类型 :(

As you requested an example with datatriggers in the comments, here you are: 正如您在评论中请求数据触发器的示例,您在这里:

A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger FrameworkElement只能有EventTriggers,因此您会收到错误消息触发器集合成员必须是EventTrigger类型

And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. 并且也不要直接使用ContentPresenter,它应该在ControlTemplate中使用。 Better use a ContentControl when you want to have dynamic content. 当您想拥有动态内容时,最好使用ContentControl。 See What's the difference between ContentControl and ContentPresenter? 看看ContentControl和ContentPresenter有什么区别?

And finally here's a suggestion to your DataTrigger issue. 最后,这是对DataTrigger问题的建议。 I have put it inside a style for reusability .... 我把它放在一个可重用的风格中....

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

I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. 我可能错了,但我相信DataTemplateSelector仅在ItemContainerGenerator为添加到集合中的项创建容器时使用。 Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector. 由于在属性值更改时未生成新容器,因此永远不会通过选择器应用新的DataTemplate

As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value. 正如评论中所建议的那样,我建议您查看VisualStateManager或数据触发器,否则当一个或多个属性更改值时,您将不得不为每个项重新创建容器。

I came up with a behavior that would theoretically do this.我想出了一个理论上可以做到这一点的行为。

C#: 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: XAML:

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

The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.当内容(在本例中为“SomeContent”)和任意值(在本例中为“SomeValue”)以及行为发生变化时,内容被“更新”(通过清除然后重置绑定)先附上。

An update is not made unless the content is not null (my project-specific requirement).除非内容不为空(我的项目特定要求),否则不会进行更新。 Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null , an update wouldn't occur until the value changes at least once.附加时不更新可能会避免无意中一次更新两次,但如果值最初为null ,则在值至少更改一次之前不会发生更新。

Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter .注意:在上面的示例中,我不确定该行为是否与ContentPresenter具有相同的数据上下文。 I use a helper class that I did not include here for brevity.为简洁起见,我使用了一个未在此处包含的帮助程序类。 Keep that in mind when testing...测试时请记住这一点...

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM