数据模板和泛型

DataTemplates and Generics

我已经阅读了将近一千篇文章,解释说在 DataTemplate 上将封闭泛型类型设置为 DataType 是行不通的,因为 WPF 不支持它。但事实上,这是完全错误的。

我可以在我的 Window.Resources 中定义以下 DataTemplate,当我将字符串列表分配给内容控件时将使用它。例如:

<Window x:Class="WpfApp1.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:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate DataType="{x:Type TypeName=Generic:List`1[System.String]}">
            <TextBlock Text="Hi List of Strings"
                       FontSize="40"
                       Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl x:Name="_contentControl">
        </ContentControl>
    </Grid>
</Window>

在代码隐藏中:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new List<string> { "Huhu" };
    }
}

使用此设置,您将看到 "Hi List of Strings"。对我来说,这证明我可以将泛型类型定义为 DataType。但我想更进一步:我想将 Dictionary<string, string> 定义为 DataType。但不幸的是,我无法让它工作。

所以问题是:如何将Dictionary<string, string>定义为DataTemplateDataType

如果你知道答案,你可以停止阅读。但由于展示我已经做过的事情是一种很好的做法,所以我继续写作。 我已经做了什么? 起初我尝试了蛮力并尝试了几种类似于以下的组合:

- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String];[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String],[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String,System.String]}"

但由于其中 none 有效,我深入研究 System.Xaml 并查看了 TypeExtensionGenericTypeNameParserGenericTypeNameScanner,因为我认为这些是解析类型的代码行。但是查看代码我意识到 ` 是一个无效字符。

为了证明,我自己写了MarkupExtension

public class UseTheTypeExtensionsParser : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var a = new TypeExtension("Generic:List`1[[System.String]]");
        var type = a.ProvideValue(serviceProvider);
        return type.ToString();
    }
}

并按如下方式使用:

<Window x:Class="WpfApp1.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:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ContentControl Content="{WpfApp1:UseTheTypeExtensionsParser}"/>
    </Grid>
</Window>

这引发了异常,字符 ` 不是预期的并且 XAML 类型无效。

这让我想知道为什么我的第一个示例有效。我认为,在为 WPF 标记编译 XAML 时,它不是用于解析 XamlType 的 TypeExtension,但我认为使用了 XamlNamespace。因为此 class 具有使用 `- 字符的 MangleGenericTypeName 方法。 但是我仍然看不到提取类型参数的代码,所以我看不到为字典指定类型参数的正确语法。这就是我卡住的地方。

(不用说 Microsoft 文档在这个主题上毫无价值。)

编辑: 既然不清楚为什么要这个,我就解释一下:我要自动选择一个ContentTemplate of the ContentControl .当然:我在示例中构造的 DataTemplate 非常简单。但是每个人都应该能够想象,我想要不同的数据模板用于列表、字典或简单的字符串。

我有一个 ViewModel,它有一个 public object Result { get; } 属性。有时,结果是一个 int,有时是一个字符串,有时是一个 List 等等。我将此 Result-属性 绑定到 ContentControlContent-属性。对于提到的所有类型,我编写了不同的 DataTemplates,它们由 WPF 自动选择。所以 ints 显示在 Rectangle 中,Strings 显示在 Ellipse.

在我完成所有这些工作后,我想要另一个 DataTemplate,但这次是 Dictionary

我用下面的代码让它工作:

为你的 DataTemplate 写一个 MarkupExtension returns 你想要的封闭通用类型作为 DataType (这不是我自己的。它来自 SO,但我没有保留 link).

public class GenericType : MarkupExtension
{
    public GenericType() { }

    public GenericType(Type baseType, params Type[] innerTypes)
    {
        BaseType = baseType;
        InnerTypes = innerTypes;
    }

    public Type BaseType { get; set; }

    public Type[] InnerTypes { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        Type result = BaseType.MakeGenericType(InnerTypes);
        return result;
    }
}

使用方法如下:

<Window x:Class="WpfApp1.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:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <x:Array Type="{x:Type System:Type}" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>

        <WpfApp1:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                           InnerTypes="{StaticResource ListWithTwoStringTypes}"
                           x:Key="DictionaryStringString" />

        <DataTemplate DataType="{StaticResource DictionaryStringString}">
            <TextBlock Text="Hi Dictionary"
                   FontSize="40"
                   Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl x:Name="_contentControl"/>
    </Grid>
</Window>

查看DataTemplate是否自动应用,使用可以在code-behind中写:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new Dictionary<string, string>();
    }
}

您将看到您的 DataTemplate。

但在我的项目中,我有一个专门用于编写所有 DataTemplates 和 ControlTemplates 样式的程序集。通常我有一个 ResourceDictionary 来保存它们。但是当我想把我的 DataTemplate 放在 ResourceDictionary 中时,编译器告诉我它没有密钥。

这不起作用:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:System="clr-namespace:System;assembly=mscorlib"
                    xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
                    xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib">


    <x:Array Type="{x:Type System:Type}" 
             x:Key="ListWithTwoStringTypes">
        <x:Type TypeName="System:String" />
        <x:Type TypeName="System:String" />
    </x:Array>

    <DataTemplates:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                               InnerTypes="{StaticResource ListWithTwoStringTypes}"
                               x:Key="DictionaryStringString" />

    <DataTemplate DataType="{StaticResource DictionaryStringString}">

        <TextBlock Text="Hi Dictionary"
                   FontSize="40"
                   Foreground="Cyan"/>
    </DataTemplate>

</ResourceDictionary>

作为解决方法,我现在在 FrameworkElementResources 中定义我的 DataTemplate,并将它们添加到 Application.Resources 的代码隐藏中。

这是DictionaryStringString.xaml

<FrameworkElement x:Class="Dana.Styles.Flat.DataTemplates.DictionaryStringString"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
             xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
             xmlns:System="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <FrameworkElement.Resources>

        <x:Array Type="{x:Type System:Type}" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>

        <DataTemplates:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                                   InnerTypes="{StaticResource ListWithTwoStringTypes}"
                                   x:Key="DictionaryStringString" />

        <DataTemplate DataType="{StaticResource DictionaryStringString}">

            <TextBlock Text="Hallo Wörterbuch"
                           FontSize="40"
                           Foreground="Cyan"/>Template>
            </ItemsControl>-->
        </DataTemplate>
    </FrameworkElement.Resources>
</FrameworkElement>

这是 DictionaryStringString.xaml.cs:

public partial class DictionaryStringString
{
    /// <summary>
    /// Konstruktor
    /// </summary>
    public DictionaryStringString()
    {
        InitializeComponent();
    }
}

然后,我在初始化样式的地方添加了:

var _dictionaryStringString = new DictionaryStringString();
Application.Current.Resources.MergedDictionaries.Add(_dictionaryStringString.Resources);

现在我可以为所有封闭的泛型定义 DataTemplates 并让 WPF 自动应用它们 =)