-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[xamlc] convert {OnIdiom} to OnIdiomExtension<T>
#28700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| using System; | ||
| using System.ComponentModel; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Globalization; | ||
| using System.Reflection; | ||
|
|
@@ -122,3 +123,118 @@ object GetValue() | |
| } | ||
| } | ||
| } | ||
|
|
||
| // NOTE: currently in *.Internals, to be used by XamlC compiler | ||
| namespace Microsoft.Maui.Controls.Xaml.Internals | ||
| { | ||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| [ContentProperty(nameof(Default))] | ||
| [RequireService( | ||
| [typeof(IProvideValueTarget), | ||
| typeof(IValueConverterProvider), | ||
| typeof(IXmlLineInfoProvider), | ||
| typeof(IConverterOptions)])] | ||
| internal class OnIdiomExtension<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T> : IMarkupExtension | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's used by generated code, it has to be public (the tests passes cause there's an internalsvisibleto for the unit tests project) you can decorate it with a [EditorBrowseable] attribute, or even [Obsolete] it to prevent usage... or move it to a namespace with 'Internals' in the name...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, this might be the cause of the current CI failures. I'll try a |
||
| { | ||
| // See Device.Idiom | ||
|
|
||
| public object Default { get; set; } | ||
| public object Phone { get; set; } | ||
| public object Tablet { get; set; } | ||
| public object Desktop { get; set; } | ||
| public object TV { get; set; } | ||
| public object Watch { get; set; } | ||
|
|
||
| public IValueConverter Converter { get; set; } | ||
|
|
||
| public object ConverterParameter { get; set; } | ||
|
|
||
| public object ProvideValue(IServiceProvider serviceProvider) | ||
| { | ||
| if (Default == null | ||
| && Phone == null | ||
| && Tablet == null | ||
| && Desktop == null | ||
| && TV == null | ||
| && Watch == null) | ||
| throw new XamlParseException("OnIdiomExtension requires a non-null value to be specified for at least one idiom or Default.", serviceProvider); | ||
|
|
||
| var valueProvider = serviceProvider?.GetService<IProvideValueTarget>() ?? throw new ArgumentException(); | ||
|
|
||
| BindableProperty bp; | ||
| PropertyInfo pi = null; | ||
| Type propertyType = typeof(T); | ||
|
|
||
| if (valueProvider.TargetObject is Setter setter) | ||
| { | ||
| bp = setter.Property; | ||
| } | ||
| else | ||
| { | ||
| bp = valueProvider.TargetProperty as BindableProperty; | ||
| pi = valueProvider.TargetProperty as PropertyInfo; | ||
| } | ||
|
|
||
| var value = GetValue(); | ||
| if (value == null && propertyType.IsValueType) | ||
| return Activator.CreateInstance<T>(); | ||
|
|
||
| if (Converter != null) | ||
| return Converter.Convert(value, propertyType, ConverterParameter, CultureInfo.CurrentUICulture); | ||
|
|
||
| var converterProvider = serviceProvider?.GetService<IValueConverterProvider>(); | ||
| if (converterProvider != null) | ||
| { | ||
| MemberInfo minforetriever() | ||
| { | ||
| if (pi != null) | ||
| return pi; | ||
|
|
||
| MemberInfo minfo = null; | ||
| try | ||
| { | ||
| minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName); | ||
| } | ||
| catch (AmbiguousMatchException e) | ||
| { | ||
| throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e); | ||
| } | ||
| if (minfo != null) | ||
| return minfo; | ||
| try | ||
| { | ||
| return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) }); | ||
| } | ||
| catch (AmbiguousMatchException e) | ||
| { | ||
| throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e); | ||
| } | ||
| } | ||
|
|
||
| return converterProvider.Convert(value, propertyType, minforetriever, serviceProvider); | ||
| } | ||
| if (converterProvider != null) | ||
| return converterProvider.Convert(value, propertyType, () => pi, serviceProvider); | ||
|
|
||
| var ret = value.ConvertTo(propertyType, () => pi, serviceProvider, out Exception exception); | ||
| if (exception != null) | ||
| throw exception; | ||
| return ret; | ||
| } | ||
|
|
||
| object GetValue() | ||
| { | ||
| if (DeviceInfo.Idiom == DeviceIdiom.Phone) | ||
| return Phone ?? Default; | ||
| if (DeviceInfo.Idiom == DeviceIdiom.Tablet) | ||
| return Tablet ?? Default; | ||
| if (DeviceInfo.Idiom == DeviceIdiom.Desktop) | ||
| return Desktop ?? Default; | ||
| if (DeviceInfo.Idiom == DeviceIdiom.TV) | ||
| return TV ?? Default; | ||
| if (DeviceInfo.Idiom == DeviceIdiom.Watch) | ||
| return Watch ?? Default; | ||
| return Default; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.OnIdiom"> | ||
| <Label x:Name="Label" Text="{OnIdiom Default=default, Desktop=desktop, Phone=phone}" /> | ||
| </ContentPage> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| using System.Linq; | ||
| using Microsoft.Maui.Controls.Core.UnitTests; | ||
| using Microsoft.Maui.Devices; | ||
| using Mono.Cecil.Cil; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Microsoft.Maui.Controls.Xaml.UnitTests | ||
| { | ||
| public partial class OnIdiom : ContentPage | ||
| { | ||
| public OnIdiom() | ||
| { | ||
| InitializeComponent(); | ||
| } | ||
|
|
||
| public OnIdiom(bool useCompiledXaml) | ||
| { | ||
| //this stub will be replaced at compile time | ||
| } | ||
|
|
||
| [TestFixture] | ||
| public class Tests | ||
| { | ||
| MockDeviceInfo mockDeviceInfo; | ||
|
|
||
| [SetUp] | ||
| public void Setup() | ||
| { | ||
| DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo()); | ||
| } | ||
|
|
||
| [TearDown] | ||
| public void TearDown() | ||
| { | ||
| DeviceInfo.SetCurrent(null); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Label_Text() | ||
| { | ||
| MockCompiler.Compile(typeof(OnIdiom), out var md, out bool hasLoggedErrors); | ||
| Assert.That(md.Body.Instructions.Any(static i => i.OpCode == OpCodes.Newobj && i.Operand.ToString() == "System.Void Microsoft.Maui.Controls.Xaml.OnIdiomExtension`1<System.String>::.ctor()")); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we handle this as a compiled markup extension (the lines just after these ones) ?