【问题标题】:UWP - How to change ItemsControl item's DataTemplate as an item's property changes?UWP - 如何在项目属性更改时更改 ItemsControl 项目的 DataTemplate?
【发布时间】:2021-10-03 21:19:03
【问题描述】:

我有一个绑定到 ItemsSource 的 ItemsControl。每个项目都可以根据项目上各种属性的值分配多个 DataTemplate 之一。这些属性可以在运行时更改,并且需要单独换出 DataTemplates。在 WPF 中,我可以使用以下(部分简化的 xaml)来做到这一点:

<ItemsControl 
    ItemsSource="{Binding Items}">
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="{x:Type ContentPresenter}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource RowTemplateConverter}">
                        <Binding Path="(local:Row.Sum)" />
                        <Binding Path="(local:Row.Avg)" />
                        <Binding Path="(local:Row.ShowFlagA)" />
                        <Binding Path="(local:Row.ShowFlagB)" />    
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>
    </ItemsControl.ItemContainerStyle>

我在尝试将其移至 UWP 时遇到了几个问题:

  1. 不支持多重绑定
  2. 为了弥补上述问题,我尝试将转换器逻辑整合到 Row 的单个字符串属性中,但似乎没有分配 DataTemplate。我使用的绑定语法也会导致运行时 XAML 错误,不知道为什么。
<ItemsControl 
    ItemsSource="{Binding Items}" >
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <Binding Path="RowTemplate" Converter="{StaticResource RowTemplateConverter}"/>
                </Setter.Value>
            </Setter>  
        </Style>
    </ItemsControl.ItemContainerStyle>
  1. DataTemplateSelector 和 ItemContainerStyleSelector 不起作用,因为它们只评估一次,我需要根据各种属性更改更新模板
  2. 我在这里看到了一些答案,说将上述选择器设为空并重新分配它们。这是我能达到的最接近我想要的行为,但是性能很差,有几十个项目,而且属性变化很快,所以我无法使用它。

【问题讨论】:

标签: c# .net wpf data-binding uwp


【解决方案1】:

这只会更新您在视图模型中选择的项目的容器。

是的,DataItemTemplateSelector 在准备物品时起作用。即使实现了INotifyPropertyChanged接口也不会响应item的属性变化,更好的方法是使用IValueConverter根据具体属性更新uielement。

例如

public class ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        object img = null;

        switch (value.ToString())
        {
            case "horizontal":
                img = new BitmapImage(new Uri("ms-appx:///Assets/holder1.png"));
                break;
            case "vertical":
                img = new BitmapImage(new Uri("ms-appx:///Assets/holder2.png"));
                break;
            default:
                break;
        }

        return img;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

请参考IValueConverterdocument

【讨论】:

    【解决方案2】:

    您可以编写附加行为来完成此操作。或者扩展例如ItemsControl(或派生类型)。

    关键是重新分配项目容器的内容,以便再次调用 DataTemplateSelector。
    附加属性将重置内容以触发 DataTemplateSelector。您的视图模型将跟踪需要重新评估实际 DataTemplate 并最终触发附加属性的数据项的更改。这是通过简单地将更改的项目分配给绑定到附加行为的视图模型属性来完成的。

    首先通过扩展 DataTemplateSelector 创建一个模板选择器:

    public class DataItemTemplateSelector : DataTemplateSelector
    {
      public DataTemplate ActivatedTemplate { get; set; }
      public DataTemplate DeactivatedTemplate { get; set; }
    
      protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
      {
        switch (item)
        {
          case DataItem dataItem when dataItem.IsActivated: return this.ActivatedTemplate; 
          default: return this.DeactivatedTemplate; 
        }
      }
    }
    

    实现修改更改项的容器的附加行为:

    public class TemplateSelector : DependencyObject
    {
      public static object GetChangedItem(DependencyObject obj)
      {
        return (object)obj.GetValue(ChangedItemProperty);
      }
    
      public static void SetChangedItem(DependencyObject obj, object value)
      {
        obj.SetValue(ChangedItemProperty, value);
      }
    
      public static readonly DependencyProperty ChangedItemProperty =
          DependencyProperty.RegisterAttached("ChangedItem", typeof(object), typeof(TemplateSelector), new PropertyMetadata(default(object), OnChangedItemChanged));
    
      private static void OnChangedItemChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
      {
        if (!(attachingElement is ItemsControl itemsControl))
        {
          throw new ArgumentException($"Attaching element must be of type '{nameof(ItemsControl)}'");
        }
    
        var container = (itemsControl.ItemContainerGenerator.ContainerFromItem(e.NewValue) as ContentControl);
        var containerContent = container.Content;
        container.Content = null;
        container.Content = containerContent; // Trigger the DataTemplateSelector
      }
    }
    

    应用附加属性并将其绑定到您的视图模型。同时分配模板选择器:

    <Page>    
      <Page.Resources>
        <local:DataItemTemplateSelector x:Key="TemplateSelector">
          <local:DataItemTemplateSelector.ActivatedTemplate>
            <DataTemplate x:DataType="local:DataItem">
              <TextBlock Text="{Binding Text}" Foreground="Red" />
            </DataTemplate>
          </local:DataItemTemplateSelector.ActivatedTemplate>
          <local:DataItemTemplateSelector.DeactivatedTemplate>
            <DataTemplate x:DataType="local:DataItem">
              <TextBlock Text="{Binding Text}" Foreground="Black" />
            </DataTemplate>
          </local:DataItemTemplateSelector.DeactivatedTemplate>
        </local:DataItemTemplateSelector>
      </Page.Resources>
    
      <Grid>
        <ListBox ItemsSource="{x:Bind MainViewModel.DataItems}"
                 local:TemplateSelector.ChangedItem="{x:Bind MainViewModel.UpdatedItem, Mode=OneWay}"
                 ItemTemplateSelector="{StaticResource TemplateSelector}" />
    
      </Grid>
    </Page>
    

    最后让视图模型跟踪相关的属性更改并设置更改的属性,例如绑定到附加行为的UpdatedItem 属性(见上文):

    public class MainViewModel : ViewModel, INotifyPropertyChanged
    {
      public MainViewModel()
      {
        DataItems = new ObservableCollection<DataItem>();
    
        for (int index = 0; index < 10; index++)
        {
          DataItem newItem = new DataItem();
    
          // Listen to property changes that are relevant 
          // for the selection of the DataTemplate
          newItem.Activated += OnItemActivated;
    
          this.DataItems.Add(newItem);
        }
      }
    
      // Trigger the attached property by setting the property that binds to the behavior
      private void OnItemActivated(object sender, EventArgs e) => this.UpdatedItem = sender as DataItem
    
      public ObservableCollection<DataItem> DataItems { get; }
    
      private DataItem updatedItem;
      public DataItem UpdatedItem
      {
        get => this.updatedItem;
        set 
        {
          this.updatedItem = value;
          OnPropertyChanged();
        }
      }
    }
    

    【讨论】:

    • 这会更新控件中所有项目的 DataTemplates,是吗?正如我在帖子中所说,我已经尝试过了,性能影响太大,无法走这条路。
    • 不,这只会更新您在视图模型中选择的项目的容器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    • 2014-12-15
    • 1970-01-01
    • 2017-12-18
    相关资源
    最近更新 更多