6 changed files with 310 additions and 1 deletions
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.PropertyStore; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Styling; |
|||
|
|||
/// <summary>
|
|||
/// Represents an event setter in a style. Event setters invoke the specified event handlers in response to events.
|
|||
/// </summary>
|
|||
public sealed class EventSetter : SetterBase, ISetterInstance |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EventSetter"/> class.
|
|||
/// </summary>
|
|||
public EventSetter() |
|||
{ |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EventSetter"/> class, using the provided event and handler parameters.
|
|||
/// </summary>
|
|||
/// <param name="eventName">The particular routed event that the <see cref="EventSetter"/> responds to.</param>
|
|||
/// <param name="handler">The handler to assign in this setter.</param>
|
|||
public EventSetter(RoutedEvent eventName, Delegate handler) |
|||
{ |
|||
Event = eventName; |
|||
Handler = handler; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the particular routed event that this EventSetter responds to.
|
|||
/// </summary>
|
|||
public RoutedEvent? Event { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the reference to a handler for a routed event in the setter.
|
|||
/// </summary>
|
|||
public Delegate? Handler { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The routing strategies to listen to.
|
|||
/// </summary>
|
|||
public RoutingStrategies Routes { get; set; } = RoutingStrategies.Bubble | RoutingStrategies.Direct; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value that determines whether the handler assigned to the setter should still be invoked, even if the event is marked handled in its event data.
|
|||
/// </summary>
|
|||
public bool HandledEventsToo { get; set; } |
|||
|
|||
internal override ISetterInstance Instance(IStyleInstance styleInstance, StyledElement target) |
|||
{ |
|||
if (target is null) |
|||
throw new ArgumentNullException(nameof(target)); |
|||
|
|||
if (Event is null) |
|||
throw new InvalidOperationException($"{nameof(EventSetter)}.{nameof(Event)} must be set."); |
|||
|
|||
if (Handler is null) |
|||
throw new InvalidOperationException($"{nameof(EventSetter)}.{nameof(Handler)} must be set."); |
|||
|
|||
if (target is not InputElement inputElement) |
|||
throw new ArgumentException($"{nameof(EventSetter)} target must be a {nameof(InputElement)}", nameof(target)); |
|||
|
|||
if (styleInstance.HasActivator) |
|||
throw new InvalidOperationException("EventSetter cannot be used in styles with activators, i.e. styles with complex selectors."); |
|||
|
|||
return new EventSetterInstance(inputElement, Event, Handler, Routes, HandledEventsToo); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.PropertyStore; |
|||
|
|||
namespace Avalonia.Styling; |
|||
|
|||
internal class EventSetterInstance : ISetterInstance, IValueEntry |
|||
{ |
|||
private readonly InputElement _inputElement; |
|||
private readonly RoutedEvent _routedEvent; |
|||
private readonly Delegate _handler; |
|||
|
|||
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010")] |
|||
private static readonly StyledProperty<EventSetterInstance> s_eventSetterProperty = AvaloniaProperty |
|||
.Register<InputElement, EventSetterInstance>("EventSetter"); |
|||
|
|||
public EventSetterInstance(InputElement inputElement, RoutedEvent routedEvent, Delegate handler, |
|||
RoutingStrategies routes, bool handledEventsToo) |
|||
{ |
|||
_inputElement = inputElement; |
|||
_routedEvent = routedEvent; |
|||
_handler = handler; |
|||
|
|||
inputElement.AddHandler(routedEvent, handler, routes, handledEventsToo); |
|||
} |
|||
|
|||
public AvaloniaProperty Property => s_eventSetterProperty; |
|||
public bool HasValue() => false; |
|||
public object GetValue() => BindingOperations.DoNothing; |
|||
|
|||
public bool GetDataValidationState(out BindingValueType state, out Exception? error) |
|||
{ |
|||
state = BindingValueType.DoNothing; |
|||
error = null; |
|||
return false; |
|||
} |
|||
|
|||
public void Unsubscribe() |
|||
{ |
|||
_inputElement.RemoveHandler(_routedEvent, _handler); |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using XamlX.Ast; |
|||
using XamlX.Transform; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
|
|||
internal class AvaloniaXamlIlEventSetterTransformer : IXamlAstTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (!(node is XamlAstObjectNode on |
|||
&& on.Type.GetClrType().FullName == "Avalonia.Styling.EventSetter")) |
|||
return node; |
|||
|
|||
IXamlType targetType = null; |
|||
|
|||
var styleParent = context.ParentNodes() |
|||
.OfType<AvaloniaXamlIlTargetTypeMetadataNode>() |
|||
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); |
|||
|
|||
if (styleParent != null) |
|||
{ |
|||
targetType = styleParent.TargetType.GetClrType() |
|||
?? throw new XamlStyleTransformException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node); |
|||
} |
|||
|
|||
if (targetType == null) |
|||
{ |
|||
throw new XamlStyleTransformException("Could not determine target type of Setter", node); |
|||
} |
|||
|
|||
var routedEventProp = on.Children.OfType<XamlAstXamlPropertyValueNode>() |
|||
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Event"); |
|||
var routedEventType = context.GetAvaloniaTypes().RoutedEvent; |
|||
var routedEventTType = context.GetAvaloniaTypes().RoutedEventT; |
|||
IXamlType actualRoutedEventType; |
|||
if (routedEventProp != null) |
|||
{ |
|||
if (routedEventProp.Values.OfType<XamlStaticExtensionNode>().FirstOrDefault() is not { } valueNode) |
|||
{ |
|||
var eventName = routedEventProp.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text; |
|||
if (eventName == null) |
|||
throw new XamlStyleTransformException("EventSetter.Event must be an event name or x:Static expression to an event.", node); |
|||
|
|||
// Ideally, compiler should do one of these lookups:
|
|||
// - Runtime lookup in RoutedEventRegistry. But can't do that in compiler. Could be the best approach, if we inject RoutedEventRegistry extra code, still no compile time alidation.
|
|||
// - Lookup for CLR events with this name and routed args. But there might not be a CLR event at all. And we can't reliably get RoutedEvent instance from there.
|
|||
// - Lookup for AddEventNameHandler methods. But the same - there might not be one.
|
|||
// Instead this transformer searches for routed event definition in one of base classes. Not ideal too, it won't handle attached events. Still better than others.
|
|||
// Combining with ability to use x:Static for uncommon use-cases this approach should work well.
|
|||
if (!eventName.EndsWith("Event")) |
|||
{ |
|||
eventName += "Event"; |
|||
} |
|||
|
|||
IXamlType type = targetType, nextType = targetType; |
|||
IXamlMember member = null; |
|||
while (nextType is not null && member is null) |
|||
{ |
|||
type = nextType; |
|||
member = type?.Fields.FirstOrDefault(f => f.IsPublic && f.IsStatic && f.Name == eventName) ?? |
|||
(IXamlMember)type?.GetAllProperties().FirstOrDefault(p => |
|||
p.Name == eventName && p.Getter is { IsPublic: true, IsStatic: true }); |
|||
nextType = type.BaseType; |
|||
} |
|||
|
|||
if (member is null) |
|||
throw new XamlStyleTransformException($"EventSetter.Event with name \"{eventName}\" wasn't found.", node); |
|||
|
|||
valueNode = new XamlStaticExtensionNode(on, |
|||
new XamlAstClrTypeReference(routedEventProp, type, false), member.Name); |
|||
|
|||
routedEventProp.Values = new List<IXamlAstValueNode> {valueNode}; |
|||
} |
|||
|
|||
actualRoutedEventType = valueNode.Type.GetClrType(); |
|||
if (!routedEventType.IsAssignableFrom(actualRoutedEventType)) |
|||
throw new XamlStyleTransformException("EventSetter.Event must be assignable to RoutedEvent type.", node); |
|||
|
|||
// Get RoutedEvent or RoutedEvent<T> base type from the field, so we can get generic parameter below.
|
|||
// This helps to ignore any other possible MyRoutedEvent : RoutedEvent<MyArgs> type definitions.
|
|||
while (!(actualRoutedEventType.FullName.StartsWith(routedEventType.FullName) |
|||
|| actualRoutedEventType.FullName.StartsWith(routedEventTType.FullName))) |
|||
{ |
|||
actualRoutedEventType = actualRoutedEventType.BaseType; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new XamlStyleTransformException($"EventSetter.Event must be set.", node); |
|||
} |
|||
|
|||
var handlerProp = on.Children.OfType<XamlAstXamlPropertyValueNode>() |
|||
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Handler"); |
|||
if (handlerProp != null) |
|||
{ |
|||
var handlerName = handlerProp.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text; |
|||
if (handlerName == null) |
|||
throw new XamlStyleTransformException("EventSetter.Handler must be a method name.", node); |
|||
|
|||
var rootType = context.RootObject?.Type.GetClrType(); |
|||
var argsType = actualRoutedEventType.GenericArguments.Any() ? |
|||
actualRoutedEventType.GenericArguments[0] : |
|||
context.GetAvaloniaTypes().RoutedEventArgs; |
|||
|
|||
var handler = rootType?.FindMethod( |
|||
handlerName, |
|||
context.Configuration.WellKnownTypes.Void, |
|||
true, |
|||
new[] { context.Configuration.WellKnownTypes.Object, argsType }); |
|||
if (handler != null) |
|||
{ |
|||
var delegateType = context.Configuration.TypeSystem.GetType("System.EventHandler`1").MakeGenericType(argsType); |
|||
var handlerInvoker = new XamlLoadMethodDelegateNode(handlerProp, context.RootObject, delegateType, handler); |
|||
handlerProp.Values = new List<IXamlAstValueNode> { handlerInvoker }; |
|||
} |
|||
else |
|||
{ |
|||
throw new XamlStyleTransformException( |
|||
$"EventSetter.Handler with name \"{handlerName}\" wasn't found on type \"{rootType?.Name ?? "(null)"}\"." + |
|||
$"EventSetter is supported only on XAML files with x:Class.", node); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new XamlStyleTransformException($"EventSetter.Handler must be set.", node); |
|||
} |
|||
|
|||
return on; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue