有多種方法可以解決這個問題。但對我而言,最簡單也是最合乎邏輯的就是像往常一樣創建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,它根據類返回了適當的模板。 –