Get delegate handler from event [duplicate] - c#

This question already has answers here:
Is it possible to "steal" an event handler from one control and give it to another?
(4 answers)
Closed 7 years ago.
I am trying to get the event handler without doing any changes in MyControl class. I tried to use reflection but I am not able to get the handler.
Following is a code sample.
Thanks
public class MyControl : Control
{
public void Register()
{
SizeChanged += MyControl_SizeChanged;
}
void MyControl_SizeChanged(object sender, EventArgs e)
{
// Do something
}
}
//[TestFixture]
public class MyControlTest
{
// [Test]
public void RegisterTest()
{
var control = new MyControl();
control.Register();
var eventInfo = control.GetType().GetEvent("SizeChanged", BindingFlags.Public | BindingFlags.Instance);
// Need to get the handler (delegate) and GetInvocationList().Count
EventHandler handler = ...;
var count = handler.GetInvocationList().Count();
Assert.That(count, IsolationLevel.EqualTo(1));
}
}

Events don't actually have handlers; an event is just a pair of specially-named add & remove methods.
For more information, see my blog.
How the event stores its handlers is an implementation detail; WinForms controls use an EventHandlerList.
You can see this in the source.

That's probably because of it's protection level which is currently private as can be seen from your posted code
void MyControl_SizeChanged(object sender, EventArgs e)
{
// Do something
}

Related

Since we have multicast delegates, why do we need events? [duplicate]

This question already has answers here:
Difference between events and delegates and its respective applications [closed]
(10 answers)
Closed 3 years ago.
I was asked this question in an interview, and either I'm suffering from brain-lock or just plain dumb, but I didn't have an answer.
Couple of reasons why we need events:
Restricting scope, you do not want to expose your events, like you can for your delegates.
You can have events as fields in your interfaces and not delegates
Example below:
Class Printer
{
public event EventHandler Print;
public void Start()
{
OnPrint();
}
protected virtual void OnPrint()
{
Print?.Invoke(this,EventArgs.Empty);
}
}
class Program
{
static void Main(string[] args)
{
//When Print is an EventHander
var printer = new Printer();
printer.Print += PrintEvent;
printer.Start();
//If Print was a delegate this is possible, else you get compile time errors
printer.Print(null,null); // Events will not allow to have a direct invoke
printer.Print = null; //You cannot assign a null to an Event Handler
}
private static void PrintEvent(object sender, EventArgs e)
{
System.Console.WriteLine("Printing event");
}
}

Assign method call to button click in parameter

I created a custom message window, like a MessageBox, but with a button disabled until checkbox is checked.
I want to:
Pass a method from any other class as a parameter to the constructor of this message window.
Bind this method to a button click so that when it's clicked the method is executed.
I'm new to C# and am a bit confused. I suspect it has something to do with delegate, Func, EventHandler etc, and I even sorted out how delegates and events work, but this is a bit too much for me.
The question is if it's possible and if yes, what is the logic behind this? Any examples, maybe?
I really doubt I'm the only one who ever needed it.
Here is a sample code that passes a EventHandler using a constructor:
[Two approach was described (Lambda, Method)]
class TestWindow
{
public TestWindow(RoutedEventHandler ButtonClickHandler)
{
SomeButton.Click += ButtonClickHandler;
}
}
class Caller
{
void Test()
{
TestWindow A = new TestWindow((S, E) => {
// Event Handler Codes ...
});
TestWindow B = new TestWindow(ClickHandler);
}
private void ClickHandler(object sender, RoutedEventArgs e)
{
// Event Handler Codes ...
}
}

Binding an event handler to any type of event using reflection

I have some code where I need to dynamically bind events to an event handler:
foreach (string evnt in attribute.Events)
{
EventInfo ei = control.GetType().GetEvent(evnt);
if(ei != null)
{
ei.AddEventHandler(control, new EventHandler((s, e) =>
{
// More awesomeness here...
}));
}
}
So for each string in the event list, get the event from the control and bind a handler.
The problem is that not all events are EventHandler, some for example might be KeyEventHander or MouseEventHandler etc.
I don't want a massive if/else list of EventHandler derived types, I just want to bind the same handler regardless of what type of EventHandler it is.
How can I do this?
Here is one way to do it:
First create this helper class:
public class HandlerHelper<T> where T : EventArgs
{
private readonly EventHandler m_HandlerToCall;
public HandlerHelper(EventHandler handler_to_call)
{
m_HandlerToCall = handler_to_call;
}
public void Handle(object sender, T args)
{
m_HandlerToCall.Invoke(sender, args);
}
}
This generic class has a Handle method that will be used to be our delegate for the many event handler types. Note that this class is generic. T will be one of the many EventArgs derived classes for different event types.
Now let's say you define the following event handler:
var event_handler = new EventHandler((s, args) =>
{
// More awesomeness here...
});
Here is how you can use the helper class to create different delegates for the different event handler types that will invoke event_handler:
foreach (var event_name in event_names)
{
var event_info = control.GetType().GetEvent(event_name);
var event_handler_type = event_info.EventHandlerType;
var event_args_type = event_handler_type.GetMethod("Invoke").GetParameters()[1].ParameterType;
var helper_type = typeof(HandlerHelper<>).MakeGenericType(event_args_type);
var helper = Activator.CreateInstance(helper_type, event_handler);
Delegate my_delegate = Delegate.CreateDelegate(event_handler_type, helper, "Handle");
event_info.AddEventHandler(button, my_delegate);
}
For each event, we obtain the EventHandlerType which is like EventHandler or MouseEventHandler.
Then we use reflection to get the type of the second parameter which is like EventArgs or MouseEventArgs.
Then we create an instance of HandlerHelper<> based on the type of the EventArgs parameter. For example HandlerHelper<EventArgs> or HandlerHelper<MouseEventArgs>.
We give event_handler to the constructor of HandlerHelper so that it can call it when it's Handle method is invoked.
This makes the signature of the Handle method as we want. For example, in the case of HandlerHelper<MouseEventArgs>, the signature of the Handle method is:
void Handle(object sender, MouseEventArgs args)
Now, we use Delegate.CreateDelegate to create a delegate based on the Handle method for the specific HandlerHelper object that we created, and we give such delegate to the AddEventHandler method.
For performance reasons, you can cache the delegate (instead of creating one every time) based on event_args_type.
You can use dynamic key word to attach the handler. More explanation you can find here: https://msdn.microsoft.com/en-us/library/ms228976(v=vs.110).aspx

C# User Control - How to tell containing object the control needs data

I am creating a C# WinForms user control. There will be times when the user control will need data from the form it's contained in. How do I go about having the user control tell the form containing it that it needs some data?
Thanks!
You can subscribe the form to an event raised on the UserControl.
Your archiecture dictates where and when you need to subscribe to the data event. We can't answer that without knowing a little more about how your whether you are adding the control at runtime or design time. Each case will require a little derivation. From the perspective of adding your control at runtime, you could do something similar to the following:
// Your sample control
public class MyUserControl : Control
{
public event EventHandler<EventArgs> INeedData;
public Data Data {get; set;}
private class DoSomething()
{
if(INeedData!=null) INeedData(this,null);
}
}
...
// Your Form, in the case that the control isn't already added.
var _myUserControl = new MyUserControl();
private void Form1_Load(object sender, EventArgs e)
{
_myUserControl.INeedData += new EventHandler<EventArgs>(MyUserControl_INeedData);
this.Controls.Add(myUserControl);
}
void MyUserControl_INeedData(object sender, EventArgs e)
{
_myUserControl.Data = SomeData;
}
Create a custom event in the user control and have the form hook into it. If you need custom event arguments, you can create those too.
In user control:
//Define your Custom Event argument
public class MyEventArgs : EventArgs
{
//Define some fields of your custom event argument
private int m_SomeValue = 0;
//Define some properties of your custom event argument
public int SomeValue
{
get { return m_SomeValue; }
set { m_SomeValue = value; }
}
}
//Define the event handler data type
public delegate void MyEventHandler(object sender, MyEventArgs e);
//Define the object which holds the outside event handlers
public event MyEventHandler SomeEvent;
//Define the function which will generate the event
public virtual void OnSomeEvent(MyEventArgs e)
{
if (SomeEvent != null)
SomeEvent(this, e);
}
.
. //Then later in the control
.
{
//We need new data
//Create the event arguments
MyEventArgs newEvent = new MyEventArgs();
//Set the values
newEvent.SomeValue = 17;
//Call the event generating function
OnSomeEvent(newEvent);
}
In your form just use something like:
myControl.SomeEvent += new MyEventHandler(handlerName);
Since your event is public, you should see it in the Properties window of your control as well.
You can fancy up the event using Metadata attributes, but I leave it up to you to discover these.
Create an event on the user control where the event args are editable. Let the form attach a handler to that event, which updates those fields. Use those fields in the OnEvent method.
[untested]
eg.
public delegate void NeedsUserDataEventHandler(object sender, NeedsUserDataEventArgs args);
public class NeedsUserDataEventArgs : EventArgs
{
public UserData UserData { get; set; }
}
// In Control
public event NeedsUserDataEventHandler NeedsUserData;
public void OnNeedsUserData(NeedsUserDataEventArgs args)
{
NeedsUserDataEventHandler handler = NeedsUserData;
if (handler != null) handler(this, args);
// store your data somewhere here
}
// In Form
public override void OnLoad(object sender, EventArgs args)
{
control.NeedsUserData += ControlNeedsUserData;
}
public override void OnClosed(object sender, EventArgs args)
{
control.NeedsUserData -= ControlNeedsUserData;
}
public void ControlNeedsUserData (object sender, NeedsUserDataEventArgs args)
{
args.UserData = // set whatever here
}
Seems a bit vague to me, but:
Make it an event in the containing WinForm, so that every time some data is ready all the subscribers can be notified in a one-to-many model; or make it an event in the subscribed control, in a one-to-one model, in which it calls the container's method that retrieves such data?
It's dependent on when that data needs to be pushed to the UserControl. Are there events taking place on the Form that will drive the need to move data within the UserControl? If so...simply grab your instance at that point and push the data down to the UserControl via a public property.
If this is a case where events are not being used or the Form in some fashion or another "receives the data" then exposing an event on the Form such as...
public event DataHandler ReceivedData;
...and allow the UserControl or any other container to register for the event and receive the data via your custom event args. Pushing the event into the UserControl and forcing the Form to latch onto the UserControl seems backwards since the Form is the initiator of the data.

What is the correct way to change properties in a parent form from a child form?

I was just wondering if I'm doing this the correct way. I have 2 forms a parent form and a child form (options dialog). To change a property in my parent form from my child form I use code like this:
// Create an array of all rich textboxes on the parent form.
var controls = this.Owner.Controls.OfType<RichTextBox>();
foreach (var item in controls) {
if (chkDetectUrls.Checked)
((RichTextBox)item).DetectUrls = true;
else
((RichTextBox)item).DetectUrls = false;
}
I only have one RichTextBox on my form. It seems silly to have to loop through a array of 1 control. Is this the correct way to do it or is there an easier way?
It's not appropriate to change properties in a parent form at all. Instead, your child form should raise an event which the parent form listens for, and changes its own value accordingly.
Manipulating the parent form from the child creates a two-way coupling - the parent form owns the child, but the child also has intimate knowledge and dependency on the parent form. Bubbling is the established solution to this, as it allows information to flow upwards ('bubbling') while avoiding any strict coupling.
Here is the most basic example of eventing. It does not include passing specific information in the event (which is what you may need) but covers the concept.
In your child form:
//the event
public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{
//make sure we have someone subscribed to our event before we try to raise it
if(this.SomethingHappened != null)
{
this.SomethingHappened(this, e);
}
}
private void SomeMethod()
{
//call our method when we want to raise the event
OnSomethingHappened(EventArgs.Empty);
}
And in your parent form:
void OnInit(EventArgs e)
{
//attach a handler to the event
myChildControl.SomethingHappened += new EventHandler(HandleSomethingHappened);
}
//gets called when the control raises its event
private void HandleSomethingHappened(object sender, EventArgs e)
{
//set the properties here
}
As I said above, you probably need to pass some specific information in your event. There's a few ways we can do this, but the simplest one is to create your own EventArgs class and your own delegate. It looks like you need to specify whether some value is set to true or false, so let's use that:
public class BooleanValueChangedEventArgs : EventArgs
{
public bool NewValue;
public BooleanValueChangedEventArgs(bool value)
: base()
{
this.NewValue = value;
}
}
public delegate void HandleBooleanValueChange(object sender, BooleanValueChangedEventArgs e);
We can change our event to use these new signatures:
public event HandleBooleanValueChange SomethingHappened;
And we pass our custom EventArgs object:
bool checked = //get value
OnSomethingHappened(new BooleanValueChangedEventArgs(checked));
And we change our event handling in the parent accordingly:
void OnInit(EventArgs e)
{
//attach a handler to the event
myChildControl.SomethingHappened += new HandleBooleanValueChange(HandleSomethingHappened);
}
//gets called when the control raises its event
private void HandleSomethingHappened(object sender, BooleanValueChangedEventArgs e)
{
//set the properties here
bool value = e.NewValue;
}

Categories