I’ve been digging the Avalonia event system, and I found that it involves forwarding numerous RoutedEventArgs and RoutedEvent to user. I would like to ask for a suggestion, how this maybe done programmatically. I’ve checked the NodeFactory, i got some untested GPT suggestion for handling this:
Summary
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Avalonia.Interactivity;
using VL.Core;
using VL.Core.Diagnostics;
namespace VL.Avalonia.Factories
{
public class AvaloniaRoutedEventFactory : IVLNodeDescriptionFactory
{
private readonly IVLNodeDescriptionFactory _factory;
private readonly ImmutableArray<IVLNodeDescription> _nodes;
public AvaloniaRoutedEventFactory(IVLNodeDescriptionFactory factory)
{
_factory = factory;
_nodes = ScanForRoutedEvents(factory);
}
/// <summary>
/// Registers this factory with the VL AppHost.
/// Call this from your main Package Entry point or library initialization.
/// </summary>
public static void Register(IVLFactory services)
{
services.RegisterNodeFactory("Avalonia.RoutedEvents", factory =>
{
var impl = new AvaloniaRoutedEventFactory(factory);
return NodeBuilding.NewFactoryImpl(impl.GetNodeDescriptions());
});
}
public ImmutableArray<IVLNodeDescription> GetNodeDescriptions() => _nodes;
private static ImmutableArray<IVLNodeDescription> ScanForRoutedEvents(IVLNodeDescriptionFactory factory)
{
var nodes = new List<IVLNodeDescription>();
// 1. Get all loaded assemblies that might contain Avalonia controls
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.GetName().Name!.StartsWith("Avalonia"));
foreach (var assembly in assemblies)
{
foreach (var type in GetTypesSafe(assembly))
{
// 2. Find static readonly fields of type RoutedEvent
var eventFields = type.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(f => typeof(RoutedEvent).IsAssignableFrom(f.FieldType));
foreach (var field in eventFields)
{
if (field.GetValue(null) is RoutedEvent routedEvent)
{
// 3. Create a VL Node Description for this event
var node = CreateNodeDescription(factory, type, field.Name, routedEvent);
nodes.Add(node);
}
}
}
}
return nodes.ToImmutableArray();
}
private static IVLNodeDescription CreateNodeDescription(
IVLNodeDescriptionFactory factory,
Type ownerType,
string fieldName,
RoutedEvent routedEvent)
{
// Naming convention:
// Name: "Click"
// Category: "Avalonia.Events.Button"
// Clean up field name (remove "Event" suffix convention)
var name = fieldName.EndsWith("Event")
? fieldName.Substring(0, fieldName.Length - 5)
: fieldName;
var category = $"Avalonia.Events.{ownerType.Name}";
return factory.NewNodeDescription(
name: name,
category: category,
fragmented: true,
init: ctx =>
{
// This defines the inputs/outputs of the node
// We have 0 inputs and 1 output (the RoutedEvent object)
var outputPin = ctx.NewOutputPin<RoutedEvent>("Event");
return new NodeImplementation(
create: nodeContext =>
{
// On Create, we simply push the static RoutedEvent instance to the output
// Since it's constant, we can set it once.
var solution = new Pin<RoutedEvent>(outputPin.ID)
{
Value = routedEvent
};
return solution;
},
update: (state, inputVals) =>
{
// No updates needed, it's a constant
return state;
}
);
}
);
}
private static IEnumerable<Type> GetTypesSafe(Assembly assembly)
{
try { return assembly.GetTypes(); }
catch (ReflectionTypeLoadException e) { return e.Types.Where(t => t != null)!; }
catch { return Enumerable.Empty<Type>(); }
}
}
}
I’m not sure if I am on right track to use NodeFactory for this… Honestly i would prefeare to use some static code generation for that but I see no other way…
Also it’s not obvious where should I host this. In AvaloniaLayer, or it’s somehow statically/magically imported on it’s own?
Prolly I would answer this myslef, when I get back to it tommorow, but would be still nice to know how it was planned.
Ok so the event classes you can forward like I showed you. The static fields I am not quite sure what you want to have at the end. From your gist I see your generated node would have returned a type - is that what is needed? A node called „Click“ returning a „Type“? Really?
Honestly I would like just to make a filter and forward everything that is under it, since forwarding with VL would be pain in the … , and this would be even better approach to handle all the other random types out there…
Basically the Listener is a generic delegate, and generated ones provide you with event args, the issue happens as soon as you try type event args or access them.