有多种方法可以解决这个问题。但对我而言,最简单也是最合乎逻辑的就是像往常一样创建DataTemplate
资源,但让更多派生类的模板使用基类的模板。
举例来说,像这样给出的模型类:
class MainModel
{
public Model BaseModel { get; set; }
public ModelOne ModelOne { get; set; }
public ModelTwo ModelTwo { get; set; }
}
class Model
{
public int BaseValue { get; set; }
}
class ModelOne : Model
{
public int OneValue { get; set; }
}
class ModelTwo : Model
{
public int TwoValue { get; set; }
}
您可以编写XAML看起来像这样:
<Page
x:Class="TestSO40445037UwpTemplateInherit.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="using:TestSO40445037UwpTemplateInherit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<l:MainModel>
<l:MainModel.BaseModel>
<l:Model BaseValue="17"/>
</l:MainModel.BaseModel>
<l:MainModel.ModelOne>
<l:ModelOne BaseValue="19" OneValue="29"/>
</l:MainModel.ModelOne>
<l:MainModel.ModelTwo>
<l:ModelTwo BaseValue="23" TwoValue="37"/>
</l:MainModel.ModelTwo>
</l:MainModel>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="baseModelTemplate" x:DataType="l:Model">
<TextBlock Text="{Binding BaseValue}"/>
</DataTemplate>
<DataTemplate x:Key="modelOneTemplate" x:DataType="l:ModelOne">
<StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource baseModelTemplate}"/>
<TextBlock Text="{Binding OneValue}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="modelTwoTemplate" x:DataType="l:ModelTwo">
<StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource baseModelTemplate}"/>
<TextBlock Text="{Binding TwoValue}"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentControl Content="{Binding BaseModel}" Margin="5"
ContentTemplate="{StaticResource baseModelTemplate}"/>
<ContentControl Content="{Binding ModelOne}" Margin="5"
ContentTemplate="{StaticResource modelOneTemplate}"/>
<ContentControl Content="{Binding ModelTwo}" Margin="5"
ContentTemplate="{StaticResource modelTwoTemplate}"/>
</StackPanel>
</Grid>
</Page>
以上可能是矫枉过正看起来字面上就像班在你的问题中的例子。但对于更复杂的视图模型,这很有效。派生类可以重用基类模板,但可以控制该模板的呈现方式(可以将ContentControl
放在模板所需的任何位置)。
除了允许在任何派生类模板中重用基类模板外,还可以避免需要包含具有所有可能视图模型绑定元素的单个模板。这种方法不仅会在运行时导致过重的视觉树,而且还会导致很多绑定错误,因为隐藏的元素仍然会尝试绑定到视图模型上的非现有属性。
在派生类模板中重用基类模板避免了这一切,对我来说更适合视图模型类继承的一般体系结构。
注意,这是它会在WPF做的方式有所不同:
<DataTemplate DataType="{x:Type lm:Model}">
<!-- template definition here...for example: -->
<StackPanel>
<TextBlock Text="{Binding Id, StringFormat=Id: {0}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type lm:ModelOne}">
<!-- template for ModelOne here; a ContentControl as shown below should be placed
in the as needed for your desired visual appearance. For example,
here is a template using a StackPanel as the top-level element,
with the base class template shown as the first item in the panel -->
<StackPanel>
<ContentControl Content="{Binding}" Focusable="False">
<ContentControl.ContentTemplate>
<StaticResourceExtension>
<StaticResourceExtension.ResourceKey>
<DataTemplateKey DataType="{x:Type lm:Model}"/>
</StaticResourceExtension.ResourceKey>
</StaticResourceExtension>
</ContentControl.ContentTemplate>
</ContentControl>
<TextBlock Text="{Binding Tasks, StringFormat=Tasks: {0}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type lm:ModelTwo}">
<!-- template for ModelTwo here; same as above -->
<StackPanel>
<ContentControl Content="{Binding}" Focusable="False">
<ContentControl.ContentTemplate>
<StaticResourceExtension>
<StaticResourceExtension.ResourceKey>
<DataTemplateKey DataType="{x:Type lm:Model}"/>
</StaticResourceExtension.ResourceKey>
</StaticResourceExtension>
</ContentControl.ContentTemplate>
</ContentControl>
<TextBlock Text="{Binding date, StringFormat=date: {0}}"/>
</StackPanel>
</DataTemplate>
(其中,当然,lm:
是你的模型类的类型无论你的实际的XML命名空间)
不幸的是,它看起来像许多其他有用的WPF功能一样,UWP(以及WinRT,Phone,Silverlight等)缺少自动数据模板资源键定义和查找。 WPF示例利用了这一点,即使使用模型类型作为基类数据模板资源引用的关键字。
在UWP,似乎所有的数据模板资源必须明确给出的密钥,并且可以显式引用,内联到一个模板性质(例如ContentTemplate
或ItemTemplate
),或经由资源引用(例如{Static Resource ...}
)。
文档诱人hints at the possibility of using automatic lookup [重点煤矿]:
所有资源都需要有一个关键。通常这个键是一个用x:Key =“myString”定义的字符串。但是,还有其他几种指定键的方法:
- Style和ControlTemplate需要一个TargetType,并且如果未指定x:Key,将使用TargetType作为键。在这种情况下,键是实际的Type对象,而不是字符串。 (请参阅下面的示例)
- 如果未指定x:Key,则具有TargetType的DataTemplate资源将使用TargetType作为键。在这种情况下,键是实际的Type对象,而不是字符串。
- x:名称可以用来代替x:Key。但是,x:Name还会为该资源的字段生成代码。因此,x:Name比x:Key效率低,因为该页面在加载页面时需要初始化该字段。
但XAML编辑器和编译器不能识别DataTemplate.TargetType
财产,有一个在the DataTemplate
class documentation没有提到它,x:DataType
没有避免需要仍然定义x:Key
属性的资源,我不没有看到明确使用实际的Type
引用作为资源键的方法。
我只能推测文档页面实际上是不正确的。也许一些懒惰的科技作家只是复制/粘贴WPF?我不知道。
所以上面的UWP例子只需要在简单的{StaticResource ...}
引用编码。
当然,UWP中的另一个选项是使用DataTemplateSelector
,这是WPF功能,似乎仍然在UWP中受支持。这里有一个相关的问题,其中包括使用选择器单向的示例:UWP DataTemplates for multiple item types in ListView。当然,还有许多其他合理的方式来初始化和使用DataTemplateSelector
。当XAML自动支持的行为不足时,这基本上就是回退,并且在实施时,您可以做到这一点,但对您来说最有意义。
这似乎不适用于UWP。 DataType不被识别为DataTemplate上的成员。 x:DataType似乎可以工作,但Type是“不支持在Windows通用项目中”。 –
@Kristoffer:对不起,我没有太多的UWP,并没有意识到它不像WPF那样做数据模板资源。请参阅上面的修订答案,使用相同的技术,但使用UWP习语。 –
谢谢,这是很多处理,但我设法创建模板,扩展基本模板,正如你在答案中所建议的。这提高了我的标记了很多。不过,我不知道我应该如何获得正确的模板。我最终没有使用x:Datatype,但我在代码隐藏中创建了一个TemplateSelector,它根据类返回了适当的模板。 –