【问题标题】:DataTemplates and Generics数据模板和泛型
【发布时间】:2019-06-03 04:53:52
【问题描述】:

我已经阅读了近千篇文章,解释在 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&lt;string, string&gt; 定义为DataType。但不幸的是,我无法让它工作。

所以问题是:如何将Dictionary&lt;string, string&gt; 定义为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]}"

但由于它们都不起作用,我深入到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。因为这个类有MangleGenericTypeName-方法,它使用了`-字符。 但是我仍然看不到提取类型参数的代码,所以我看不到为字典指定类型参数的正确语法。这就是我卡住的地方。

(不用说微软文档在这个话题上毫无价值。)

编辑:由于似乎不清楚我为什么想要这个,我会解释一下:我想要自动选择ContentTemplateContentControl。当然:我在示例中构造的DataTemplate 非常简单。但是每个人都应该能够想象到,我想要不同的 DataTemplates 用于 Lists、Dictionaries 或简单的字符串。

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

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

【问题讨论】:

  • 这里没有任何泛型类型。你有一个 concrete 类型,List&lt;string&gt;。泛型类型是List&lt;T&gt;。您不能仅仅因为 编译器 无法知道这些泛型类型中包含什么而使用泛型类型
  • 不,MS 文档并非一文不值,也不是所有的人都说你不能错误地使用泛型类型。毕竟 WPF 是在 10 年前问世的,有人会注意到的。 几乎所有教程和文档都显示绑定到泛型类型,无论是 List&lt;SomeEntity&gt;ObservableCollection&lt;SomeOtherEntity&gt; 还是 EF 返回的内容
  • 没关系,问题应该按原样关闭,我没有投反对票。为什么你认为你需要指定这样的类型?您要解决的实际问题是什么?数据模板通常用于在容器中显示 items,这就是为什么您会将字符串视为 DataType 而不是 IEnumerable。容器由派生自 ItemsControls 的控件呈现,这些控件已经知道 IEnumerable
  • 最后,您甚至不需要在 DataTemplate 中指定 DataType。 WPF 数据绑定与反射一起使用,因此您无需指定类型,只要模板绑定到的对象具有满足绑定表达式的属性。
  • @Clemens 并且仍然需要 ItemsControl 来显示项目。即使需要一些转换,converter 也可以完成这项工作。无需创建标记扩展

标签: .net wpf xaml .net-4.6.2


【解决方案1】:

我让它与以下代码一起工作:

写一个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 时,编译器会告诉我它没有密钥。

这不起作用:

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

作为一种解决方法,我现在在ResourcesFrameworkElement 中定义我的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 自动应用它们 =)

【讨论】:

  • 嘿,这个方法非常适合我的用例,它在我第一次运行我的程序时工作,但下一次,没有任何代码更改,它失败,并出现构建错误:“所有对象已添加到 IDictionary 必须在 DataTemplate 定义中具有 Key 属性...”。我已经尝试过构建,重建,我能想到的任何东西,但摆脱构建错误的唯一方法是用普通类型替换 DataTemplate 的 DataType :( 有什么想法吗?&lt;DataTemplate DataType="{StaticResource OrderViewDataType}"&gt;
  • TabViewData 其中 T : IsTab OrderViewData : IsTab
  • 好的,我通过用&lt;DataTemplate.DataType&gt; &lt;StaticResource ResourceKey="OrderViewDataType" /&gt; &lt;/DataTemplate.DataType&gt; 替换 DataTemplate DataType 分配来“修复”它,如果我将 carret 放在上述代码中,设计器视图就会中断,但我可以忍受。希望其他人可以使用这个:)
  • 啊,它又坏了。我正在研究项目的其他部分,试图构建。 Bam:“字典的键不能是 'System.Windows.StaticResourceExtension' 类型”我不明白......抱歉给你的帖子发了垃圾邮件。
  • 我创建了自己的问题:stackoverflow.com/questions/70082742/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-09
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多