c# - UWP 中基于类的条件 xaml 布局

标签 c# uwp uwp-xaml

我有一个具有继承性的数据模型,我想在我的 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 的数据上下文将是模型类型的字段。每个模型都有我想要显示的不同字段,但 xaml 的其余部分将是相同的,所以我希望我可以避免创建两个 View 。我可以创建一个将类转换为可见性的转换器,但我认为这不是最好的解决方案。 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>

对于看起来像你问题中的示例的类来说,上面的内容可能有点过分了。但对于更复杂的 View 模型,这种方法效果很好。派生类可以重用基类模板,但可以对模板的呈现方式进行一些控制(通过能够将 ContentControl 放置在模板中需要的任何位置)。

除了允许在任何派生类模板中重用基类模板之外,这还避免了需要包含具有所有可能 View 模型绑定(bind)的元素的单个模板。这种方法不仅会导致运行时视觉树过重,而且还会出现大量绑定(bind)错误,因为隐藏元素仍会尝试绑定(bind)到 View 模型上不存在的属性。

在派生类模板中重用基类模板可以避免这一切,并且对我来说更适合 View 模型类继承的一般体系结构。


请注意,这与在 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 ...})。

文档令人着迷hints at the possibility of using automatic lookup [强调我的]:

All resources need to have a key. Usually that key is a string defined with x:Key=”myString”. However, there are a few other ways to specify a key:

  • Style and ControlTemplate require a TargetType, and will use the TargetType as the key if x:Key is not specified. In this case, the key is the actual Type object, not a string. (See examples below)
  • DataTemplate resources that have a TargetType will use the TargetType as the key if x:Key is not specified. In this case, the key is the actual Type object, not a string.
  • x:Name can be used instead of x:Key. However, x:Name also generates a code behind field for the resource. As a result, x:Name is less efficient than x:Key because that field needs to be initialized when the page is loaded.

但是 XAML 编辑器和编译器无法识别 DataTemplate.TargetType 属性,the DataTemplate class documentation 中没有提及它。 ,x:DataType 并不能避免仍然需要为资源定义 x:Key 属性,而且我没有看到使用实际 的方法>明确键入引用作为资源键。

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

因此上面的 UWP 示例使用了在需要时显式编码的简单 {StaticResource ...} 引用。

当然,UWP 中的另一个选项是使用 DataTemplateSelector,这是 UWP 中似乎仍受支持的 WPF 功能。这是一个相关问题,其中包括使用选择器的一种方式示例:UWP DataTemplates for multiple item types in ListView 。当然,还有许多其他合理的方法来初始化和使用 DataTemplateSelector。当 XAML 自动支持的行为不够时,它基本上是一种后备措施,并且在实现一种行为时,您可以这样做,但对您来说最有意义。

关于c# - UWP 中基于类的条件 xaml 布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40445037/

相关文章:

c# - UWP SQLite 查询结果类包含另一个类

c# - UWP 导航查看同一类型的多个页面

c# - 'using' 有什么优势吗?

c# - 无法绑定(bind)到我的自定义控件中的命令

c# - 为什么 "An unhandled exception of type ' System.IO.IOException' occurred in mscorlib.dll (locked by some .exe)”发生?

uwp - 如何为 uwp 应用程序创建 .exe/.msi

c# - UWP App 不显示图片

xaml - UWP Light 关闭 ContentDialog

c# - 更改控制台颜色在多线程应用程序中无法正常工作

c# - 系统.IO.FileNotFoundException : Could not load file or assembly 'X' or one of its dependencies when deploying the application