数据模板和泛型

问题描述 投票:0回答:1

我读过近千篇文章,解释在

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

通过此设置,您将看到“Hi List of Strings”。对我来说,这证明我可以将泛型类型定义为

DataType
。但我想更进一步:我想将
Dictionary<string, string>
定义为
DataType
。但不幸的是,我无法让它工作。

所以问题是:如何将

Dictionary<string, string>
定义为
DataType
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.Xaml
并查看
TypeExtension
GenericTypeNameParser
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();
    }
}

并按如下方式使用它:

<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
方法。 但我仍然看不到提取类型参数的代码,因此我看不到为字典指定类型参数的正确语法。这就是我被困住的地方。

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

编辑:由于似乎不清楚我为什么想要这个,我将解释一下:我想要自动选择

ContentTemplate
中的
ContentControl
。当然:我在示例中构造的
DataTemplate
非常简单。但每个人都应该能够想象,我想要不同的 DataTemplates 用于列表、字典或简单字符串。

我有一个 ViewModel,它有一个

public object Result { get; }
属性。有时,结果是一个 int,有时是一个 string,有时是一个 List,等等。我将此
Result
属性绑定到
Content
ContentControl
属性。对于提到的所有类型,我编写了不同的 DataTemplates,它们由 WPF 自动选择。因此
int
显示在
Rectangle
中,而
String
显示在
Ellipse
中。

完成所有这些工作后,我想要另一个

DataTemplate
,但这次是
Dictionary

.net wpf xaml .net-4.6.2
1个回答
4
投票

我让它可以使用以下代码:

编写一个

MarkupExtension
,它返回您想要的封闭通用类型作为
DataType
为您的
DataTemplate
(这不是我自己的。它来自 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
ControlTemplate
。通常我有一个
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>

作为解决方法,我现在在

DataTemplate
Resources
中定义我的
FrameworkElement
,并将它们添加到代码隐藏中的
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);

现在我可以为所有封闭泛型类型定义

DataTemplate
并让 WPF 自动应用它们 =)

© www.soinside.com 2019 - 2024. All rights reserved.