Skip to content

Commit 6b48721

Browse files
feat: Every RoutedEvent should be usable as Attached Event (#15274)
* test: Automatic RoutedEvent Handler Generation * feat: Every RoutedEvent should be usable as Attached Event * fix: Namespace * fix: Address review * feat: Handle Preview event * test: Handle Preview event * fix: Address Review reverted Preview feture * fix: Throw On Fatal * fix: Error Code
1 parent 2dfd9be commit 6b48721

File tree

5 files changed

+223
-4
lines changed

5 files changed

+223
-4
lines changed

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t)
8686
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
8787
new AvaloniaXamlIlDeferredResourceTransformer());
8888

89+
InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());
90+
8991
Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
9092
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
9193
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using XamlX.Ast;
4+
using XamlX.Emit;
5+
using XamlX.IL;
6+
using XamlX.Transform;
7+
using XamlX.TypeSystem;
8+
9+
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
10+
11+
internal class AvaloniaXamlIlTransformRoutedEvent : IXamlAstTransformer
12+
{
13+
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
14+
{
15+
if (node is XamlAstNamePropertyReference prop
16+
&& prop.TargetType is XamlAstClrTypeReference targetRef
17+
&& prop.DeclaringType is XamlAstClrTypeReference declaringRef)
18+
{
19+
var xkt = context.GetAvaloniaTypes();
20+
var interactiveType = xkt.Interactivity.Interactive;
21+
var routedEventType = xkt.Interactivity.RoutedEvent;
22+
var AddHandlerT = xkt.Interactivity.AddHandlerT;
23+
24+
if (interactiveType.IsAssignableFrom(targetRef.Type))
25+
{
26+
var eventName = $"{prop.Name}Event";
27+
if (declaringRef.Type.GetAllFields().FirstOrDefault(f => f.IsStatic && f.Name == eventName) is { } eventField)
28+
{
29+
if (routedEventType.IsAssignableFrom(eventField.FieldType))
30+
{
31+
var instance = new XamlAstClrProperty(prop
32+
, prop.Name
33+
, targetRef.Type
34+
, null
35+
);
36+
instance.Setters.Add(new XamlDirectCallAddHandler(eventField,
37+
targetRef.Type,
38+
xkt.Interactivity.AddHandler,
39+
xkt.Interactivity.RoutedEventHandler
40+
)
41+
);
42+
if (eventField.FieldType.GenericArguments?.Count == 1)
43+
{
44+
var agrument = eventField.FieldType.GenericArguments[0];
45+
if (!agrument.Equals(xkt.Interactivity.RoutedEventArgs))
46+
{
47+
instance.Setters.Add(new XamlDirectCallAddHandler(eventField,
48+
targetRef.Type,
49+
xkt.Interactivity.AddHandlerT.MakeGenericMethod([agrument]),
50+
xkt.EventHandlerT.MakeGenericType(agrument)
51+
)
52+
);
53+
}
54+
}
55+
return instance;
56+
}
57+
else
58+
{
59+
context.ReportDiagnostic(new XamlX.XamlDiagnostic(
60+
AvaloniaXamlDiagnosticCodes.TransformError,
61+
XamlX.XamlDiagnosticSeverity.Error,
62+
$"Event definition {prop.Name} found, but its type {eventField.FieldType.GetFqn()} is not compatible with RoutedEvent.",
63+
node));
64+
}
65+
}
66+
}
67+
}
68+
return node;
69+
}
70+
71+
private sealed class XamlDirectCallAddHandler : IXamlILOptimizedEmitablePropertySetter
72+
{
73+
private readonly IXamlField _eventField;
74+
private readonly IXamlType _declaringType;
75+
private readonly IXamlMethod _addMethod;
76+
77+
public XamlDirectCallAddHandler(IXamlField eventField,
78+
IXamlType declaringType,
79+
IXamlMethod addMethod,
80+
IXamlType routedEventHandler
81+
)
82+
{
83+
Parameters = [routedEventHandler];
84+
_eventField = eventField;
85+
_declaringType = declaringType;
86+
_addMethod = addMethod;
87+
}
88+
89+
public IXamlType TargetType => _declaringType;
90+
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
91+
public IReadOnlyList<IXamlType> Parameters { get; }
92+
93+
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => [];
94+
95+
public void Emit(IXamlILEmitter emitter)
96+
=> emitter.EmitCall(_addMethod, true);
97+
98+
public void EmitWithArguments(XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
99+
IXamlILEmitter emitter,
100+
IReadOnlyList<IXamlAstValueNode> arguments)
101+
{
102+
103+
using (var loc = emitter.LocalsPool.GetLocal(_declaringType))
104+
emitter
105+
.Ldloc(loc.Local);
106+
107+
emitter.Ldfld(_eventField);
108+
109+
for (var i = 0; i < arguments.Count; ++i)
110+
context.Emit(arguments[i], emitter, Parameters[i]);
111+
112+
emitter.Ldc_I4(5);
113+
emitter.Ldc_I4(0);
114+
115+
emitter.EmitCall(_addMethod, true);
116+
}
117+
}
118+
}

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
1111
{
12-
class AvaloniaXamlIlWellKnownTypes
12+
13+
sealed class AvaloniaXamlIlWellKnownTypes
1314
{
1415
public IXamlType RuntimeHelpers { get; }
1516
public IXamlType AvaloniaObject { get; }
@@ -124,6 +125,49 @@ class AvaloniaXamlIlWellKnownTypes
124125
public IXamlType WindowTransparencyLevel { get; }
125126
public IXamlType IReadOnlyListOfT { get; }
126127
public IXamlType ControlTemplate { get; }
128+
public IXamlType EventHandlerT { get; }
129+
130+
sealed internal class InteractivityWellKnownTypes
131+
{
132+
public IXamlType Interactive { get; }
133+
public IXamlType RoutedEvent { get; }
134+
public IXamlType RoutedEventArgs { get; }
135+
public IXamlType RoutedEventHandler { get; }
136+
public IXamlMethod AddHandler { get; }
137+
public IXamlMethod AddHandlerT { get; }
138+
139+
internal InteractivityWellKnownTypes(TransformerConfiguration cfg)
140+
{
141+
var ts = cfg.TypeSystem;
142+
Interactive = ts.FindType("Avalonia.Interactivity.Interactive");
143+
RoutedEvent = ts.FindType("Avalonia.Interactivity.RoutedEvent");
144+
RoutedEventArgs = ts.FindType("Avalonia.Interactivity.RoutedEventArgs");
145+
var eventHanlderT = ts.FindType("System.EventHandler`1");
146+
RoutedEventHandler = eventHanlderT.MakeGenericType(RoutedEventArgs);
147+
AddHandler = Interactive.FindMethod(m => m.IsPublic
148+
&& !m.IsStatic
149+
&& m.Name == "AddHandler"
150+
&& m.Parameters.Count == 4
151+
&& m.Parameters[0].Equals(RoutedEvent)
152+
&& m.Parameters[1].Equals(cfg.WellKnownTypes.Delegate)
153+
&& m.Parameters[2].IsEnum
154+
&& m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean)
155+
);
156+
AddHandlerT = Interactive.FindMethod(m => m.IsPublic
157+
&& !m.IsStatic
158+
&& m.Name == "AddHandler"
159+
&& m.Parameters.Count == 4
160+
&& RoutedEvent.IsAssignableFrom(m.Parameters[0])
161+
&& m.Parameters[0].GenericArguments?.Count == 1 // This is specific this case workaround to check is generic method
162+
&& (cfg.WellKnownTypes.Delegate).IsAssignableFrom(m.Parameters[1])
163+
&& m.Parameters[2].IsEnum
164+
&& m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean) == true
165+
);
166+
167+
}
168+
}
169+
170+
public InteractivityWellKnownTypes Interactivity { get; }
127171

128172
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
129173
{
@@ -161,7 +205,6 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
161205
IBinding, cfg.WellKnownTypes.Object);
162206
UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType");
163207
StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
164-
StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
165208
INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope");
166209
INameScopeRegister = INameScope.GetMethod(
167210
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
@@ -242,7 +285,7 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
242285
StyledElementClassesProperty =
243286
StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
244287
ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
245-
.FindMethod( "BindClass", IDisposable, false, StyledElement,
288+
.FindMethod("BindClass", IDisposable, false, StyledElement,
246289
cfg.WellKnownTypes.String,
247290
IBinding, cfg.WellKnownTypes.Object);
248291

@@ -271,6 +314,8 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
271314
ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
272315
ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate");
273316
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
317+
EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1");
318+
Interactivity = new InteractivityWellKnownTypes(cfg);
274319
}
275320
}
276321

@@ -291,7 +336,7 @@ public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext
291336
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
292337
return rv;
293338
}
294-
339+
295340
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx)
296341
{
297342
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))

tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,15 @@
3636
<Link>PlatformFactAttribute.cs</Link>
3737
</Compile>
3838
</ItemGroup>
39+
<ItemGroup>
40+
<PackageReference Update="xunit.runner.console" Version="2.7.0">
41+
<PrivateAssets>all</PrivateAssets>
42+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
43+
</PackageReference>
44+
<PackageReference Update="xunit.runner.visualstudio" Version="2.5.7">
45+
<PrivateAssets>all</PrivateAssets>
46+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
47+
</PackageReference>
48+
</ItemGroup>
3949
<Import Project="..\..\build\BuildTargets.targets" />
4050
</Project>

tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ public void Attached_Event_Is_Assigned()
3939
Assert.True(target.WasTapped);
4040
}
4141

42+
[Fact]
43+
public void Attached_Event_Is_Assigned_Generic()
44+
{
45+
var xaml = @"<Panel xmlns='https://github.com/avaloniaui'><Grid DoubleTapped='OnTapped'><Button Name='target'/></Grid></Panel>";
46+
var host = new MyPanel();
47+
48+
AvaloniaRuntimeXamlLoader.Load(xaml, rootInstance: host);
49+
50+
var target = host.FindControl<Button>("target");
51+
52+
Assert.NotNull(target);
53+
54+
target.RaiseEvent(new TappedEventArgs(Gestures.DoubleTappedEvent, default));
55+
56+
Assert.True(host.WasTapped);
57+
}
58+
4259
[Fact]
4360
public void Exception_Is_Thrown_If_Event_Not_Found()
4461
{
@@ -48,6 +65,25 @@ public void Exception_Is_Thrown_If_Event_Not_Found()
4865
XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaRuntimeXamlLoader.Load(xaml, rootInstance: target));
4966
}
5067

68+
69+
70+
[Fact]
71+
public void Attached_Event_Routed_Event_Handler()
72+
{
73+
var xaml = @"<Panel xmlns='https://github.com/avaloniaui' Button.Click='OnClick'><Button Name='target'/></Panel>";
74+
var host = new MyPanel();
75+
76+
AvaloniaRuntimeXamlLoader.Load(xaml, rootInstance: host);
77+
78+
var target = host.FindControl<Button>("target");
79+
target.RaiseEvent(new RoutedEventArgs
80+
{
81+
RoutedEvent = Button.ClickEvent,
82+
});
83+
84+
Assert.True(host.WasClicked);
85+
}
86+
5187
public class MyButton : Button
5288
{
5389
public bool WasClicked { get; private set; }
@@ -56,5 +92,13 @@ public class MyButton : Button
5692
public void OnClick(object sender, RoutedEventArgs e) => WasClicked = true;
5793
public void OnTapped(object sender, RoutedEventArgs e) => WasTapped = true;
5894
}
95+
96+
public class MyPanel : Panel
97+
{
98+
public bool WasClicked { get; private set; }
99+
public bool WasTapped { get; private set; }
100+
public void OnClick(object sender, RoutedEventArgs e) => WasClicked = true;
101+
public void OnTapped(object sender, RoutedEventArgs e) => WasTapped = true;
102+
}
59103
}
60104
}

0 commit comments

Comments
 (0)