Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/Controls/src/Build.Tasks/CreateObjectVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Maui.Controls.Xaml;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using static Mono.Cecil.Cil.Instruction;
using static Mono.Cecil.Cil.OpCodes;

Expand Down Expand Up @@ -94,6 +95,39 @@ public void Visit(ElementNode node, INode parentNode)
return;
}

// Convert OnIdiomExtension to OnIdiomExtension<T>
if (typeref.FullName == "Microsoft.Maui.Controls.Xaml.OnIdiomExtension")
Copy link
Contributor

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) ?

{
// Find the property being set with {OnIdiom}
XmlName propertyName = XmlName.Empty;
SetPropertiesVisitor.TryGetPropertyName(node, node.Parent, out propertyName);
var localName = propertyName.LocalName;
var parentType = Module.ImportReference((node.Parent as IElementNode).XmlType.GetTypeReference(Context.Cache, Module, node));
var bpRef = SetPropertiesVisitor.GetBindablePropertyReference(parentType, propertyName.NamespaceURI, ref localName, out _, Context, node);

// Lookup the target type for OnIdiomExtension<T>
TypeReference targetType = null;
if (bpRef is not null)
{
targetType = Module.ImportReference(bpRef.GetBindablePropertyType(Context.Cache, node, Module));
}
else
{
var propertyRef = parentType.GetProperty(Context.Cache, pd => pd.Name == localName, out var declaringTypeReference);
if (propertyRef != null)
{
targetType = Module.ImportReference(propertyRef.PropertyType.ResolveGenericParameters(declaringTypeReference));
}
}

// Change typeref to OnIdiomExtension<T>
if (targetType is not null)
{
var onIdiomExtensionType = Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml.Internals", "OnIdiomExtension`1"));
typeref = onIdiomExtensionType.MakeGenericInstanceType(targetType);
}
}

//if this is a MarkupExtension that can be compiled directly, compile and returns the value
var compiledMarkupExtensionName = typeref
.GetCustomAttribute(Context.Cache, Module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "ProvideCompiledAttribute"))
Expand Down
116 changes: 116 additions & 0 deletions src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs
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;
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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...

Copy link
Member Author

Choose a reason for hiding this comment

The 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 public type in the *.Internals namespace.

{
// 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;
}
}
}
6 changes: 6 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/OnIdiom.xaml
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>
46 changes: 46 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/OnIdiom.xaml.cs
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()"));
}
}
}
}
Loading