-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
7349a30
commit 4ebc5a3
Showing
6 changed files
with
293 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<Page x:Class="PrimitivesExperiment.Samples.SwitchPresenter.SwitchConverterBrushSample" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:controls="using:CommunityToolkit.WinUI.Controls" | ||
xmlns:converters="using:CommunityToolkit.WinUI.Converters" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:local="using:PrimitivesExperiment.Samples.SwitchPresenter" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
xmlns:ui="using:CommunityToolkit.WinUI" | ||
mc:Ignorable="d"> | ||
|
||
<Page.Resources> | ||
<!-- | ||
If you reference an enum directly in UWP, you need to use it somewhere for the XamlTypeInfo reference to be generated... | ||
--> | ||
<local:CheckStatus x:Key="MyChecks">Warning</local:CheckStatus> | ||
|
||
<!-- SwitchConverter lets you easily convert general values to resources --> | ||
<!-- Note: This is in the converters namespace --> | ||
<converters:SwitchConverter x:Key="StatusToColorSwitchConverter" | ||
TargetType="local:CheckStatus"> | ||
<!-- Note: These are reused from the controls namespace from SwitchPresenter --> | ||
<controls:Case Content="{ThemeResource SystemFillColorCriticalBrush}" | ||
Value="Error" /> | ||
<controls:Case Content="{ThemeResource SystemFillColorCautionBrush}" | ||
Value="Warning" /> | ||
<controls:Case Content="{ThemeResource SystemFillColorSuccessBrush}" | ||
Value="Success" /> | ||
</converters:SwitchConverter> | ||
</Page.Resources> | ||
|
||
<StackPanel Spacing="8"> | ||
<ComboBox x:Name="StatusPicker" | ||
Header="Pick a status" | ||
SelectedIndex="0"> | ||
<x:String>Error</x:String> | ||
<x:String>Warning</x:String> | ||
<x:String>Success</x:String> | ||
</ComboBox> | ||
<TextBlock Text="This is it, this is the demo:" /> | ||
<TextBlock FontWeight="SemiBold" | ||
Foreground="{x:Bind StatusPicker.SelectedItem, Converter={StaticResource StatusToColorSwitchConverter}, Mode=OneWay}" | ||
Text="{x:Bind StatusPicker.SelectedItem, Mode=OneWay}" /> | ||
</StackPanel> | ||
</Page> |
23 changes: 23 additions & 0 deletions
23
components/Primitives/samples/SwitchPresenter/SwitchConverterBrushSample.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using CommunityToolkit.WinUI.Converters; | ||
|
||
namespace PrimitivesExperiment.Samples.SwitchPresenter; | ||
|
||
[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.")] | ||
public sealed partial class SwitchConverterBrushSample : Page | ||
{ | ||
public SwitchConverterBrushSample() | ||
{ | ||
this.InitializeComponent(); | ||
} | ||
} | ||
|
||
public enum CheckStatus | ||
{ | ||
Error, | ||
Warning, | ||
Success, | ||
} |
80 changes: 80 additions & 0 deletions
80
components/Primitives/src/SwitchPresenter/SwitchConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using CommunityToolkit.WinUI.Controls; | ||
|
||
namespace CommunityToolkit.WinUI.Converters; | ||
|
||
/// <summary> | ||
/// A helper <see cref="IValueConverter"/> which can automatically translate incoming data to a set of resulting values defined in XAML. | ||
/// </summary> | ||
/// <example> | ||
/// <converters:SwitchConverter x:Key="StatusToColorSwitchConverter" | ||
/// TargetType="models:CheckStatus"> | ||
/// <controls:Case Value="Error" Content="{ThemeResource SystemFillColorCriticalBrush}"/> | ||
/// <controls:Case Value="Warning" Content="{ThemeResource SystemFillColorCautionBrush}"/> | ||
/// <controls:Case Value="Success" Content="{ThemeResource SystemFillColorSuccessBrush}"/> | ||
/// </converters:SwitchConverter> | ||
/// ... | ||
/// <TextBlock | ||
/// FontWeight="SemiBold" | ||
/// Foreground="{x:Bind Status, Converter={StaticResource StatusToColorSwitchConverter}}" | ||
/// Text = "{x:Bind Status}" /> | ||
/// </example> | ||
[ContentProperty(Name = nameof(SwitchCases))] | ||
public sealed partial class SwitchConverter : DependencyObject, IValueConverter | ||
{ | ||
/// <summary> | ||
/// Gets or sets a value representing the collection of cases to evaluate. | ||
/// </summary> | ||
public CaseCollection SwitchCases | ||
{ | ||
get { return (CaseCollection)GetValue(SwitchCasesProperty); } | ||
set { SetValue(SwitchCasesProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Identifies the <see cref="SwitchCases"/> property. | ||
/// </summary> | ||
public static readonly DependencyProperty SwitchCasesProperty = | ||
DependencyProperty.Register(nameof(SwitchCases), typeof(CaseCollection), typeof(SwitchConverter), new PropertyMetadata(null)); | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating which type to first cast and compare provided values against. | ||
/// </summary> | ||
public Type TargetType | ||
{ | ||
get { return (Type)GetValue(TargetTypeProperty); } | ||
set { SetValue(TargetTypeProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Identifies the <see cref="TargetType"/> property. | ||
/// </summary> | ||
public static readonly DependencyProperty TargetTypeProperty = | ||
DependencyProperty.Register(nameof(TargetType), typeof(Type), typeof(SwitchConverter), new PropertyMetadata(null)); | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="SwitchPresenter"/> class. | ||
/// </summary> | ||
public SwitchConverter() | ||
{ | ||
// Note: we need to initialize this here so that XAML can automatically add cases without needing this defined around it as the content. | ||
// 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. | ||
// See https://learn.microsoft.com/windows/uwp/xaml-platform/custom-dependency-properties#initializing-the-collection | ||
SwitchCases = new CaseCollection(); | ||
} | ||
|
||
public object Convert(object value, Type targetType, object parameter, string language) | ||
{ | ||
var result = SwitchCases.EvaluateCases(value, TargetType ?? targetType); | ||
|
||
return result?.Content!; | ||
} | ||
|
||
public object ConvertBack(object value, Type targetType, object parameter, string language) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
components/Primitives/src/SwitchPresenter/SwitchHelpers.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
namespace CommunityToolkit.WinUI.Controls; | ||
|
||
/// <summary> | ||
/// Internal helpers for use between <see cref="SwitchPresenter"/> and <see cref="SwitchConverter"/>. | ||
/// 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. | ||
/// </summary> | ||
internal static partial class SwitchHelpers | ||
{ | ||
/// <summary> | ||
/// Extension method for a set of cases to find the matching case given its value and type. | ||
/// </summary> | ||
/// <param name="switchCases">The collection of <see cref="Case"/>s in a <see cref="CaseCollection"/></param> | ||
/// <param name="value">The value of the <see cref="Case"/> to find</param> | ||
/// <param name="targetType">The desired type of the result for automatic conversion</param> | ||
/// <returns>The discovered value, the default value, or <c>null</c></returns> | ||
internal static Case? EvaluateCases(this CaseCollection switchCases, object value, Type targetType) | ||
{ | ||
if (switchCases == null || | ||
switchCases.Count == 0) | ||
{ | ||
// If we have no cases, then we can't match anything. | ||
return null; | ||
} | ||
|
||
Case? xdefault = null; | ||
Case? newcase = null; | ||
|
||
foreach (Case xcase in switchCases) | ||
{ | ||
if (xcase.IsDefault) | ||
{ | ||
// 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. | ||
xdefault = xcase; | ||
continue; | ||
} | ||
|
||
if (CompareValues(value, xcase.Value, targetType)) | ||
{ | ||
newcase = xcase; | ||
break; | ||
} | ||
} | ||
|
||
if (newcase == null && xdefault != null) | ||
{ | ||
// Inject default if we found one without matching anything | ||
newcase = xdefault; | ||
} | ||
|
||
return newcase; | ||
} | ||
|
||
/// <summary> | ||
/// Compares two values using the TargetType. | ||
/// </summary> | ||
/// <param name="compare">Our main value in our SwitchPresenter.</param> | ||
/// <param name="value">The value from the case to compare to.</param> | ||
/// <returns>true if the two values are equal</returns> | ||
internal static bool CompareValues(object compare, object value, Type targetType) | ||
{ | ||
if (compare == null || value == null) | ||
{ | ||
return compare == value; | ||
} | ||
|
||
if (targetType == null || | ||
(targetType == compare.GetType() && | ||
targetType == value.GetType())) | ||
{ | ||
// Default direct object comparison or we're all the proper type | ||
return compare.Equals(value); | ||
} | ||
else if (compare.GetType() == targetType) | ||
{ | ||
// If we have a TargetType and the first value is the right type | ||
// Then our 2nd value isn't, so convert to string and coerce. | ||
var valueBase2 = ConvertValue(targetType, value); | ||
|
||
return compare.Equals(valueBase2); | ||
} | ||
|
||
// Neither of our two values matches the type so | ||
// we'll convert both to a String and try and coerce it to the proper type. | ||
var compareBase = ConvertValue(targetType, compare); | ||
|
||
var valueBase = ConvertValue(targetType, value); | ||
|
||
return compareBase.Equals(valueBase); | ||
} | ||
|
||
/// <summary> | ||
/// Helper method to convert a value from a source type to a target type. | ||
/// </summary> | ||
/// <param name="targetType">The target type</param> | ||
/// <param name="value">The value to convert</param> | ||
/// <returns>The converted value</returns> | ||
internal static object ConvertValue(Type targetType, object value) | ||
{ | ||
if (targetType.IsInstanceOfType(value)) | ||
{ | ||
return value; | ||
} | ||
else if (targetType.IsEnum && value is string str) | ||
{ | ||
#if HAS_UNO | ||
if (Enum.IsDefined(targetType, str)) | ||
{ | ||
return Enum.Parse(targetType, str); | ||
} | ||
#else | ||
if (Enum.TryParse(targetType, str, out object? result)) | ||
{ | ||
return result!; | ||
} | ||
#endif | ||
|
||
static object ThrowExceptionForKeyNotFound() | ||
{ | ||
throw new InvalidOperationException("The requested enum value was not present in the provided type."); | ||
} | ||
|
||
return ThrowExceptionForKeyNotFound(); | ||
} | ||
else | ||
{ | ||
return XamlBindingHelper.ConvertValue(targetType, value); | ||
} | ||
} | ||
} |
Oops, something went wrong.