条件 XAML

条件 XAML 提供直接在 XAML 标记中使用 ApiInformation.IsApiContractPresent 方法的方法。 可以基于 OS 级 API 协定的存在设置属性,而无需在后面编写代码。 条件语句在运行时进行求值——带有条件 XAML 标记的元素如果其求值结果为 true,则会被解析;否则会被忽略。

从 Windows 应用 SDK 2.0 开始,还可以通过实现 IXamlCondition 接口来定义自己的条件。 使用自定义条件,可以根据特定于应用的状态(例如功能标志或配置设置)包含或排除标记。 有关详细信息,请参阅 “创建自定义条件”。

条件 XAML 需要Windows 10版本 1809(内部版本 17763)或更高版本,这是Windows 应用 SDK应用的最低操作系统版本。

先决条件

Important

内置条件方法使用 ApiInformation 方法,这些方法用于检查是否存在由 OS 提供的 Windows 运行时(Windows.*)API 协定和类型。 这些检查不适用于 WinUI 3 (Microsoft.UI.Xaml.*) 控件,因为 WinUI 3 通过Windows 应用 SDK而不是 OS 随应用一起提供,因此,无论设备运行的Windows 10/11 版本如何,构建应用时都会始终存在每个 WinUI 3 控件。 #if预处理器指令在这里也帮不上忙:它们是在编译时根据目标框架来求值的,而不是在运行时根据操作系统或实际安装的 Windows 应用 SDK 版本来求值。 若要在应用当前据以运行的 Windows 应用 SDK 版本上控制某项功能,请在构建时检查 SDK 版本,或在 API 调用周围使用 try/catch。 有关详细信息,请参阅 版本自适应代码 。

有关 ApiInformation 和 API 合约的背景信息,请参阅 版本自适应应用。

条件命名空间

若要在 XAML 中使用条件方法,请在页面顶部声明条件 XAML 命名空间 :

xmlns:myNamespace="schema?conditionalMethod(parameter)"

分隔符前 ? 的内容是命名空间或架构。 ? 后面的内容是用于确定命名空间的求值结果是 true 还是 false 的条件方法。

在大多数情况下,架构是默认的 XAML 命名空间:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

条件 XAML 支持以下条件方法:

方法 逆
IsApiContractPresent(ContractName, VersionNumber) IsApiContractNotPresent(ContractName, VersionNumber)
IsTypePresent(ControlType) IsTypeNotPresent(ControlType)
IsPropertyPresent(ControlType, PropertyName) IsPropertyNotPresent(ControlType, PropertyName)

还可以使用在应用中定义的条件。 有关详细信息,请参阅“创建自定义条件”。

注释

使用 IsApiContractPresent 和 IsApiContractNotPresent 可获得最佳设计时体验。 在 Visual Studio 设计器中,其他条件语句尚未获得完全支持。

有条件地设置属性

此示例仅在应用在 Windows 10 版本 1903(2019 年 5 月更新,内部版本 18362)或更高版本上运行时,才会在 TextBlock 中显示文本 - 这种合约检查有意义,因为它比 Windows 应用 SDK 的 1809 最低版本更新。

首先,定义条件命名空间:

xmlns:contract8Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,8)"

然后,使用条件命名空间为属性添加前缀:

<TextBlock contract8Present:Text="Hello, Conditional XAML"/>

下面是完整的标记代码:

<Page
    x:Class="ConditionalTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:contract8Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,8)">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock contract8Present:Text="Hello, Conditional XAML"/>
    </Grid>
</Page>

代码隐藏文件中的等效检查:

TextBlock textBlock = new TextBlock();

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
    textBlock.Text = "Hello, Conditional XAML";
}

注释

即使 IsApiContractPresent 将字符串用作协定名称参数,在 XAML 命名空间声明中也不要将其用引号括起来。

由于 Windows 应用 SDK 支持的最低操作系统版本是 1809(UniversalApiContract版本 7),因此在 Windows 应用 SDK 应用中检查协定版本是否小于或等于 7,结果始终为 true,无法提供任何有用信息。 仅检查大于 7 的合约版本。

使用 if/else 条件

若要根据 API 协定设置不同的值,请同时定义正条件命名空间和负条件命名空间:

xmlns:contract8NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,8)"
xmlns:contract8Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,8)"

然后设置属性两次,每个属性具有不同的条件前缀。 运行时仅应用一个:

<TextBlock contract8NotPresent:Text="Hello, World"
           contract8Present:Text="Hello, May 2019 Update or later"/>

按条件实例化控件

注释

根据 OS API 协定(而不是有条件地设置属性)实例化元素是特定于 UWP 的模式。 它不适用于 WinUI 3 控件。

在 UWP 中,当较新的控件在 OS 上不可用时,可以使用此模式回退到较旧的 Windows.UI.Xaml.Controls 控件。 在Windows 应用 SDK应用中,WinUI 3 控件(Microsoft.UI.Xaml.Controls.*)通过Windows 应用 SDK(而不是 OS)随应用一起提供。 应用构建的每个控件(包括 ColorPicker )都保证在运行时存在,因此在实例化之前,无需检查操作系统版本。

如果需要根据应用所针对的 Windows 应用 SDK 版本来限制 WinUI 3 控件或 API 的使用,请在构建时进行该检查(通过将最低 Windows 应用 SDK 包版本设为目标版本),或者将运行时调用包装在 try/catch 中,而不要使用条件 XAML。

若要根据应用定义的逻辑(例如功能标志)包含或排除元素,请使用 自定义条件。

创建自定义条件

从 Windows 应用 SDK 2.0 开始,可以定义自己的条件并将其与条件命名空间语法一起使用。 自定义条件允许你根据特定于应用的状态包括或排除标记,例如功能标志、配置设置或设备功能。 与内置条件方法一样,在运行时解析 XAML 时,将评估自定义条件。

WinUI 3 示例集图标 WinUI 3 示例库应用包含 WinUI 控件和功能的交互式示例。 从 Microsoft Store 或浏览 GitHub 上的源代码获取应用。

注释

IXamlCondition 替代了 Windows 应用 SDK 2.0 实验版本中的 IXamlPredicate 接口。

实现条件

若要创建条件,请定义一个派生自 DependencyObject 并实现 IXamlCondition 的类。 XAML 分析程序调用 Evaluate 方法,并将条件命名空间声明的括号之间的文本作为 argument 参数传递。 返回 true 以包含限定标记或 false 忽略它。

此示例来自 WinUI 3 库示例,评估应用存储在字典中的功能标志:

using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Markup;

namespace MyApp;

public sealed partial class FeatureFlagCondition : DependencyObject, IXamlCondition
{
    public static IDictionary<string, bool> FeatureFlags { get; } = new Dictionary<string, bool>
    {
        ["NewExperience"] = true,
        ["LegacyMode"] = false,
    };

    // The XAML parser calls Evaluate with the text between the parentheses
    // in the conditional namespace declaration, for example "NewExperience".
    public bool Evaluate(string argument)
    {
        return FeatureFlags.TryGetValue(argument, out bool enabled) && enabled;
    }
}

为该条件声明条件命名空间

首先,使用 using: 声明映射包含条件类的命名空间。 然后,在条件命名空间限定符的 ? 分隔符之后,通过该前缀引用该类,并在括号中传递参数:

xmlns:cond="using:MyApp"
xmlns:newExp="http://schemas.microsoft.com/winfx/2006/xaml/presentation?cond:FeatureFlagCondition(NewExperience)"
xmlns:legacy="http://schemas.microsoft.com/winfx/2006/xaml/presentation?cond:FeatureFlagCondition(LegacyMode)"

在此示例中,分析程序调用 Evaluate("NewExperience") 处理使用 newExp 前缀的标记,并调用 Evaluate("LegacyMode") 处理使用 legacy 前缀的标记。

对元素、属性和 setter 使用条件

使用条件前缀的方式与使用内置条件命名空间的方式相同。 这些示例基于 WinUI 3 Gallery 示例。

若要仅在条件 true 为真时包含元素,请为元素添加前缀:

<StackPanel Spacing="8">
    <!-- Included only when FeatureFlagCondition.Evaluate("NewExperience") returns true. -->
    <newExp:InfoBar Title="New experience" IsOpen="True" Severity="Success"
                    Message="This InfoBar is included because the NewExperience flag is true." />

    <!-- Included only when FeatureFlagCondition.Evaluate("LegacyMode") returns true. -->
    <legacy:InfoBar Title="Legacy mode" IsOpen="True" Severity="Warning"
                    Message="This InfoBar is included because the LegacyMode flag is true." />
</StackPanel>

若要仅在条件为 true 时设置属性,请为该属性添加前缀:

<!-- Make sure that no more than one of these conditions is true at runtime. -->
<Button newExp:Content="Open the new editor"
        legacy:Content="Open the classic editor" />

若要仅当条件为 true 时应用样式设置器,请为 Setter 元素添加前缀:

<Page.Resources>
    <Style x:Key="ConditionalHeadingStyle" TargetType="TextBlock">
        <newExp:Setter Property="FontWeight" Value="SemiBold" />
        <newExp:Setter Property="FontSize" Value="28" />
        <legacy:Setter Property="FontWeight" Value="Normal" />
        <legacy:Setter Property="FontSize" Value="18" />
    </Style>
</Page.Resources>

在分析 XAML 之前设置状态

在进程生命周期内,分析程序会针对每种条件和参数组合缓存 Evaluate 的结果。 如果在结果被缓存之后更改了条件所读取的状态,那么已经解析的标记不会改变,而之后解析的标记仍会使用缓存的结果。 若要使用不同的值,请重启应用。

请在解析器评估条件之前设置该条件所读取的状态,例如在应用启动时,或在页面构造函数中调用 InitializeComponent 之前:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        // Set the flags before InitializeComponent so that the XAML parser
        // uses these values when it evaluates the conditions.
        FeatureFlagCondition.FeatureFlags["NewExperience"] = true;
        FeatureFlagCondition.FeatureFlags["LegacyMode"] = false;

        this.InitializeComponent();
    }
}

Limitations

  • 两个元素不能具有相同的 x:Name,即使其中只有一个会在运行时包含在内。
  • 如果将多个条件应用于同一属性,请确保在运行时只有其中一个条件的求值结果为 true。 如果多个计算结果为 true,分析器将引发异常。
  • 由于条件在运行时评估,因此 XAML 编译器无法检查它们是否具有逻辑一致性。 测试应用支持的条件值的每个组合。
  • 自定义条件不适用于来自 http://schemas.microsoft.com/winfx/2006/xaml 命名空间(x:Uid)的项,例如 x:Key 和 x:。