Skip to content

Commit 4f6e2a1

Browse files
Introducing SwitchConverter! 🦙❤️
Migrate core logic from SwitchPresenter and generlize to share between both SwitchPresenter and SwitchConverter Added small demo of SwitchConverter to SwitchPresenter docs Tested on UWP, WinUI 3, and Uno Platform
1 parent bd87041 commit 4f6e2a1

File tree

6 files changed

+295
-119
lines changed

6 files changed

+295
-119
lines changed

components/Primitives/samples/SwitchPresenter.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@ We can also invert the paradigm a bit with a `SwitchPresenter` to do data transf
3535
> [!SAMPLE SwitchPresenterTemplateSample]
3636
3737
That's right! `SwitchPresenter` can be used not just for displaying different UIElements but in feeding different kinds of data into the `ContentTemplate` as well.
38+
39+
## SwitchConverter
40+
41+
A new analog to `SwitchPresenter` is the `SwitchConverter` which can be used in bindings to translate values into resources:
42+
43+
> [!SAMPLE SwitchConverterBrushSample]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<Page x:Class="PrimitivesExperiment.Samples.SwitchPresenter.SwitchConverterBrushSample"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
5+
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:local="using:PrimitivesExperiment.Samples.SwitchPresenter"
8+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9+
xmlns:ui="using:CommunityToolkit.WinUI"
10+
mc:Ignorable="d">
11+
12+
<Page.Resources>
13+
<!--
14+
If you reference an enum directly in UWP, you need to use it somewhere for the XamlTypeInfo reference to be generated...
15+
-->
16+
<local:CheckStatus x:Key="MyChecks">Warning</local:CheckStatus>
17+
18+
<!-- SwitchConverter lets you easily convert general values to resources -->
19+
<!-- Note: This is in the converters namespace -->
20+
<converters:SwitchConverter x:Key="StatusToColorSwitchConverter"
21+
TargetType="local:CheckStatus">
22+
<!-- Note: These are reused from the controls namespace from SwitchPresenter -->
23+
<controls:Case Content="{ThemeResource SystemFillColorCriticalBrush}"
24+
Value="Error" />
25+
<controls:Case Content="{ThemeResource SystemFillColorCautionBrush}"
26+
Value="Warning" />
27+
<controls:Case Content="{ThemeResource SystemFillColorSuccessBrush}"
28+
Value="Success" />
29+
</converters:SwitchConverter>
30+
</Page.Resources>
31+
32+
<StackPanel Spacing="8">
33+
<ComboBox x:Name="StatusPicker"
34+
Header="Pick a status"
35+
SelectedIndex="0">
36+
<x:String>Error</x:String>
37+
<x:String>Warning</x:String>
38+
<x:String>Success</x:String>
39+
</ComboBox>
40+
<TextBlock Text="This is it, this is the demo:" />
41+
<TextBlock FontWeight="SemiBold"
42+
Foreground="{x:Bind StatusPicker.SelectedItem, Converter={StaticResource StatusToColorSwitchConverter}, Mode=OneWay}"
43+
Text="{x:Bind StatusPicker.SelectedItem, Mode=OneWay}" />
44+
</StackPanel>
45+
</Page>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.WinUI.Converters;
6+
7+
namespace PrimitivesExperiment.Samples.SwitchPresenter;
8+
9+
[ToolkitSample(id: nameof(SwitchConverterBrushSample), "SwitchConverter Brush", description: $"A sample for showing how to use a {nameof(SwitchConverter)} for swapping a brush based on an enum.")]
10+
public sealed partial class SwitchConverterBrushSample : Page
11+
{
12+
public SwitchConverterBrushSample()
13+
{
14+
this.InitializeComponent();
15+
}
16+
}
17+
18+
public enum CheckStatus
19+
{
20+
Error,
21+
Warning,
22+
Success,
23+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.WinUI.Controls;
6+
7+
namespace CommunityToolkit.WinUI.Converters;
8+
9+
/// <summary>
10+
/// A helper <see cref="IValueConverter"/> which can automatically translate incoming data to a set of resulting values defined in XAML.
11+
/// </summary>
12+
/// <example>
13+
/// &lt;converters:SwitchConverter x:Key=&quot;StatusToColorSwitchConverter&quot;
14+
/// TargetType=&quot;models:CheckStatus&quot;&gt;
15+
/// &lt;controls:Case Value=&quot;Error&quot; Content=&quot;{ThemeResource SystemFillColorCriticalBrush}&quot;/&gt;
16+
/// &lt;controls:Case Value=&quot;Warning&quot; Content=&quot;{ThemeResource SystemFillColorCautionBrush}&quot;/&gt;
17+
/// &lt;controls:Case Value=&quot;Success&quot; Content=&quot;{ThemeResource SystemFillColorSuccessBrush}&quot;/&gt;
18+
/// &lt;/converters:SwitchConverter&gt;
19+
/// ...
20+
/// &lt;TextBlock
21+
/// FontWeight=&quot;SemiBold&quot;
22+
/// Foreground=&quot;{x:Bind Status, Converter={StaticResource StatusToColorSwitchConverter}}&quot;
23+
/// Text = &quot;{x:Bind Status}&quot; /&gt;
24+
/// </example>
25+
[ContentProperty(Name = nameof(SwitchCases))]
26+
public sealed partial class SwitchConverter : DependencyObject, IValueConverter
27+
{
28+
/// <summary>
29+
/// Gets or sets a value representing the collection of cases to evaluate.
30+
/// </summary>
31+
public CaseCollection SwitchCases
32+
{
33+
get { return (CaseCollection)GetValue(SwitchCasesProperty); }
34+
set { SetValue(SwitchCasesProperty, value); }
35+
}
36+
37+
/// <summary>
38+
/// Identifies the <see cref="SwitchCases"/> property.
39+
/// </summary>
40+
public static readonly DependencyProperty SwitchCasesProperty =
41+
DependencyProperty.Register(nameof(SwitchCases), typeof(CaseCollection), typeof(SwitchConverter), new PropertyMetadata(null));
42+
43+
/// <summary>
44+
/// Gets or sets a value indicating which type to first cast and compare provided values against.
45+
/// </summary>
46+
public Type TargetType
47+
{
48+
get { return (Type)GetValue(TargetTypeProperty); }
49+
set { SetValue(TargetTypeProperty, value); }
50+
}
51+
52+
/// <summary>
53+
/// Identifies the <see cref="TargetType"/> property.
54+
/// </summary>
55+
public static readonly DependencyProperty TargetTypeProperty =
56+
DependencyProperty.Register(nameof(TargetType), typeof(Type), typeof(SwitchConverter), new PropertyMetadata(null));
57+
58+
/// <summary>
59+
/// Initializes a new instance of the <see cref="SwitchPresenter"/> class.
60+
/// </summary>
61+
public SwitchConverter()
62+
{
63+
// Note: we need to initialize this here so that XAML can automatically add cases without needing this defined around it as the content.
64+
// We don't do this in the PropertyMetadata as then we create a static shared collection for all converters, which we don't want. We want it per instance.
65+
// See https://learn.microsoft.com/windows/uwp/xaml-platform/custom-dependency-properties#initializing-the-collection
66+
SwitchCases = new CaseCollection();
67+
}
68+
69+
public object Convert(object value, Type targetType, object parameter, string language)
70+
{
71+
var result = SwitchCases.EvaluateCases(value, TargetType ?? targetType);
72+
73+
return result?.Content!;
74+
}
75+
76+
public object ConvertBack(object value, Type targetType, object parameter, string language)
77+
{
78+
throw new NotImplementedException();
79+
}
80+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.WinUI.Converters;
6+
7+
namespace CommunityToolkit.WinUI.Controls;
8+
9+
/// <summary>
10+
/// Internal helpers for use between <see cref="SwitchPresenter"/> and <see cref="SwitchConverter"/>.
11+
/// The logic here is the main code which looks across a <see cref="CaseCollection"/> to match a specific <see cref="Case"/> with a given value while converting types based on the <see cref="SwitchPresenter.TargetType"/> property. This will handle <see cref="Enum"/> values as well as values compatible with the <see cref="XamlBindingHelper.ConvertValue(Type, object)"/> method.
12+
/// </summary>
13+
internal static partial class SwitchHelpers
14+
{
15+
/// <summary>
16+
/// Extension method for a set of cases to find the matching case given its value and type.
17+
/// </summary>
18+
/// <param name="switchCases">The collection of <see cref="Case"/>s in a <see cref="CaseCollection"/></param>
19+
/// <param name="value">The value of the <see cref="Case"/> to find</param>
20+
/// <param name="targetType">The desired type of the result for automatic conversion</param>
21+
/// <returns>The discovered value, the default value, or <c>null</c></returns>
22+
internal static Case? EvaluateCases(this CaseCollection switchCases, object value, Type targetType)
23+
{
24+
if (switchCases == null ||
25+
switchCases.Count == 0)
26+
{
27+
// If we have no cases, then we can't match anything.
28+
return null;
29+
}
30+
31+
Case? xdefault = null;
32+
Case? newcase = null;
33+
34+
foreach (Case xcase in switchCases)
35+
{
36+
if (xcase.IsDefault)
37+
{
38+
// If there are multiple default cases provided, this will override and just grab the last one, the developer will have to fix this in their XAML. We call this out in the case comments.
39+
xdefault = xcase;
40+
continue;
41+
}
42+
43+
if (CompareValues(value, xcase.Value, targetType))
44+
{
45+
newcase = xcase;
46+
break;
47+
}
48+
}
49+
50+
if (newcase == null && xdefault != null)
51+
{
52+
// Inject default if we found one without matching anything
53+
newcase = xdefault;
54+
}
55+
56+
return newcase;
57+
}
58+
59+
/// <summary>
60+
/// Compares two values using the TargetType.
61+
/// </summary>
62+
/// <param name="compare">Our main value in our SwitchPresenter.</param>
63+
/// <param name="value">The value from the case to compare to.</param>
64+
/// <returns>true if the two values are equal</returns>
65+
internal static bool CompareValues(object compare, object value, Type targetType)
66+
{
67+
if (compare == null || value == null)
68+
{
69+
return compare == value;
70+
}
71+
72+
if (targetType == null ||
73+
(targetType == compare.GetType() &&
74+
targetType == value.GetType()))
75+
{
76+
// Default direct object comparison or we're all the proper type
77+
return compare.Equals(value);
78+
}
79+
else if (compare.GetType() == targetType)
80+
{
81+
// If we have a TargetType and the first value is the right type
82+
// Then our 2nd value isn't, so convert to string and coerce.
83+
var valueBase2 = ConvertValue(targetType, value);
84+
85+
return compare.Equals(valueBase2);
86+
}
87+
88+
// Neither of our two values matches the type so
89+
// we'll convert both to a String and try and coerce it to the proper type.
90+
var compareBase = ConvertValue(targetType, compare);
91+
92+
var valueBase = ConvertValue(targetType, value);
93+
94+
return compareBase.Equals(valueBase);
95+
}
96+
97+
/// <summary>
98+
/// Helper method to convert a value from a source type to a target type.
99+
/// </summary>
100+
/// <param name="targetType">The target type</param>
101+
/// <param name="value">The value to convert</param>
102+
/// <returns>The converted value</returns>
103+
internal static object ConvertValue(Type targetType, object value)
104+
{
105+
if (targetType.IsInstanceOfType(value))
106+
{
107+
return value;
108+
}
109+
else if (targetType.IsEnum && value is string str)
110+
{
111+
#if HAS_UNO
112+
if (Enum.IsDefined(targetType, str))
113+
{
114+
return Enum.Parse(targetType, str);
115+
}
116+
#else
117+
if (Enum.TryParse(targetType, str, out object? result))
118+
{
119+
return result!;
120+
}
121+
#endif
122+
123+
static object ThrowExceptionForKeyNotFound()
124+
{
125+
throw new InvalidOperationException("The requested enum value was not present in the provided type.");
126+
}
127+
128+
return ThrowExceptionForKeyNotFound();
129+
}
130+
else
131+
{
132+
return XamlBindingHelper.ConvertValue(targetType, value);
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)