数据模板和泛型

时间:2019-01-08 13:23:33

标签: .net wpf xaml .net-4.6.2

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

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

<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" };
    }
}

使用此设置,您将看到“高字符串列表”。对我来说,这就是我可以将泛型定义为DataType的证明。但我想更进一步:我想将Dictionary<string, string>定义为DataType。但不幸的是,我无法使它正常工作。

问题是:如何将Dictionary<string, string>定义为DataType的{​​{1}}?

如果您知道答案,则可以停止阅读。但是由于这是展示我已经做过的好习惯,所以我继续写作。 我已经做了什么? 刚开始我就蛮力尝试了几种类似的组合:

DataTemplate

但是,由于它们都不起作用,因此我深入研究- 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]}" ,并查看了System.XamlTypeExtensionGenericTypeNameParser,因为我认为这些是可以解决类型。但是看着代码,我意识到`是一个无效字符。

为了证明这一点,我写了自己的GenericTypeNameScanner

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();
    }
}

这引发了一个例外,即字符'不是预期的,并且xaml类型无效。

这让我想知道为什么我的第一个示例有效。我认为,在为wpf标记xaml进行编译时,不是<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> 用于解析XamlType,但是我认为使用了TypeExtension。因为此类具有使用`-字符的XamlNamespace方法。 但是我仍然看不到提取类型参数的代码,因此我看不到为Dictionary指定类型参数的正确语法。这就是我被困住的地方。

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

编辑:由于不清楚为什么要这样做,我将对其进行解释:我希望自动选择ContentControl的ContentTemplate。当然,我在示例中构造的MangleGenericTypeName非常简单。但是每个人都应该能够想象,我想要列表,字典或简单字符串使用不同的DataTemplates。

我有一个具有DataTemplate属性的ViewModel。有时,结果是一个int,有时是一个字符串,有时是一个List等,依此类推。我将此public object Result { get; }属性绑定到Result的{​​{1}}属性。对于提到的所有类型,我编写了不同的DataTemplates,这些模板由WPF自动选择。因此Content显示在ContentControl中,而int显示在Rectangle中。

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

1 个答案:

答案 0 :(得分:0)

我可以使用以下代码:

编写一个MarkupExtension,将您想要的闭合通用类型作为DataTemplate的DataType返回(这不是我自己的。它位于SO的某处,但我没有保留链接)。

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,请使用可以在代码后边写:

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

您将看到您的DataTemplate。

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

这不起作用:

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

作为一种解决方法,我现在在FrameworkElement的资源中定义我的DataTemplates,并将它们以代码隐藏的方式添加到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 =)自动应用它们。