|
| 1 | +XAML Custom Condition |
| 2 | +=== |
| 3 | + |
| 4 | +# Background |
| 5 | + |
| 6 | +This spec provides WinUI3 XAML applications with the ability to define custom conditionals. This extends the concept from |
| 7 | +the pre-existing [XAML conditionals](https://learn.microsoft.com/windows/uwp/debug-test-perf/conditional-xaml). |
| 8 | + |
| 9 | +XAML conditionals allow you to conditionally include or exclude markup based on runtime conditions. |
| 10 | +The platform provides built-in conditionals like `IsApiContractPresent`, `IsTypePresent`, and `IsPropertyPresent`. |
| 11 | + |
| 12 | +Custom conditionals address scenarios where you need conditional XAML based on: |
| 13 | +- Application-specific feature flags |
| 14 | +- Custom device capabilities |
| 15 | +- Business logic conditions |
| 16 | +- Configuration settings |
| 17 | +- Any other runtime condition specific to your application |
| 18 | + |
| 19 | +By implementing the `IXamlCondition` interface, you can create conditionals that work seamlessly with XAML's conditional namespace syntax, |
| 20 | +evaluated at runtime. |
| 21 | + |
| 22 | +# Conceptual pages (How To) |
| 23 | + |
| 24 | +The guidance in the below examples can be followed by developers for using custom conditionals in their WinUI3 XAML applications. |
| 25 | +Conditional evaluation for markup can be achieved for elements as well as their attributes. It can also be achieved for user controls, styles, |
| 26 | +storyboard, Grid,resources, and more. |
| 27 | + |
| 28 | +### Remarks |
| 29 | +* Any two elements cannot have the same x:Name, even if they are conditionally loaded. |
| 30 | +* If multiple conditions are applied to an element attribute, ensure that only one condition evaluates to true at runtime to avoid exceptions. |
| 31 | +* Custom conditionals are evaluated at runtime, so compiler checks for logical consistency cannot be performed. |
| 32 | +* Custom conditionals don't work with anything that is derived from 'xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"' namespace. |
| 33 | + For example: x:uid, x:Key etc. |
| 34 | +* For single conditionals with same args, the evaluate result is cached. The evaluate result during startup decides the loading of the elements. |
| 35 | + For example, if a TextBlock is conditionally loaded based on a custom conditional with argument "FeatureX" and the conditional evaluates to true during startup, |
| 36 | + the TextBlock will be loaded. Subsequent changes to the condition (e.g., toggling "FeatureX" off) will not affect the loading of the TextBlock. |
| 37 | + |
| 38 | + |
| 39 | +# API Pages |
| 40 | + |
| 41 | +_(Each of the following L2 sections correspond to a page that will be on docs.microsoft.com)_ |
| 42 | + |
| 43 | +## IXamlCondition interface |
| 44 | + |
| 45 | +IXamlCondition interface defines a custom conditional that can be used in XAML conditionals. This interface consists of a single method, |
| 46 | +Evaluate, which takes a string argument and returns a boolean value indicating the result of the conditional evaluation. This allows |
| 47 | +developers to implement their own logic for determining conditions in XAML based on application-specific requirements. A developer-defined |
| 48 | +class implementing this interface can then be referenced in XAML to control the inclusion or exclusion of markup |
| 49 | +based on the evaluation result. |
| 50 | + |
| 51 | + |
| 52 | +Example: |
| 53 | +```c# |
| 54 | +namespace CustomConditionNamespace |
| 55 | +{ |
| 56 | + public class MyCustomCondition : DependencyObject, Microsoft.UI.Xaml.Markup.IXamlCondition |
| 57 | + { |
| 58 | + public MyCustomCondition() |
| 59 | + { |
| 60 | + |
| 61 | + } |
| 62 | + public bool Evaluate(string argument) |
| 63 | + { |
| 64 | + if (argument == "ConditionOne" || argument == "ConditionThree" || argument == "ConditionDerived" ) |
| 65 | + return true; |
| 66 | + else |
| 67 | + return false; |
| 68 | + } |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | + |
| 74 | +```xaml |
| 75 | +<?xml version="1.0" encoding="utf-8"?> |
| 76 | +<Window |
| 77 | + x:Class="YourApplicationNamespace.MainWindow" |
| 78 | + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| 79 | + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| 80 | + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| 81 | + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| 82 | + xmlns:local="using:YourApplicationNamespace" |
| 83 | + xmlns:condition="using:CustomConditionNamespace" |
| 84 | + xmlns:condition1="http://schemas.microsoft.com/winfx/2006/xaml/presentation?condition:MyCustomCondition(ConditionOne)" |
| 85 | + xmlns:condition2="http://schemas.microsoft.com/winfx/2006/xaml/presentation?condition:MyCustomCondition(ConditionTwo)" |
| 86 | + xmlns:condition3="http://schemas.microsoft.com/winfx/2006/xaml/presentation?condition:MyCustomCondition(ConditionThree)" |
| 87 | + xmlns:condition4="http://schemas.microsoft.com/winfx/2006/xaml/presentation?condition:MyCustomCondition(ConditionFour)" |
| 88 | + xmlns:conditionderived="using:YourApplicationDerivedUserControlNamespace?condition:MyCustomCondition(ConditionDerived)" |
| 89 | + mc:Ignorable="d" |
| 90 | + Title="YourApplicationNamespace"> |
| 91 | + <Grid> |
| 92 | + <Grid.Resources> |
| 93 | + <!-- Style for Buttons --> |
| 94 | + <Style x:Key="CustomButtonStyle" TargetType="Button"> |
| 95 | + <condition1:Setter Property="Background" Value="PaleVioletRed"/> |
| 96 | + <condition2:Setter Property="Background" Value="DodgerBlue"/> |
| 97 | + <Setter Property="Foreground" condition1:Value="Black" condition2:Value="White"/> |
| 98 | + <Setter Property="Padding" Value="20,10"/> |
| 99 | + <Setter Property="CornerRadius" Value="5"/> |
| 100 | + </Style> |
| 101 | + </Grid.Resources> |
| 102 | + <StackPanel> |
| 103 | + <condition1:TextBlock Text="Hello, I am loaded if condition one is evaluated to true." x:Name="TextBlock1"/> |
| 104 | + <condition2:TextBlock Text="Hello, I am loaded if condition two is evaluated to true." x:Name="TextBlock2"/> |
| 105 | + <Button Content="My background is red if ConditionOne is true else it is purple if ConditionTwo is true. Both cannot be true at the same time." condition1:Background="Red" condition2:Background="Purple" x:Name="MyButton1" Click="OnClick"/> |
| 106 | + <Button Content="I am getting my style from CustomButtonStyle based on conditional evaluation." Style="{StaticResource CustomButtonStyle}" x:Name="MyButton2" Click="OnClick"/> |
| 107 | + <conditionderived:ButtonDerived/> |
| 108 | + </StackPanel> |
| 109 | + </Grid> |
| 110 | +</Window> |
| 111 | + |
| 112 | +``` |
| 113 | + |
| 114 | +In the above example, a custom conditional `MyCustomCondition` is defined in the `CustomConditionNamespace` namespace. |
| 115 | +While using custom conditionals, developers need to be cautious that more than one condition for element attributes like |
| 116 | +Background in the above example should not evaluate to true at the same time, as it may lead to unexpected behavior |
| 117 | +and cause an exception at runtime. Since the evaluation happens at runtime, compiler doesn't perform any sanity checks for such scenarios. |
| 118 | + |
| 119 | +More examples: |
| 120 | + |
| 121 | +```xaml |
| 122 | +<Grid.Resources> |
| 123 | + <!-- ControlTemplate for Button --> |
| 124 | + <!-- Condition1 for the custom template --> |
| 125 | + <condition1:ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button"> |
| 126 | + <Grid> |
| 127 | + <VisualStateManager.VisualStateGroups> |
| 128 | + <VisualStateGroup x:Name="CommonStates"> |
| 129 | + <VisualState x:Name="Normal"/> |
| 130 | + <VisualState x:Name="PointerOver"> |
| 131 | + <condition3:Storyboard> |
| 132 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background"> |
| 133 | + <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}"/> |
| 134 | + </ObjectAnimationUsingKeyFrames> |
| 135 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterElement" Storyboard.TargetProperty="Foreground"> |
| 136 | + <condition4:DiscreteObjectKeyFrame KeyTime="0" Value="Red"/> |
| 137 | + </ObjectAnimationUsingKeyFrames> |
| 138 | + </condition3:Storyboard> |
| 139 | + </VisualState> |
| 140 | + <VisualState x:Name="Pressed"> |
| 141 | + <Storyboard> |
| 142 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background"> |
| 143 | + <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}"/> |
| 144 | + </ObjectAnimationUsingKeyFrames> |
| 145 | + </Storyboard> |
| 146 | + </VisualState> |
| 147 | + </VisualStateGroup> |
| 148 | + </VisualStateManager.VisualStateGroups> |
| 149 | + <Border x:Name="RootBorder" |
| 150 | + Background="{TemplateBinding Background}" |
| 151 | + BorderBrush="{TemplateBinding BorderBrush}" |
| 152 | + BorderThickness="{TemplateBinding BorderThickness}" |
| 153 | + CornerRadius="8" |
| 154 | + Padding="{TemplateBinding Padding}"> |
| 155 | + <ContentPresenter x:Name="ContentPresenterElement" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" |
| 156 | + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> |
| 157 | + </Border> |
| 158 | + </Grid> |
| 159 | + </condition1:ControlTemplate> |
| 160 | + <!-- Condition2 for the custom template --> |
| 161 | + <condition2:ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button"> |
| 162 | + <Grid> |
| 163 | + <VisualStateManager.VisualStateGroups> |
| 164 | + <VisualStateGroup x:Name="CommonStates"> |
| 165 | + <VisualState x:Name="Normal"/> |
| 166 | + <VisualState x:Name="PointerOver"> |
| 167 | + <condition3:Storyboard> |
| 168 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background"> |
| 169 | + <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}"/> |
| 170 | + </ObjectAnimationUsingKeyFrames> |
| 171 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterElement" Storyboard.TargetProperty="Foreground"> |
| 172 | + <condition4:DiscreteObjectKeyFrame KeyTime="0" Value="Black"/> |
| 173 | + </ObjectAnimationUsingKeyFrames> |
| 174 | + </condition3:Storyboard> |
| 175 | + </VisualState> |
| 176 | + <VisualState x:Name="Pressed"> |
| 177 | + <Storyboard> |
| 178 | + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background"> |
| 179 | + <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}"/> |
| 180 | + </ObjectAnimationUsingKeyFrames> |
| 181 | + </Storyboard> |
| 182 | + </VisualState> |
| 183 | + </VisualStateGroup> |
| 184 | + </VisualStateManager.VisualStateGroups> |
| 185 | + <Border x:Name="RootBorder" |
| 186 | + Background="{TemplateBinding Background}" |
| 187 | + BorderBrush="{TemplateBinding BorderBrush}" |
| 188 | + BorderThickness="{TemplateBinding BorderThickness}" |
| 189 | + CornerRadius="8" |
| 190 | + Padding="{TemplateBinding Padding}"> |
| 191 | + <ContentPresenter x:Name="ContentPresenterElement" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" |
| 192 | + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> |
| 193 | + </Border> |
| 194 | + </Grid> |
| 195 | + </condition2:ControlTemplate> |
| 196 | +</Grid.Resources> |
| 197 | +``` |
| 198 | + |
| 199 | +## IXamlCondition members |
| 200 | + |
| 201 | +| Name | Description | |
| 202 | +|-|-| |
| 203 | +| Evaluate | Evaluate method accepts a string argument and returns a boolean value indicating the result of the conditional evaluation. | |
| 204 | + |
| 205 | +# API Details |
| 206 | + |
| 207 | +```c# (but really MIDL3) |
| 208 | +namespace Microsoft.UI.Xaml.Markup |
| 209 | +{ |
| 210 | + [contract(Microsoft.UI.Xaml.WinUIContract, 10)] |
| 211 | + [webhosthidden] |
| 212 | + interface IXamlCondition |
| 213 | + { |
| 214 | + Boolean Evaluate(String argument); |
| 215 | + }; |
| 216 | +} |
| 217 | +``` |
0 commit comments