数据模板和泛型
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>
定义为DataTemplate
的DataType
?
如果你知道答案,你可以停止阅读。但由于展示我已经做过的事情是一种很好的做法,所以我继续写作。
我已经做了什么?
起初我尝试了蛮力并尝试了几种类似于以下的组合:
- 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
并查看了 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
。因为此 class 具有使用 `- 字符的 MangleGenericTypeName
方法。
但是我仍然看不到提取类型参数的代码,所以我看不到为字典指定类型参数的正确语法。这就是我卡住的地方。
(不用说 Microsoft 文档在这个主题上毫无价值。)
编辑: 既然不清楚为什么要这个,我就解释一下:我要自动选择一个ContentTemplate
of the ContentControl
.当然:我在示例中构造的 DataTemplate
非常简单。但是每个人都应该能够想象,我想要不同的数据模板用于列表、字典或简单的字符串。
我有一个 ViewModel,它有一个 public object Result { get; }
属性。有时,结果是一个 int,有时是一个字符串,有时是一个 List 等等。我将此 Result
-属性 绑定到 ContentControl
的 Content
-属性。对于提到的所有类型,我编写了不同的 DataTemplates,它们由 WPF 自动选择。所以 int
s 显示在 Rectangle
中,String
s 显示在 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。
但在我的项目中,我有一个专门用于编写所有 DataTemplate
s 和 ControlTemplate
s 样式的程序集。通常我有一个 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>
作为解决方法,我现在在 FrameworkElement
的 Resources
中定义我的 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);
现在我可以为所有封闭的泛型定义 DataTemplate
s 并让 WPF 自动应用它们 =)
我已经阅读了将近一千篇文章,解释说在 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>
定义为DataTemplate
的DataType
?
如果你知道答案,你可以停止阅读。但由于展示我已经做过的事情是一种很好的做法,所以我继续写作。 我已经做了什么? 起初我尝试了蛮力并尝试了几种类似于以下的组合:
- 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
并查看了 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
。因为此 class 具有使用 `- 字符的 MangleGenericTypeName
方法。
但是我仍然看不到提取类型参数的代码,所以我看不到为字典指定类型参数的正确语法。这就是我卡住的地方。
(不用说 Microsoft 文档在这个主题上毫无价值。)
编辑: 既然不清楚为什么要这个,我就解释一下:我要自动选择一个ContentTemplate
of the ContentControl
.当然:我在示例中构造的 DataTemplate
非常简单。但是每个人都应该能够想象,我想要不同的数据模板用于列表、字典或简单的字符串。
我有一个 ViewModel,它有一个 public object Result { get; }
属性。有时,结果是一个 int,有时是一个字符串,有时是一个 List 等等。我将此 Result
-属性 绑定到 ContentControl
的 Content
-属性。对于提到的所有类型,我编写了不同的 DataTemplates,它们由 WPF 自动选择。所以 int
s 显示在 Rectangle
中,String
s 显示在 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。
但在我的项目中,我有一个专门用于编写所有 DataTemplate
s 和 ControlTemplate
s 样式的程序集。通常我有一个 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>
作为解决方法,我现在在 FrameworkElement
的 Resources
中定义我的 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);
现在我可以为所有封闭的泛型定义 DataTemplate
s 并让 WPF 自动应用它们 =)