Simple C# event args question - c#

Im using WPF which has a Storyboard class that has a Completed event.
I use it like so:
sb.Completed += AddControlToTaskbar;
private void AddControlToTaskbar(object sender, EventArgs args)
{
//...
}
How to I pass in the EventArgs to my method? Its always null, and I need it to be a custom class
Thanks
Mark

You don't pass the EventArgs to your method, the framework which dispatches the event does that. A common way to handle this is to wrap up your AddControlToTaskbar method in a class which stores the state, e.g.:
sb.Completed += new MyCustomClass(theStateYouNeedToStore).AddControlToTaskbar;
Your constructor stores the state.
class MyCustomClass<T> {
private T state;
public MyCustomClass(T state) {
this.state = state;
}
public void AddControlToTaskbar(object sender, EventArgs args) {
// Do what you need to do, you have access to your state via this.state.
}
}

Related

Generic method to register/unregister event handler

I try to figure out a way to keep my event registration and un-registration in sync from my initialization and cleanup.
What I want is to be able to call a generic method to register or unregister an event and only pass a Boolean to give the operation.
I don't want to use with Window, but that was the for an easy sample.
class EventSample
{
private Window myWindow;
public EventSample(Window window)
{
myWindow = window;
InitEvent(true);
}
~EventSample()
{
InitEvent(false);
}
private void InitEvent(bool register)
{
// I want a generic similar to that
RegisterEvent(register, myWindow.Activated, MyWindow_Activated);
RegisterEvent(register,myWindow.Closed , MyWindow_Closed);
RegisterEvent(register, myWindow.Closing ,MyWindow_Closing);
}
private void MyWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
}
private void MyWindow_Closed(object sender, EventArgs e)
{
}
private void MyWindow_Activated(object sender, EventArgs e)
{
}
}
I have faced similar problems with cleanup methods. I have solved this by keeping a list of actions to execute when disposing the window or control.
Something like this:
this.RegisterEvent
( () => this.Event += handler
, () => this.Event -= handler
);
The RegisterEvent executes (delayed in my case) the event attachment:
private List<Action> unregisterEvents = new List<Action>();
private void RegisterEvent(Action registerAction, Action unregisterAction)
{
registerAction.Invoke();
unregisterEvents.Add(unregisterAction);
}
On dispose, just walk over the unregister events:
foreach (Action a in unregisterEvents)
{
a.Invoke();
}

GUI Invoke with EventHandlers

I have a class like that "ClientSocket.cs"
class ClientSocket {
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
ev(this, e);
}
}
And another class "myForm.cs"
public partial class myForm : Form {
private ClientSocket client;
private void button1_Click(object sender, EventArgs e) {
client = new ClientSocket();
client.ConnectEvent += myForm_OnConnectEvent;
client.connect();
}
// Handler for ConnectEvent
private void myForm_OnConnectEvent(object sender, EventArgs e) {
//this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
writeLog("Connected");
}
// Function that write a log string to a TextBox
public writeLog(string log) {
guiTextBox.AppendText(log);
}
}
Here the question.
I try to call writeLog with BeginInvoke or calling it directly. Sometimes I get an InvalidOperationException when writing to guiTextBox.
I don't understand why I receive that message. The event is fired by ClientSocket object, but the event handler is relative to the main UI-thread (myForm).
Can I avoid to use BeginInvoke/Invoke for each EventHandler of my class?
EDIT: I understand what's the difference, now I'm try to understand the best practice for calling the event.
Should I put the BeginInvoke/Invoke method when RAISING the event in the BASE class (ClientSocket in that case)
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
}
or should I put that WHEN I'm using that object and add a listeners to that handler
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) {
this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
}
Cheers
the this.BeginInvoke inside ClientSocket does not exist. To be able to do the BeginInvoke it must be called on a object that has that method (your form in your case).
If you wanted the invoking to happen inside your ClientSocket class you would need to pass in a Control that has the BeginInvoke function.
However if I where writing this I would not do this approach. It adds a unnecessary requirement to ClientSocket that you must have a Control passed in (this is called Tightly Coupling and you should try to avoid it in your programming). Personally I would let the event pass along in whatever thread it wants to be raised in and let the consumer worry about doing any special invoking (if they even need to at all).
Here is how I would write myForm_OnConnectEvent, this pattern checks to see if we need to invoke and if we do it calls the function again with the same arguments but this time on the UI thread.
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
return;
}
writeLog("Connected");
}
As a side note, I don't know what writeLog is doing (it should have a capital W by the way) but if it is not interacting with the UI you don't need to do any invoking at all. If it interacts with a TextBox or something else on the UI, that is where I would do my invoking.
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
writeLog("Connected");
}
private void writeLog(string logMessage)
{
if(logTextBox.InvokeRequired)
{
logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
return;
}
var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
logTextBox.AppendText(logLine);
}
The event handler is declared in myForm, but the thread, which executes handler, is defined by the logic of ClientSocket class. If this will be background thread, event handler will be raised from background thread, so, you'll need BeginInvoke to avoid cross-thread access to controls.
In other words: belonging of any method of any type isn't related to the thread, which will ever execute this method. These things (types and threads) are parallel universes.
By the way, you can replace this:
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
with this:
public event EventHandler ConnectEvent;
There's no need to make yet another delegate type.

One handler for several events

I have four events:
View.AdditionPerformed += new EventHandler<EventArgs>(OnOperationPerformed);
View.SubtractionPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
View.DivisionPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
View.MultiplyPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
and one method:
private void OnOperationPerformed(object sender, EventArgs e)
{
}
How can I define which event raised my method? Something like this:
private void OnOperationPerformed(object sender, EventArgs e)
{
switch(event)
{
case MultiplyPerformed:{}
case DivisionPerformed:{}
...
}
}
Write your own EventArgs which has an enum inside, telling you the raised event.
enum MyEventEnum
{
AdditionPerformed,
SubtractionPerformed,
DivisionPerformed,
MultiplayPerformed
}
The EventArgs
class MyEventArgs : EventArgs
{
public MyEventEnum EventRaised { get; set; }
}
Define the Handlers
View.AdditionPerformed += new EventHandler<MyEventArgs>(OnOperationPerformed);
View.SubtractionPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
View.DivisionPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
View.MultiplyPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
When you call them:
this.AdditionPerformed(this, new MyEventArgs
{ EventRaised = MyEventEnum.AdditionPerformed };
I know it's pretty hardcoded, but there isn't any other way.
Instead of using EventArgs, you could use your own event argument class to pass in the necessary data to make the choice inside the handler.
It would then become available on your e variable inside the handler.
Cheers

Why can't I directly pass a custom event args class into a method subscribing to my event?

Why can't I directly pass custom event arguments into a method subscribing to my event even though my custom event args class directly inherits from EventArgs?
For example, consider the two below classes. One is the class I want to work with, the other inherits from EventArgs, and contains some additional information related to the event:
using System;
namespace ConsoleApplication11
{
class MyClass
{
public event EventHandler MyEvent;
public void MyMethod(int myNumber)
{
Console.WriteLine(myNumber);
if(myNumber == 7)
{
MyEvent.Invoke(null, new MyCustomEvent() { Foo = "Bar" });
}
}
}
class MyCustomEvent : EventArgs
{
public string Foo { get; set; }
}
}
So if a number 7 is passed into MyMethod, I want to invoke MyEvent passing in the a new instance of the MyCustomEvent class to any method subscribing to my event.
I subscribe to this from my main program like so:
using System;
namespace ConsoleApplication11
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.MyEvent += new EventHandler(myClass_MyEvent);
for (int i = 0; i < 9; i++)
{
myClass.MyMethod(i);
}
}
static void myClass_MyEvent(object sender, EventArgs e)
{
//Do Stuff
}
}
}
Even though I am passing in a MyCustomEvent object when invoking the event, if I change the second parameter in the method subscribing to my event to a MyCustomEvent object I get a compile error. I need to instead explicitly cast the EventArgs object to a MyCustomEvent before I can access any additional fields/methods etc inside the class.
When working with an object that has a lot of different events, each one having a unique related custom EventArgs class, keeping track of what the EventArgs in each method subscribing to each event needs to be casted to can get a bit messy.
It would be a lot easier if I could pass my custom EventArgs class directly into the methods subscribing to my event.
Is this a possibility?
Thanks
Instead of declaring your event as being of type EventHandler, create a new delegate that specifically uses your custom event args class:
public delegate void MyEventHandler(object sender, MyEventArgs args);
public event MyEventHandler MyEvent;
Now you can pass the arguments directly without casting.
The other option is to use the generic EventHandler<T>:
public event EventHandler<MyEventArgs> MyEvent;
I prefer the former, since you can then give the delegate type a more descriptive name, though the latter is a bit quicker.
Declare your event using generic version of EventHandler:
public event EventHandler<MyCustomEvent> MyEvent;
Rather than use a default event handler just define your own that fits the signature you are trying to achieve...
public delegate void MyEventHandler(object sender, MyCustomEventArgs e)
public event MyEventHandler MyEvent;
forgive me if i understood your question wrong. I'm a little confused by not seeing any custom EventArgs class.
You can't make a method signature like
static void myClass_MyEvent(object sender, CustomEventArgs e)
for an event that expects a
static void myClass_MyEvent(object sender, EventArgs e)
That is backwards polymorphism, and polymorphism doesn't work backwards.
let's say your event accesses a e.OnlyInCustomEventArgs, but is instead passed the base object, EventArgs. Then the contract has been broken.
Now what you can do is
static void myClass_MyEvent(object sender, EventArgs e)
{
CustomEventArgs cea = (CustomEventArgs)e;
}
Here is the full code for a correctly implemented custom arg event handler.
class MyClass
{
public event EventHandler<MyCustomEventArgs> MyEvent;
public void MyMethod(int myNumber)
{
Console.WriteLine(myNumber);
if (myNumber == 7)
{
MyEvent.Invoke(null, new MyCustomEventArgs() { Foo = "Bar" });
}
}
}
class MyCustomEventArgs : EventArgs
{
public string Foo { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.MyEvent += new EventHandler<MyCustomEventArgs>(myClass_MyEvent);
for (int i = 0; i < 9; i++)
{
myClass.MyMethod(i);
}
}
//This is the method signature that you need to use to handle the event
static void myClass_MyEvent(object sender, MyCustomEventArgs e)
{
Console.WriteLine(e.Foo); // prints "Bar"
//Do Stuff
}
//If you need to handle more than one type of custom event args
static void myClass_MyEvent<T>(object sender, T e) where T : EventArgs
{
//Do Stuff
}
}

How can I pass addition parameters to my centralized event handlers?

In a WPF application, I've got my events centralized in one class like this:
public class EventFactory
{
public static void Button_Edit_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("you clicked edit");
}
public static void Button_Add_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("you clicked add");
}
}
so that I can reuse them in many Windows like this:
public Window1()
{
InitializeComponent();
ButtonEdit.Click += EventFactory.Button_Edit_Click;
ButtonAdd.Click += EventFactory.Button_Add_Click;
}
This works fine, but now I want the events to act on the Windows which call them which I was able to do when the event handlers were simply in the code-behind for each window.
How can I e.g. inject a window object into the event handler so that that event handler can directly manipulate it, something like this:
ButtonEdit.Click += EventFactory.Button_Edit_Click(this);
One way:
ButtonEdit.Click += EventFactory.ForConsumer<Window1>().Button_Edit_Click;
In other words, turn your factory class into an actual factory that creates objects based on some context. In this case, the context is the object consuming the events.
Another way:
public static void Button_Edit_Click(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(sender as DependencyObject);
MessageBox.Show("you clicked edit");
}
I'm not particularly fond of either of these approaches, but there you go.
You can try something like this:
public class CommonEventHandler
{
private CommonEventHandler() { }
private object Context { get; set; }
public static EventHandler CreateShowHandlerFor(object context)
{
CommonEventHandler handler = new CommonEventHandler();
handler.Context = context;
return new EventHandler(handler.HandleGenericShow);
}
private void HandleGenericShow(object sender, EventArgs e)
{
Console.WriteLine(this.Context);
}
}
class Program
{
static void Main(string[] args)
{
EventHandler show5 = CommonEventHandler.CreateShowHandlerFor(5);
EventHandler show7 = CommonEventHandler.CreateShowHandlerFor(7);
show5(null, EventArgs.Empty);
Console.WriteLine("===");
show7(null, EventArgs.Empty);
}
}
You need to adapt the types to suit your needs but it shows the general idea.

Categories