Scope when using Event Handlers and Reflection - c#

I have a Button defined in Xaml like
<Button Name="button1" />
I'm trying to subscribe to the Click event of this Button using Reflection. More precisely I want to invoke an Action<object, EventArgs> and inside the event handler I want to access a local variable from outside of the scope like this
string testString = "Hello";
Action<object, EventArgs> clickAction = (object sender, EventArgs e) =>
{
MessageBox.Show(testString);
};
If I do this, Hello is displayed
clickAction.Invoke(null, null);
Similar, if I just subscribe to the Click event like this, Hello is displayed
button1.Click += (sender, e) =>
{
MessageBox.Show(testString);
};
But if I use reflection to subscribe to the event with AddEventHandler, I'm unable to access testString and I get a NullReferenceException in the event handler.
Type buttonType = button1.GetType();
EventInfo clickEvent = buttonType.GetEvent("Click");
Delegate clickEventHandler = Delegate.CreateDelegate(clickEvent.EventHandlerType,
null,
clickAction.Method);
clickEvent.AddEventHandler(button1, clickEventHandler);
Why does this happend?
Is there a fix I can apply to make it work the way I want?
Do I have another option?
Update - This is what the Method looks like in full
private void MyMethod()
{
string testString = "Hello";
Action<object, EventArgs> clickAction = (object sender, EventArgs e) =>
{
MessageBox.Show(testString);
};
Type buttonType = button1.GetType();
EventInfo clickEvent = buttonType.GetEvent("Click");
Delegate clickEventHandler = Delegate.CreateDelegate(clickEvent.EventHandlerType,
null,
clickAction.Method);
clickEvent.AddEventHandler(button1, clickEventHandler);
}

EDIT - rewriting my answer after the comments:
you can make this work if you bind the delegate to an instance. that instance should be a class containing all information/variable you want to use from within your event handler...
you create that instance, initialize it with the values you want and then bind it:
Delegate clickEventHandler = Delegate.CreateDelegate(clickEvent.EventHandlerType, yourinstance, clickAction.Method);
this way the delegate has a "this" which can be used to access the variables...
you could even make those variables instead of local just plain class fields and bind the delegate to this.
another option would be to put thos variables into the "sender object" and access them that way...

Related

How to set an EventHandler equal to another EventHandler

I am trying to create an extension method Clone() for a RichTextBox (RTB).
I want to set the TextChanged event handler of the new RTB to the TextChanged event handler of the old RTB. For example:
newRTB.TextChanged += oldRTB.TextChanged;
However, the following error is given:
"The event 'System.Windows.Forms.Control.TextChanged' can only appear on the left hand side of += or -=."
One possible solution is to add the event handler as a parameter to the clone method and just recreate the event, but I need to do this for multiple events and that would get cumbersome. Any ideas?
The "=" sign also does not seem to work.
We could copy the events via reflection. Now i myself would be wary of doing this, so please test exhaustively and with all versions (2.0, 3.0, 4.0). I tried many ways but the following was the only way, i got it to work. A Smoke test was run on .NET 4.0.
Create an extension method on the Form Class
public static class FormExtension
{
public static void CopyEvent(this Form form, Control src, string fieldName, string eventName, Control dest)
{
EventHandlerList events = (EventHandlerList)typeof(Control)
.GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(src, null);
object key = typeof(Control).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
EventInfo evInfo = typeof(Control).GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance);
Delegate del = events[key];
if (del != null)
{
Delegate d = Delegate.CreateDelegate(evInfo.EventHandlerType, form, del.Method);
MethodInfo addHandler = evInfo.GetAddMethod();
Object[] addHandlerArgs = { d };
addHandler.Invoke(dest, addHandlerArgs);
}
}
}
Now use it like this
Here i show an example of copying the click and the text changed event.
this.CopyEvent(richTextBox1, "EventText", "TextChanged", richTextBox2);
this.CopyEvent(richTextBox1, "EventClick", "Click", richTextBox2);
How to use it for other events
You would have to open the Control class via Reflector and get the field and the eventnames.
So in the case of Text Changed it was something like:
public event EventHandler TextChanged <-----The Event name for the "CopyEvent" function
{
add
{
base.Events.AddHandler(EventText, value);
}
remove
{
base.Events.RemoveHandler(EventText, value);
}
}
where EventText is
private static readonly object EventText = new object(); <-------The Field name
Try this:
newRTB.TextChanged += new TextChangedEventHandler(oldRTB_textChanged);
Create a method like this:
void oldRTB_textChanged(object sender, EventArgs e)
{
// do something
}
If you precreate the TextChanged event, you can do this, in a form put two TextBox and a Button, this will make your controls share the event.
private void button1_Click(object sender, EventArgs e)
{
textBox2.TextChanged += new EventHandler(textbox1_TextChanged);
}
private void textbox1_TextChanged(object sender, EventArgs e)
{
MessageBox.Show("this");
}
UPDATE
in a different file, create the method you want, you can take the code of the current event and copy in this method, then you just assign it to the new controls
void MyCloned_TextChanged(object sender, EventArgs e)
{
// Shared method OldRTB to share with NewRTB
}
In the form you will use it like this
public Form1()
{
InitializeComponent();
textBox1.TextChanged += new EventHandler(MyCloned_TextChanged);
textBox2.TextChanged += new EventHandler(MyCloned_TextChanged);
}

Is += delegate {} the correct way to attach custom eventargs to a UI control event?

I have been tinkering with Events to gain a better understanding of their use in very general situations. I'm surprised to find the following, so I'm probably heading in the wrong direction...the essence of what I'm doing is changing a button to a random color when it is clicked:
Windows Form
public Form1()
{
ColorChanges KK = new ColorChanges();
KK.ColorEventHandler += handle_ColorChanges;
button1.Click += delegate { KK.ChangeColor(button1); };
}
Event Class
class ColorChanges
{
*... properties & constructor*
public void ChangeColor(object sender)
{
*... randomly assign color to ColorEventArgs*
}
protected virtual void onColorEvent(object sender, ColorEventArgs e)
{
EventHandler<ColorEventArgs> ceh = ColorEventHandler;
{
if (ceh != null)
{
ceh(sender, e)
}
}
}
public event EventHandler<ColorEventArgs> ColorEventHandler;
}
Custom Event Args
public class ColorEventArgs : EventArgs
{
public Color xColor { get; set; }
}
Event Handler
public void handle_ColorChanges(object sender, ColorEventArgs e)
{
if (sender is Button)
{
var ButtonSender = (Button)sender;
ButtonSender.BackColor = e.xColor;
}
}
So the edited questions are:
Is use of the EventHandler(TEventArgs) Delegate useful? MS documentation indicates that syntax like
button1.Click += new EventHandler<AutoRndColorEventArgs>(handle_ColorChanges);
is correct, but that will not reach my code to randomly select a color and an error
"No overload for 'handle_ColorChanges' matches delegate >'System.EventHandler' "
so something like
button1.Click += new EventHandler<AutoRndColorEventArgs>(KK.ChangeColor(button1));
or
button1.Click += new EventHandler(KK.ChangeColor(button1));
Error says that a method is required and if I use
"No overload for 'handle_ColorChanges' matches delegate
'System.EventHandler'"
Lambda expressions help thanks for the supporting answers
button1.Click += (sender,args) => KK.ChangeColor(s);
But that doesn't allow un-assignment and that will be required later...
An anonymous delegate has the same problem
button1.Click += delegate(object sender, EventArgs e)
{ KK.ChangeColor(sender); };
The crux of the problem is that my color methods or their delegates do not match the button delegate signature (object, event). I don't care about the button args and want to use my own HOW?
Is the use of the delegate correct?
Yep, what you are doing is assigning an anonymous delegate as your event handler. This is perfectly valid, however, the catch here is you can't unassign the event handler because you have no reference to it. You could keep a reference to it and do it that way (if required)
var clickHandler = delegate { ... };
button1.Click += clickHandler;
...
button1.Click -= clickHandler
If you need access to the parameters of the event handler you will need to add those into the signature e.g.
button1.Click += delegate (object sender, EventArgs args) { ... }
The new EventHandler(SomeHandlerMethod) construct is the long way of doing things, it's synonymous to += SomeHandlerMethod. Your code currently doesn't work because you are trying to actually call the handler inside the constructor when the constructor expects a reference to the method
+= new EventHandler<ColorEventArgs>(KK.ChangeColor);
Is there a better structure for this?
Yeah, you can do it using even less code
button1.Click += (s, args) => KK.ChangeColor(button1);
This is incorrect:
button1.Click += new EventHandler<AutoRndColorEventArgs>(KK.ChangeColor(button1));
Instead of KK.ChangeColor(button1), you just need to specify the event handler method name as you did in here:
KK.ColorEventHandler += handle_ColorChanges;
The event handler method signature should match with the EventHandler delegate.If you want to just call a method in event handler, you can use lambda statement like this:
button1.Click += (s,e) => KK.ChangeColor(s);
Or:
button1.Click += delegate(object s, EventArgs e) { KK.ChangeColor(s); };
By doing this you are creating an anonymous method and attach it to your Click event.

describe the meaning of the shorthand notation of .net?("+=","=>")

The first one code is shorthand notation of second:
itemCountLines.Click = itemCountLines.Click + (sender, args) => countLines();
itemCountLines.Click += (sender, args) => CountLines();
But i did not understand what this expression is doing.Anybody Please Explain it to me
This code adds an handler to the Control.Click event:
public event EventHandler Click
where EventHandler is a delegate of type:
public delegate void EventHandler(
object sender,
EventArgs e
)
Normally, given you have a method with the same signature:
void SomeClickHandler(object sender, EventArgs e)
{
CountLines();
}
you would add this handler to handle Click event:
itemCountLines.Click += SomeClickHandler;
Operator += is possible because Click is an event, so you can add or remove a multiple EventHandlers to it. Simple speaking, after some control is clicked, you may want to make multiple actions (show some other control, log it to the database etc) so you are able to add multiple event handlers. You can even do itemCountLines.Click -= SomeClickHandler somewhere later to say, you do not want to handle Click with SomeClickHandler anymore.
But above code needs to define method SomeClickHandler which sometimes is unnecessary (for example, it is used only one in whole class). Then you can use anonymous delegate (added in C# 2.0):
itemCountLines.Click += delegate(object sender, EventArgs args)
{
CountLines();
};
but you can further shorten this syntax with lambda expression (added in C# 3.0) to:
itemCountLines.Click += (sender, args) => CountLines();
It simply add an event to the list of listeners itemCountLines.Click = itemCountLines.Click + (sender, args) so when an event occurs the instance of sender will be notified to raise the event inline => countLines(); as you are using lambda Expression => which will invoke countLines method
You're just attaching an event on Click, it is the same as saying
itemCountLines.Click += CountLines(sender, args);
Somewhere, there should be a method like this :
private void CountLines()
{
// Some Code There
}

EventHandler with a different type of sender

I'm trying to implement drag and drop for a specific object of a Type that I've created in c# for windows phone 8. I'm using Manipulation Events like this :
deck[r[i, j]].card.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(ImageManipulationCompleted);
private void ImageManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
//something
}
How can I change object to the type that I want ?
keyboardP's solution will work just fine. But I personally prefer to store the information I need in the Tag property of the control, which has been designed for this very purpose.
deck[r[i, j]].card.Tag = deck[r[i, j]];
deck[r[i, j]].card.ManipulationCompleted += ImageManipulationCompleted;
private void ImageManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
var deck = (Deck)((FrameworkElement)sender).Tag;
}
The good side of keyboardP's approach is that, since you receive directly the desired object as a parameter of your method, it's easier to read. The downside is that you have to declare a custom delegate for every event you need, and you lose the ability to assign event handlers directly from the XAML. My solution is a bit harder to read, but addresses this point.
In the end, which solution is better really depends on your tastes and your needs.
What you could do is just call a method that takes in your type instead of using the standard ImageManipulationCompleted handler. I don't know what the deck[r[i, j]] type is but you can replace MyType below with the correct type.
deck[r[i, j]].card.ManipulationCompleted += delegate(object s, ManipulationCompletedEventArgs e){ CardManipulated(s, e, deck[r[i, j]]); };
private void CardManipulated(object sender, ManipulationCompletedEventArgs e, MyType selectedObject)
{
//you know have access to selectedObject which is of type deck[r[i, j]],
//the ManipluationCompletedEvents properties if needed,
//and the actual card Image object (sender).
}
You cant.
Since you are subscribing to an event with this code new EventHandler<>(..), you cannot change the type of sender because in the description of EventHandler<> there is only object sender:
public delegate EventHandler<T>(object sender, T eventArgs) where T : EventArgs
If you need to create your own delegate, you could make a factory or simply write this:
public delegate EventHandler<T, TArgs>(T sender, TArgs eventArgs) where TTArgs : EventArgs
ManipulationCompletedEventHandler signature is using object in its first parameter
public delegate void ManipulationCompletedEventHandler(object sender,
ManipulationCompletedRoutedEventArgs e);
So, you can't change the signature but you can use delegate to typecast object always to your type like this -
deck[r[i, j]].card.ManipulationCompleted += (s, e) =>
ManipulateMe_ManipulationCompleted((YourType)s, e);
private void ImageManipulationCompleted(YourType sender,
ManipulationCompletedEventArgs e)
{
//something
}
Replace YourType with your desired Type (TextBox or something whichever you want)

C# attach event handling with additional parameters

PREFACE
I have a Windows form Button that exposes the event OnClick (object sender, EventArgs e).
In my application I can handle this by using the classic event handling technique of C#:
// Button Creation
Button button = new Button();
button.Click += MyEventHandler;
Then Windows Form ask me for an handler with the following signature:
public void MyEventHandler(object sender, EventArgs e) { }
Suppose that I would like to use lambda expression to do this I can use the syntax:
button.Click += (sender, args) =>
{
// execute the code
};
The drawback of this is that I can't unsubscribe from a Lambda expression.
QUESTION
What I would like to do is to have an utility class that will allow me to handle any Click event plus using an additional Action as a parameter. So I would like to write something like this:
button.Click += MyUtility.Click(()
=> {
// the custom code the Click event will execute
})
Can I do it in somehow?
You can assign the lambda expression to a local variable or field.
For example:
EventHandler handler = (sender, args) =>
{
// execute the code
};
button.Click += handler;
button.Click -= handler;
If you want to unsubscribe inside the handler, you'll need to assign handler to null, then to the lambda, to avoid definite assignment issues.

Categories

Resources