UWP中基于类的条件xaml布局

本文关键字:条件 xaml 布局 于类 UWP | 更新日期: 2023-09-27 18:16:43

我有一个继承的数据模型,我想在我的xaml标记中显示每个子类的正确字段。

public abstract class Model {
    public int Id { set; get; }
}
public class ModelOne : Model {
    public int Tasks { set; get; }
}
public class ModelTwo : Model {
    public DateTime date { set; get; }
}

我的xaml的数据上下文将是一个Model类型的字段。每个模型都有我想要显示的不同字段,但xaml的其余部分将是相同的,因此我希望可以避免创建两个视图。我可以创建一个转换器,将类转换为可见性,但我不认为这将是最好的解决方案。在UWP-xaml中有什么功能可以帮助我实现这一点吗?

UWP中基于类的条件xaml布局

有多种方法可以实现这一点。但是对我来说,最简单和最合乎逻辑的方法是像往常一样创建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中,似乎所有数据模板资源都必须显式地给定一个键,并显式地引用,要么内联到模板属性(例如ContentTemplateItemTemplate),要么通过资源引用(例如{Static Resource ...})。

文档诱人地暗示了使用自动查找的可能性:

所有资源都需要有一个键。通常这个键是一个用x: key = " myString "定义的字符串。但是,还有一些其他的方法来指定键:

  • Style和ControlTemplate需要一个TargetType,如果没有指定x: key,则使用TargetType作为键。在本例中,键是实际的Type对象,而不是字符串。(见下面的例子)
  • 具有TargetType的DataTemplate资源将使用TargetType作为key,如果x: key没有指定。在这里,键是实际的Type对象,而不是字符串。
  • x:Name可以代替x:Key。但是,x:Name也为资源生成一个代码隐藏字段。因此,x:Name比x:Key效率低,因为该字段需要在加载页面时初始化。

但是XAML编辑器和编译器不识别DataTemplate.TargetType属性,在DataTemplate类文档中没有提到它,x:DataType并没有避免为资源定义x:Key属性的需要,并且我没有看到显式使用实际Type引用作为资源键的方法。

我只能猜测文档页实际上是不正确的。也许一些懒惰的科技作家只是从WPF复制/粘贴?我不知道。

所以上面的UWP示例在需要的地方显式地使用了简单的{StaticResource ...}引用。

当然,UWP中的另一个选择是使用DataTemplateSelector,这是一个在UWP中似乎仍然支持的WPF特性。这里有一个相关的问题,其中包括一个示例使用选择器的一种方式:UWP DataTemplates在ListView中用于多个项目类型。当然,还有许多其他合理的方法来初始化和使用DataTemplateSelector。当XAML中自动支持的行为不够时,它基本上是一种退路,当实现一个行为时,您可以根据自己的需要来做。