Skip to content

Commit b1fee8a

Browse files
API spec for XAML custom conditional feature in WinUI3 (#10986)
1 parent 15af68e commit b1fee8a

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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

Comments
 (0)