I have seen several answers already but somehow I can't get mine to work. I want to dynamically use any of the events of various controls (textbox, checkbox, button, etc) and, preferably, assign them to one event handler. The handler should be assigned at runtime. Furthermore, I want to know in the handler which event triggered the handler.
I got this to work partially. Using a lambda expression I call my handler (EventAssistant) and pass an extra parameter (command) which contains the name of the event. It works for events that use type EventHandler. However, it won't work for events that expect a different handler such as type MouseEventHandler. It will fail to subscribe at AddEventHandler.
private void RegisterEventHandlers(Control ctl)
{
foreach (Command command in CommandList)
{
EventInfo eventInfo = ctl.GetType().GetEvent(command.Name);
EventHandler handler = (sender, args) =>
{
EventAssistant(sender, args, command);
};
eventInfo.AddEventHandler(ctl, handler);
}
}
public void EventAssistant(object sender, EventArgs e, Command c)
{
//do lots of other fun stuff
}
Based on C# passing extra parameters to an event handler?
As an alternative I tried to solve the problem with an Expression Tree as shown here: Why am I getting an Argument exception when creating event handler dynamically?
Apparently, the EventHandlerType can be retrieved from the EventInfo and used in a lambda expression.
But, whatever I do I always get an InvalidOperationException "Lambda Parameter not in scope".
private void RegisterEventHandlers(Control ctl)
{
foreach (Command command in CommandList)
{
EventInfo eventInfo = ctl.GetType().GetEvent(command.Name);
var sender = Expression.Parameter(typeof(object), "sender");
var e = Expression.Parameter(typeof(EventArgs), "e");
var c = Expression.Parameter(typeof(Command), "command");
Expression[] arg = new Expression[] { sender, e, c };
MethodInfo mi = this.GetType().GetMethod("EventAssistant");
var body = Expression.Call(Expression.Constant(this), mi, arg);
var lambda = Expression.Lambda(eventInfo.EventHandlerType, body, sender, e);
eventInfo.AddEventHandler(ctl, lambda.Compile());
}
}
What am I doing wrong with the Expression Tree?
Also, the first piece of code looks a lot more clean. Is it possible to get what I want using the first code sample?
In your second attempt, the variable c shouldn't be a ParameterExpression, but a ConstantExpression with the value set to the current command instead. With the current code, you are creating a handler, which essentially looks like this:
(_sender, _e) => this.EventAssistant(_sender, _e, _c)
// The expression expects "_c" to be a parameter of the lambda, which is why
// you're getting that exception
However, if you change
var c = Expression.Parameter(typeof(Command), "command");
to
var c = Expression.Constant(command);
the generated code will look (and work, of course) as expected:
(_sender, _e) => this.EventAssistant(_sender, _e, command)
Related
I want do something like this:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Where btn1_Click is already defined in the class:
void btn1_Click(object sender, EventArgs e)
{
//
}
This won't compile, of course ("The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -="). Is there a way to take the event handler from one control and assign it to another at runtime? If that's not possible, is duplicating the event handler and assigning it to another control at runtime doable?
A couple of points: I have googled the heck out of this one for awhile and found no way of doing it yet. Most of the attempted approaches involve reflection, so if you read my question and think the answer is incredibly obvious, please try to compile the code in Visual Studio first. Or if the answer really is incredibly obvious, please feel free to slap me with it. Thanks, I'm really looking forward to seeing if this is possible.
I know I could just do this:
btn2.Click += new EventHandler(btn1_Click);
That's not what I'm looking for here.
This is also not what I'm looking for:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
Yeah, it's technically possible. Reflection is required because many of the members are private and internal. Start a new Windows Forms project and add two buttons. Then:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
If this convinces you that Microsoft tried really hard to prevent your from doing this, you understood the code.
No, you can't do this. The reason is encapsulation - events are just subscribe/unsubscribe, i.e. they don't let you "peek inside" to see what handlers are already subscribed.
What you could do is derive from Button, and create a public method which calls OnClick. Then you just need to make btn1 an instance of that class, and subscribe a handler to btn2 which calls btn1.RaiseClickEvent() or whatever you call the method.
I'm not sure I'd really recommend it though. What are you actually trying to do? What's the bigger picture?
EDIT: I see you've accepted the version which fetches the current set of events with reflection, but in case you're interested in the alternative which calls the OnXXX handler in the original control, I've got a sample here. I originally copied all events, but that leads to some very odd effects indeed. Note that this version means that if anyone subscribes to an event in the original button after calling CopyEvents, it's still "hooked up" - i.e. it doesn't really matter when you associate the two.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
I did some digging around with #nobugz's solution and came up with this generic version which could be used on most general-purpose objects.
What I found out is that events for, dare I say, automatic events actually are compiled with a backing delegate field of the same name:
So here's one for stealing event handlers for simpler objects:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Thought it might be useful to some.
But do note that the way events are wired in Windows Forms components is rather different. They are optimized so that multiple events doesn't take up a lot of memory just holding nulls. So it'll need a little more digging around, but #nobugz has already done that :-)
The article Delegates and events about combined delegates might help clarify a lot of points in answers.
You could use a common event handler for your buttons and your picture boxes (as per the comments on an earlier answer) and then use the 'sender' object to determine how to handle the event at runtime.
I have been using an expression tree to create a delegate and subscribe to any event with an Action<Object[]>, where all the event parameters are converted to an array of objects.
This has been working fine until now, that I need to subscribe to an event with an out/ref parameter and I need to set the value of this parameter.
Is there a way to use something similar to the expression tree and subscribe to any event, but still be able to set/return values to the object that raised the event?
Expression tree to create a delegate:
public static Delegate CreateProxyWithDynamicParameters(this EventInfo EventInfo, Action<object[]> Action)
{
var EventHandlerType = EventInfo.EventHandlerType;
var InvokeMethodInfo = EventHandlerType.GetMethod("Invoke");
var Parameters = InvokeMethodInfo.GetParameters().Select(Parameter => Expression.Parameter(Parameter.ParameterType, Parameter.Name)).ToArray();
var ConvertedParameters = Parameters.Select(Parameter => Expression.Convert(Parameter, typeof(object))).Cast<Expression>().ToArray();
var NewArrayInit = Expression.NewArrayInit(typeof(object), ConvertedParameters);
var Body = Expression.Call(Expression.Constant(Action), "Invoke", null, NewArrayInit);
var lambdaExpression = Expression.Lambda(Body, Parameters);
return Delegate.CreateDelegate(EventInfo.EventHandlerType, lambdaExpression.Compile(), InvokeMethodInfo.Name, ignoreCase: false);
}
Subscription with the delegate:
protected static void AddEvent<TMessage>(EventInfo eventInfo) where TMessage : EventArgs
{
void EventAction(object[] e)
{
// Run some event code.
}
var #delegate = eventInfo.CreateProxyWithDynamicParameters(EventAction);
DelegateDictionary[typeof(TMessage)] = #delegate;
eventInfo.AddEventHandler(x.Target, #delegate);
}
Edit:
Here is a vanilla simple. Where I need to set the HandlingCode parameter.
InventorApplication.ApplicationEvents.OnSaveDocument += ApplicationEvents_OnSaveDocument;
private void ApplicationEvents_OnSaveDocument(_Document DocumentObject, EventTimingEnum BeforeOrAfter, NameValueMap Context, out HandlingCodeEnum HandlingCode)
{
HandlingCode = HandlingCodeEnum.kEventHandled;
}
You are going to struggle to do this with Expression, because Expression likes to be able to run things via reflection for fallback, and it can't handle refs via reflection. You might be able to do it via raw IL, but... that's getting messier and messier.
Frankly, this event API is making life hard for you. If you control that API, I strongly suggest using the more idiomatic (object sender, SomeEventArgs args) where SomeEventArgs : EventArgs. Your SomeEventArgs would then have properties for all the things you're trying to pass as parameters, and the interested code can just do:
args.HandlingCode = ...
to assign a value.
I am currently trying to write an extension function to be able to easily attach an action that is only used once when the event is fired, then unsubscribed.
I am trying something like this:
public static void AttachOnce<TEventArgs>([NotNull] this EventHandler<TEventArgs> me, [NotNull] Action<object, TEventArgs> action)
where TEventArgs : System.EventArgs
{
var handler = me;
EventHandler<TEventArgs> wrappedAction = null;
wrappedAction = (sender, args) =>
{
action(sender, args);
handler -= wrappedAction;
};
handler += wrappedAction;
}
But ReSharper complains on the unsubscribe that handler is "Access to modified closure".
I know what this means, so I made the local variable for the closure already, but it doesn't seem to resolve it. What is failing here?
The direct hard-coded code works. Something like this:
var file = new FileSystemWatcher("path/to/file");
FileSystemEventHandler handler = null;
handler = (sender, args) =>
{
// My action code here
file.Changed -= handler;
};
file.Changed += handler;
EDIT 1 (2018-10-09 11:43 CET):
I may just have been too fast, asking a question before thoroughly thinking it through.
You can't create extension methods on Events. At all. It's just not possible in C#. So I can't even test why ReSharper is complaining and if it is right, because a call like file.Changed.AttachOnce(action) is not valid. It says "The event 'Changed' can only appear on the left hand side of += or -=".
I have found some more sources for similar requests/questions:
http://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
One time generic event call?
I've been thinking about a different but much simpler approach, using a "self-detaching" inline handler which would be used like this:
obj.Event += (s, e) =>
{
Detach(obj, nameof(obj.Event));
// ..do stuff..
};
The Detach method would look like this and could be put anywhere you like (most likely a static helper class):
public static void Detach(object obj, string eventName)
{
var caller = new StackTrace().GetFrame(1).GetMethod();
var type = obj.GetType();
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
if (typeof(Delegate).IsAssignableFrom(field.FieldType))
{
var handler = (field.GetValue(obj) as Delegate)?.GetInvocationList().FirstOrDefault(m => m.Method.Equals(caller));
if (handler != null)
{
type.GetEvent(eventName).RemoveEventHandler(obj, handler);
return;
}
}
}
}
So for your example the code would look like this:
file.Changed += (s, e) =>
{
Detach(file, nameof(file.Changed));
// My action code here
};
In this case, it's okay.
ReSharper simply warns that handler is different between the time you declare and the time it is executed.
Not sure how exactly you want to design your extension method, but maybe this will get you started:
public static void OnChangedOnce(this FileSystemWatcher instance, Action<object, FileSystemEventArgs> action)
{
var file = instance;
var wrappedAction = action;
FileSystemEventHandler handler = null;
handler = (object sender, FileSystemEventArgs args) =>
{
wrappedAction(sender, args);
file.Changed -= handler;
};
file.Changed += handler;
file.EnableRaisingEvents = true; // mandatory
}
var file = new FileSystemWatcher("path/to/file");
file.OnChangedOnce((sender, args)) =>
{
// your action here
});
In reference to my older question
WPF How to assign the same event lambda expression to two different elements
I have a slightly different need now.
I have to subscribe to different types of events using the same lambda action:
EventHandler action = (sender2,args) =>
{
var cmbChanged = sender2 as ComboBox;
//...check...
};
cmb.DropDownClosed += action;
cmb.PreviewKeyUp += action; // <----- compiler error here
but the compiler rightly says that an action is System.EventHandler while here I need System.Window.Input.KeyEventHandler.
In short, I have to check for some condition both when you click with the mouse and close the combo and when you choose a value by typing.
Since the delegate types are different, you cannot assign add it directly. But you can forward it simply with:
cmb.DropDownClosed += action;
cmb.PreviewKeyUp += (sender, e) => action(sender, e);
Since e is of type KeyEventArgs which is derived from EventArgs you can pass it to action.
I am trying to write a library that allows the attaching of events using attributes, and I want to create a set of convenience method signatures.
For example, a button will have a Click event with a handler signature of void(object, EventArgs).
I have already mapped the methods that match this signature directly:
// the object that raises the event
Object eventSource = ...;
EventInfo evnt = ...;
// the object with the target method
Object target = ...;
MethodInfo method = ...;
// create and attach delegate
var del Delegate.CreateDelegate(evnt.EventHandlerType, target, method);
evnt.AddEventHandler(eventSource, del);
This works well as long as the methods are the same/compatible, even when detaching an event:
evnt.RemoveEventHandler(eventSource, del);
However, I would like to also be able to map parameterless methods.
Is it possible to create a delegate that will accept any arguments, but then ignore them, and invoke a desired method on an object?
For an example, in the working bit, I can do:
// the method
void MyClickMethod(object sender, EventArgs e)
{
}
// the execution
var evnt = myButton.GetType().GetEvent("Click");
var method = this.GetType().GetMethod("MyClickMethod")
AttachEvent(/*eventSource*/ myButton, /*evnt*/ evnt, /*target*/ this, /*method*/ method);
But, I would also like to be able to attach this method:
void MyClickMethod()
{
}
My logic here is that we can attach any parameterless method on any type to any event. This is often very helpful. It is important that I be able to detach any events.
What goes on in my mind, would to somehow create a delegate that does this:
eventSource.Event += (sender, e) => {
target.method();
};
Is there a way to do this cleanly?