WPF DataTemplate Selector (2)

  • 565
  • 0
  • 2020-05-03

上一篇談了普遍的 DataTemplate Selector 實作,這一篇聊點進階的玩意。

從上一篇延伸來的問題,如果在專案裡面有許多類似的情況,每次都要建立一個特定的 DataTemplate Selector 真的太麻煩,能不能畢其功於一役,撰寫一個適用範圍能夠擴大的 DataTemplate Selector;這正是這篇要展示的做法。

利用 Attribute

從前一篇可以想見是靠著 ResourceDictionary 的 key 來選擇所對應的 DataTamplate,既然那個 key 不過就是個字串,那何不把這個字串放在 View Model 身上。我採取的作法是將這個 key 利用 attribute 掛在 View Model 的型別宣告。基於懶惰的理由,我就不再另外設計 attribute class,而是直接利用現有的 DescriptionAttribute 的 Description 屬性儲存這個 key 值。

分離 DataTemplate

這個做法的另外一個事情就是將 DataTemplate 單獨放在一個 xaml 檔案中,所以我們會建立一個 Resource Dictionary (資源字典) file,內容如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DataTemplate x:Key="personTemplate">
        <StackPanel Orientation="Horizontal" Margin="6">
            <TextBlock Text="Name : "/>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text="Age : " Margin="6,0,0,0"/>
            <TextBlock Text="{Binding Age}"/>
        </StackPanel>
    </DataTemplate>
    <DataTemplate x:Key="carTemplate">
        <Border  Margin="4">
            <Border.Background >
                <SolidColorBrush Color="{Binding Color}"/>
            </Border.Background>
            <StackPanel Margin="2">
                <TextBlock Text="Brand"/>
                <TextBlock Text="{Binding Brand}"/>
            </StackPanel>
        </Border>
    </DataTemplate>
</ResourceDictionary>
為 View Model 掛上 Attribute
 [Description("personTemplate")]
 public class Person : NotifyPropertyBase
 {
     private string _name;     
     public string Name
     {
         get => _name;
         set => SetProperty(ref _name, value);
     }


     private int _age;
     public int Age
     {
         get => _age;
         set => SetProperty(ref _age, value);
     }
 }

 [Description ("carTemplate")]
 public class Car : NotifyPropertyBase
 {
     private string _brand;    
     public string Brand
     {
         get => _brand;
         set => SetProperty(ref _brand, value);
     }

     private Color _color;
     public Color Color 
     {
         get => _color; 
         set => SetProperty(ref _color , value); 
     }
 }
 DataTemplate Selector

接著就是建立 DataTemplate Selector,這邊會在這個類別加上一個 Path 屬性,這個屬性的用途是為了指示 DataTemplate 所在的檔案來源。內部藉由反射取得 item 參數所屬型別的 DescriptionAttribute 中的 Description 屬性值來對應 Resource Dictionary 中的 key。

  public class UniversalTemplateSelector : DataTemplateSelector
  {
      public string Path { get; set; }

      public override DataTemplate SelectTemplate(object item, DependencyObject container)
      {            
          var source = new ResourceDictionary { Source = new Uri(Path, UriKind.RelativeOrAbsolute) };
          var attribute = item?.GetType().GetCustomAttribute<DescriptionAttribute>();
          if (attribute != null && source.Contains(attribute.Description))
          {
              return source[attribute.Description] as DataTemplate;
          }
          return null;
      }
  }
View 上的應用

在 View 上只要設定好這個 selector 的 Path,就可以直接使用了,這個作法能夠更靈活地抽換 DataTemplate。

<Window x:Class="DataTemplateSelectorSample002.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:DataTemplateSelectorSample002.ViewModels"
        xmlns:local="clr-namespace:DataTemplateSelectorSample002"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext >
        <vm:MainViewModel />
    </Window.DataContext>
    <Window.Resources >
        <local:UniversalTemplateSelector Path="/Templates/TemplatesDictionary.xaml" x:Key="selector"/>        
    </Window.Resources>
    <ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Data}" ItemTemplateSelector="{StaticResource selector}"/>
</Window>

詳細的範例程式碼可以點選此處