I have a problem with MouseEvents on my WinForm C# application.
I want to get all mouse clicks on my application, but I don't want to put a listener in every child component neither use Windows mouse hook.
On Flash I could put a listener on Stage to get all the MouseEvents on the movie.
Is there such thing on C#? A global MouseListener?
Edit:
I create this class from IMessageFilter ans used Application.AddMessageFilter.
public class GlobalMouseHandler : IMessageFilter{
private const int WM_LBUTTONDOWN = 0x201;
public bool PreFilterMessage(ref Message m){
if (m.Msg == WM_LBUTTONDOWN) {
// Do stuffs
}
return false;
}
}
And put this code on the Controls that need listen global clicks:
GlobalMouseHandler globalClick = new GlobalMouseHandler();
Application.AddMessageFilter(globalClick);
One straightforward way to do this is to add a message loop filter by calling Application.AddMessageFilter and writing a class that implements the IMessageFilter interface.
Via IMessageFilter.PreFilterMessage, your class gets to see any inputs messages that pass through your application's message loop. PreFilterMessage also gets to decide whether to pass these messages on to the specific control to which they're destined.
One piece of complexity that this approach introduces is having to deal with Windows messages, via the Message struct passed to your PreFilterMessage method. This means referring to the Win32 documention on WM\_LBUTTONDOWN, WM\_MOUSEMOVE, WM\_LBUTTONUP etc, instead of the conventional MouseDown, MouseMove and MouseUp events.
Sample Class
class CaptureEvents : IMessageFilter
{
#region IMessageFilter Members
public delegate void Callback(int message);
public event Callback MessageReceived;
IntPtr ownerWindow;
Hashtable interestedMessages = null;
CaptureEvents(IntPtr handle, int[] messages)
{
ownerWindow = handle;
for(int c = 0; c < messages.Length ; c++)
{
interestedMessages[messages[c]] = 0;
}
}
public bool PreFilterMessage(ref Message m)
{
if (m.HWnd == ownerWindow && interestedMessages.ContainsKey(m.Msg))
{
MessageReceived(m.Msg);
}
return true;
}
#endregion
}
Take a look at this article. It recursively hoooks all the control events and broadcasts them. You could also override WndProc in your form.
If you don't want to handle the messages by overriding Form.PreProcessMessage or Form.WndProc then you could subclass Form to hook an event handler to all the MouseClick events from the various controls on the form.
EDIT: forgot to recurse through child controls of controls on the form.
public class MousePreviewForm : Form
{
protected override void OnClosed(EventArgs e)
{
UnhookControl(this as Control);
base.OnClosed(e);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
HookControl(this as Control);
}
private void HookControl(Control controlToHook)
{
controlToHook.MouseClick += AllControlsMouseClick;
foreach (Control ctl in controlToHook.Controls)
{
HookControl(ctl);
}
}
private void UnhookControl(Control controlToUnhook)
{
controlToUnhook.MouseClick -= AllControlsMouseClick;
foreach (Control ctl in controlToUnhook.Controls)
{
UnhookControl(ctl);
}
}
void AllControlsMouseClick(object sender, MouseEventArgs e)
{
//do clever stuff here...
throw new NotImplementedException();
}
}
Your forms would then need to derive from MousePreviewForm not System.Windows.Forms.Form.
Related
I'm learning C# and I need help, please.
My question: how to know whether a USB-disk has been mounted/unmounted?
I found an answer for WndProd
const int WM_DEVICECHANGE = 0x0219;
const int DBT_DEVICEARRIVAL = 0x8000;
const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HDR
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DEVICECHANGE)
{
int EventCode = m.WParam.ToInt32();
Log(string.Format("WM_DEVICECHANGE. Код={0}", EventCode));
switch (EventCode)
{
case DBT_DEVICEARRIVAL:
{
Log("Добавление устройства");
break;
}
case DBT_DEVICEREMOVECOMPLETE:
{
Log("Удаление устройства");
break;
}
}
}
base.WndProc (ref m);
}
and this version
public class WMIReceiveEvent
{
public WMIReceiveEvent()
{
try
{
WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
watcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
// Start listening for events
watcher.Start();
// Do something while waiting for events
System.Threading.Thread.Sleep(20000);
// Stop listening for events
//watcher.Stop();
//return;
}
catch (ManagementException err)
{
}
}
private void HandleEvent(object sender, EventArrivedEventArgs e)
{
Console.WriteLine("Win32_DeviceChangeEvent event occurred. "+ e.NewEvent.ClassPath.ClassName.ToString());
Console.WriteLine("2_Win32_DeviceChangeEvent event occurred. " + e.NewEvent.Properties.ToString());
Console.ReadLine();
}
}
but I would like version for DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE without WinForm. Because for WndProc need System.Windows.Form and Class must be the successor ":Form"
And for WMIReceiveEvent not the best solution for my task.
You can use NativeWindow instead of Form and still use WndProc(ref Message msg).
It's practically an invisible form, see example:
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class MyMessageHandler : NativeWindow
{
private event EventHandler<MyEventArgs> messageReceived;
public event EventHandler<MyEventArgs> MessageReceived
{
add
{
if (messageReceived == null || !messageReceived.GetInvocationList().Contains(value))
messageReceived += value;
}
remove
{
messageReceived -= value;
}
}
public MyMessageHandler()
{
var cp = new CreateParams();
CreateHandle(cp);
}
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message msg)
{
var handler = messageReceived;
if (handler != null)
handler(this, new MyEventArgs(msg));
base.WndProc(ref msg);
}
}
The problem with writing a Console application for this is that it doesn't have a message loop (at least, not by default; you would have to write your own).
The simpler solution is to create a Windows Forms project, but just don't show any forms. You would essentially be creating a "background" application that doesn't display any user interface. WinForms applications provide a message pump for you automatically, allowing you to catch the messages you're interested in.
Depending on the requirements of your application you might as well do polling. You build a loop checking all possible drive letters like
System.IO.Directory.Exists(driveLetter);
and compare it to an existing drive letter array or struct or whatever. Create an event as soon as they are different.
That would be the easy way though not as fantastic regarding performance. But as I said, it depends on your requirements.
It's quite hard to explain this in the title, if someone would like to change it it's ok.
I have a situation where, in WPF, I create an "hidden" window which is transparent to the programmer.
What I mean is that this window is created in static constructor, hidden and moved outside of the screen and it's width and height are 0. This because I'm using this window to do some interop operations and to allow a sort of handlers for all WndProcs override that someone could require (there is a list of delegates which handles methods that should override WndProc).
In hope that you understand what I've said (it's not easy), my problem is that when I create a WPF project and start it, if I close the Main Window (which is not the one created transparently to the programmer), I want that my application shutdown. However with the code I created this doesn't happen except if I use Application.Current.Shutdown();
Are there any way to fix this without calling that method? I want a transparent way, that other programmers shouldn't even notice (it's a lib, shouldn't change the behaviour of working programs in this way).
Thanks for any suggestion, here you can see some code snippets:
The window created by the lib
public class InteropWindow : Window
{
public HwndSource Source { get; protected set; }
private static InteropWindow _Instance;
static InteropWindow()
{
_WndProcs = new LinkedList<WndProcHandler>();
_Instance = new InteropWindow();
}
private static WindowInteropHelper _InteropHelper;
public static WindowInteropHelper InteropHelper
{
get
{
if (_InteropHelper == null)
{
_InteropHelper = new WindowInteropHelper(_Instance);
_InteropHelper.EnsureHandle();
}
return _InteropHelper;
}
}
public static IntPtr Handle { get { return InteropHelper.Handle; } }
private InteropWindow()
{
Opacity = 0.0;
//We have to "show" the window in order to obtain hwnd to process WndProc messages in WPF
Top = -10;
Left = -10;
Width = 0;
Height = 0;
WindowStyle = WindowStyle.None;
ShowInTaskbar = false;
ShowActivated = false;
Show();
Hide();
}
private static LinkedList<WndProcHandler> _WndProcs;
public static void AddWndProcHandler(WndProcHandler handler)
{
_WndProcs.AddLast(handler);
}
public static void RemoveWndProcHandler(WndProcHandler handler)
{
_WndProcs.Remove(handler);
}
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
IntPtr result = IntPtr.Zero;
foreach (WndProcHandler handler in _WndProcs)
{
IntPtr tmp = handler(hwnd, msg, wParam, lParam, ref handled);
if (tmp != IntPtr.Zero)
{
if (result != IntPtr.Zero)
throw new InvalidOperationException(string.Format("result should be zero if tmp is non-zero:\nresult: {0}\ntmp: {1}", result.ToInt64().ToString(), tmp.ToInt64().ToString()));
result = tmp;
}
}
return result;
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
Source = PresentationSource.FromVisual(this) as HwndSource;
Source.AddHook(WndProc);
OnWindowInitialized(null, e);
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (Source != null)
Source.RemoveHook(WndProc);
OnWindowClosed(null, e);
}
private static void OnWindowInitialized(object sender, EventArgs e)
{
if (WindowInitialized != null) WindowInitialized(sender, e);
}
private static void OnWindowClosed(object sender, EventArgs e)
{
if (WindowClosed != null) WindowClosed(sender, e);
}
public static event EventHandler WindowInitialized;
public static event EventHandler WindowClosed;
}
A normal window created with wpf (base window created from project)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ExClipboard.ClipboardUpdate += new RoutedEventHandler(ExClipboard_ClipboardUpdate);
Closed += new EventHandler(MainWindow_Closed);
}
private void MainWindow_Closed(object sender, EventArgs e)
{
//InteropWindow.Dispose();
App.Current.Shutdown(0);
}
}
Update 1:
To answer to your answers, No I would like to avoid any intervetion by programmer using my library, so the ideal solution is that in my lib I subscribe to some Application.Exit event and close my window, obviusly I can't use Application.Exit because the application doesn't close due of my window not closing
Maybe there is a way to calculate all windows that belongs to an application? I can do something with that too
If you have a main window, can't you set Application.ShutdownMode to OnMainWindowClose ?
The default value is OnLastWindowClose, which is most likely why you're seeing this behaviour.
It's a cheap hack, but I think this may achieve what you're after..
In your lib you will need to reference the xaml dependencies (PresentationCore, PresentationFramework, System.Xaml and WindowsBase)
In the static constructor for your lib, you can then add something like
Application.Current.MainWindow.Closed += new EventHandler(MainWindow_Closed);
static void MainWindow_Closed(object sender, EventArgs e)
{
Dispose();
}
Where dispose closes your window (_Instance.Close()) and handles any other clean-up calls
Conceptually speaking, the only thing that comes to mind is an event message notification service, in which, the second window is listening or awaiting a message to close and the first window sends a message for it to be closed. This also requires using the MVVM pattern. I'm not sure about this entirely, and I am also not sure if this falls into your idea of not letting other programmers know.
Here is a blog article on it:
Sending notifications in WPF MVVM applications
first of all i'm kinda new to C#
I'm trying to do something like this in a C# winforms application
when my app starts, a form starts minimized in the system tray. when i double click it, it opens and sends a request to a qpid broker for some info. then a message is sent back, and received in a listener in my app (i'm not sure code is relevant but i'll post it anyway)
namespace MyApp
{
public class MyListener : IMessageListener
{
public void MessageTransfer(IMessage m)
{
//do stuff with m
}
}
}
what i'm trying to do is populate a listbox that's in that form with the message received in that function, but i have no idea how to communicate with that specific form from the MessageTransfer function
I would suggest that your listener has no knowledge about how the messages are to be presented. Instead, expose an event that the form can listen to:
// event args class for transmitting the message in the event
public class MessageEventArgs : EventArgs
{
public IMessage Message { get; private set; }
public MessageEventArgs(IMessage message)
{
Message = message;
}
}
In your listener class:
public class MyListener : IMessageListener
{
public event EventHandler<MessageEventArgs> MessageReceived;
public void MessageTransfer(IMessage m)
{
OnMessageReceived(new MessageEventArgs(m));
}
protected void OnMessageReceived(MessageEventArgs e)
{
EventHandler<MessageEventArgs> temp = MessageReceived;
if (temp != null)
{
temp(this, e);
}
}
}
Now you can add an event listener in your form and add the message information to a listbox or any other kind of control you like.
Update
Here is an example on how to hook up the event handler in the form. This code makes two assumptions:
The event MessageReceived is defined in the IMessageListener interface
The IMessage interface has a property called Text.
Code sample:
public partial class MainUI : Form
{
private IMessageListener _messageListener;
public MainUI()
{
InitializeComponent();
_messageListener = new MyListener();
_messageListener.MessageReceived += MessageListener_MessageReceived;
}
void MessageListener_MessageReceived(object sender, MessageEventArgs e)
{
_messageListBox.Items.Add(e.Message.Text);
}
}
If your forms holds the listener the simplest way is to create an event that the listener will rise for each message that is being transfered.
add the following to your listener class:
public delegate void MessageHandler(IMessage m);
public event MessageHandler MessageReceived;
add the following to your MessageTransfer method:
if (MessageReceived != null)
MessageReceived(m);
Now in your form you can attach a method for the event you just created:
The following line should be placed in the form after you initialize the listener:
_listener.MessageReceived += new MessageHandler(Form1_MessageReceived);
The following method should me placed in the form itself:
void Form1_MessageReceived(IMessage m)
{
// add the message to the list
}
There is one more thing you need to do if the listener is running on a different thread, and thats to invoke another method in the forms thread to modify the list.
I need to be able to trigger a event whenever an object is added to a Queue<Delegate>.
I created a new class that extends Queue:
public delegate void ChangedEventHandler(object sender, EventArgs e);
public class QueueWithChange<Delegate> : Queue<Delegate>
{
public event ChangedEventHandler Changed;
protected virtual void OnChanged(EventArgs e) {
if (Changed != null)
{
Changed(this, e);
}
}
}
And then attached the event from another class, like such:
QueueWithChange<TimerDelegate> eventQueue = new QueueWithChange<TimerDelegate>();
//
eventQueue.Changed += new ChangedEventHandler(delegate(object s, EventArgs ex) {
//This event is not being triggered, so this code is unreachable atm...and that is my problem
if (eventQueue.Count > 0)
{
eventQueue.Dequeue().Invoke(new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(5) });
actionTimer.Stop();
}
});
But whenever I enqueue an object (eventQueue.Enqueue(something)), the attached event is not being fired.
What am I missing here?
If you mean the non-generic Queue class, then you can just override Enqueue:
public override void Enqueue(object obj)
{
base.Enqueue(obj);
OnChanged(EventArgs.Empty);
}
However, if you mean the generic Queue<T> class, then note that there is no suitable virtual method to override. You might do better to encapsulate the queue with your own class:
(** important edit: removed base-class!!! **)
class Foo<T>
{
private readonly Queue<T> queue = new Queue<T>();
public event EventHandler Changed;
protected virtual void OnChanged()
{
if (Changed != null) Changed(this, EventArgs.Empty);
}
public virtual void Enqueue(T item)
{
queue.Enqueue(item);
OnChanged();
}
public int Count { get { return queue.Count; } }
public virtual T Dequeue()
{
T item = queue.Dequeue();
OnChanged();
return item;
}
}
However, looking at your code, it seems possible that you are using multiple threads here. If that is the case, consider a threaded queue instead.
I just did write up on what I call a TriggeredQueue. It's inspired the answer by Marc Gravell.
You can find my post here: http://joesauve.com/triggeredqueuet
And the Gist here: http://gist.github.com/jsauve/b2e8496172fdabd370c4
It has four events:
WillEnqueue
WillDequeue
DidEnqueue
DidDequeue
You can hook into any of these like so:
YourQueue.WillEnqueue += (sender, e) => {
// kick off some process
};
YourQueue.DidEnqueue += (sender, e) => {
// kick off some process
// e.Item provides access to the enqueued item, if you like
};
YourQueue.WillDequeue += (sender, e) => {
// kick off some process
};
YourQueue.DidDequeue += (sender, e) => {
// kick off some process
// e.Item provides access to the dequeued item, if you like
};
One neat trick is that you can use the DidDequeue method to kick off some process to ensure that the queue is full by making a web request or loading some data from a filesystem, etc. I use this class in Xamarin mobile apps to ensure that data and images are pre-cached in order to provide a smooth user experience, instead of loading images AFTER they scroll onto the screen (like you might see in Facebook and countless other apps).
try
public new void Enqueue(Delegate d)
{
base.Enqueue(d);
OnChanged(EventArgs.Empty);
}
You have to override Enqueue, to call OnChanged.
I am currently having a hardtime understanding and implementing events in C# using delagates. I am used to the Java way of doing things:
Define an interface for a listener type which would contain a number of method definitions
Define adapter class for that interface to make things easier if I'm not interested in all the events defined in a listener
Define Add, Remove and Get[] methods in the class which raises the events
Define protected fire methods to do the dirty work of looping through the list of added listeners and calling the correct method
This I understand (and like!) - I know I could do this exactly the same in c#, but it seems that a new (better?) system is in place for c#. After reading countless tutorials explaining the use of delegates and events in c# I still am no closer to really understanding what is going on :S
In short, for the following methods how would I implement the event system in c#:
void computerStarted(Computer computer);
void computerStopped(Computer computer);
void computerReset(Computer computer);
void computerError(Computer computer, Exception error);
^ The above methods are taken from a Java application I once made which I'm trying to port over to c#.
Many many thanks!
You'd create four events, and methods to raise them, along with a new EventArgs-based class to indicate the error:
public class ExceptionEventArgs : EventArgs
{
private readonly Exception error;
public ExceptionEventArgs(Exception error)
{
this.error = error;
}
public Error
{
get { return error; }
}
}
public class Computer
{
public event EventHandler Started = delegate{};
public event EventHandler Stopped = delegate{};
public event EventHandler Reset = delegate{};
public event EventHandler<ExceptionEventArgs> Error = delegate{};
protected void OnStarted()
{
Started(this, EventArgs.Empty);
}
protected void OnStopped()
{
Stopped(this, EventArgs.Empty);
}
protected void OnReset()
{
Reset(this, EventArgs.Empty);
}
protected void OnError(Exception e)
{
Error(this, new ExceptionEventArgs(e));
}
}
Classes would then subscribe to the event using either a method or a an anonymous function:
someComputer.Started += StartEventHandler; // A method
someComputer.Stopped += delegate(object o, EventArgs e)
{
Console.WriteLine("{0} has started", o);
};
someComputer.Reset += (o, e) => Console.WriteLine("{0} has been reset");
A few things to note about the above:
The OnXXX methods are protected so that derived classes can raise the events. This isn't always necessary - do it as you see fit.
The delegate{} piece on each event declaration is just a trick to avoid having to do a null check. It's subscribing a no-op event handler to each event
The event declarations are field-like events. What's actually being created is both a variable and an event. Inside the class you see the variable; outside the class you see the event.
See my events/delegates article for much more detail on events.
You'll have to define a single delegate for that
public delegate void ComputerEvent(object sender, ComputerEventArgs e);
ComputerEventArgs would be defined like this:
public class ComputerEventArgs : EventArgs
{
// TODO wrap in properties
public Computer computer;
public Exception error;
public ComputerEventArgs(Computer aComputer, Exception anError)
{
computer = aComputer;
error = anError;
}
public ComputerEventArgs(Computer aComputer) : this(aComputer, null)
{
}
}
The class that fires the events would have these:
public YourClass
{
...
public event ComputerEvent ComputerStarted;
public event ComputerEvent ComputerStopped;
public event ComputerEvent ComputerReset;
public event ComputerEvent ComputerError;
...
}
This is how you assign handlers to the events:
YourClass obj = new YourClass();
obj.ComputerStarted += new ComputerEvent(your_computer_started_handler);
Your handler is:
private void ComputerStartedEventHandler(object sender, ComputerEventArgs e)
{
// do your thing.
}
The main difference is that in C# the events are not interface-based. Instead, the event publisher declares the delegate which you can think of as a function pointer (although not exactly the same :-)). The subscriber then implements the event prototype as a regular method and adds a new instance of the delegate to the event handler chain of the publisher. Read more about delegates and events.
You can also read short comparison of C# vs. Java events here.
First of all, there is a standard method signature in .Net that is typically used for events. The languages allow any sort of method signature at all to be used for events, and there are some experts who believe the convention is flawed (I mostly agree), but it is what it is and I will follow it for this example.
Create a class that will contain the event’s parameters (derived from EventArgs).
public class ComputerEventArgs : EventArgs
{
Computer computer;
// constructor, properties, etc.
}
Create a public event on the class that is to fire the event.
class ComputerEventGenerator // I picked a terrible name BTW.
{
public event EventHandler<ComputerEventArgs> ComputerStarted;
public event EventHandler<ComputerEventArgs> ComputerStopped;
public event EventHandler<ComputerEventArgs> ComputerReset;
...
}
Call the events.
class ComputerEventGenerator
{
...
private void OnComputerStarted(Computer computer)
{
EventHandler<ComputerEventArgs> temp = ComputerStarted;
if (temp != null) temp(this, new ComputerEventArgs(computer)); // replace "this" with null if the event is static
}
}
Attach a handler for the event.
void OnLoad()
{
ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
computerEventGenerator.ComputerStarted += new EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
}
Create the handler you just attached (mostly by pressing the Tab key in VS).
private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
{
if (args.Computer.Name == "HAL9000")
ShutItDownNow(args.Computer);
}
Don't forget to detach the handler when you're done. (Forgetting to do this is the biggest source of memory leaks in C#!)
void OnClose()
{
ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
}
And that's it!
EDIT: I honestly can't figure out why my numbered points all appear as "1." I hate computers.
there are several ways to do what you want. The most direct way would be to define delegates for each event in the hosting class, e.g.
public delegate void ComputerStartedDelegate(Computer computer);
protected event ComputerStartedDelegate ComputerStarted;
public void OnComputerStarted(Computer computer)
{
if (ComputerStarted != null)
{
ComputerStarted.Invoke(computer);
}
}
protected void someMethod()
{
//...
computer.Started = true; //or whatever
OnComputerStarted(computer);
//...
}
any object may 'listen' for this event simply by:
Computer comp = new Computer();
comp.ComputerStarted += new ComputerStartedDelegate(
this.ComputerStartedHandler);
protected void ComputerStartedHandler(Computer computer)
{
//do something
}
The 'recommended standard way' of doing this would be to define a subclass of EventArgs to hold the Computer (and old/new state and exception) value(s), reducing 4 delegates to one. In this case that would be a cleaner solution, esp. with an Enum for the computer states in case of later expansion. But the basic technique remains the same:
the delegate defines the signature/interface for the event handler/listener
the event data member is a list of 'listeners'
listeners are removed using the -= syntax instead of +=
In c# events are delegates. They behave in a similar way to a function pointer in C/C++ but are actual classes derived from System.Delegate.
In this case, create a custom EventArgs class to pass the Computer object.
public class ComputerEventArgs : EventArgs
{
private Computer _computer;
public ComputerEventArgs(Computer computer) {
_computer = computer;
}
public Computer Computer { get { return _computer; } }
}
Then expose the events from the producer:
public class ComputerEventProducer
{
public event EventHandler<ComputerEventArgs> Started;
public event EventHandler<ComputerEventArgs> Stopped;
public event EventHandler<ComputerEventArgs> Reset;
public event EventHandler<ComputerEventArgs> Error;
/*
// Invokes the Started event */
private void OnStarted(Computer computer) {
if( Started != null ) {
Started(this, new ComputerEventArgs(computer));
}
}
// Add OnStopped, OnReset and OnError
}
The consumer of the events then binds a handler function to each event on the consumer.
public class ComputerEventConsumer
{
public void ComputerEventConsumer(ComputerEventProducer producer) {
producer.Started += new EventHandler<ComputerEventArgs>(ComputerStarted);
// Add other event handlers
}
private void ComputerStarted(object sender, ComputerEventArgs e) {
}
}
When the ComputerEventProducer calls OnStarted the Started event is invoked which in turn will call the ComputerEventConsumer.ComputerStarted method.
The delegate declares a function signature, and when it's used as an event on a class it also acts as a collection of enlisted call targets. The += and -= syntax on an event is used to adding a target to the list.
Given the following delegates used as events:
// arguments for events
public class ComputerEventArgs : EventArgs
{
public Computer Computer { get; set; }
}
public class ComputerErrorEventArgs : ComputerEventArgs
{
public Exception Error { get; set; }
}
// delegates for events
public delegate void ComputerEventHandler(object sender, ComputerEventArgs e);
public delegate void ComputerErrorEventHandler(object sender, ComputerErrorEventArgs e);
// component that raises events
public class Thing
{
public event ComputerEventHandler Started;
public event ComputerEventHandler Stopped;
public event ComputerEventHandler Reset;
public event ComputerErrorEventHandler Error;
}
You would subscribe to those events with the following:
class Program
{
static void Main(string[] args)
{
var thing = new Thing();
thing.Started += thing_Started;
}
static void thing_Started(object sender, ComputerEventArgs e)
{
throw new NotImplementedException();
}
}
Although the arguments could be anything, the object sender and EventArgs e is a convention that's used very consistently. The += thing_started will first create an instance of the delegate pointing to target method, then add it to the event.
On the component itself you would typically add methods to fire the events:
public class Thing
{
public event ComputerEventHandler Started;
public void OnStarted(Computer computer)
{
if (Started != null)
Started(this, new ComputerEventArgs {Computer = computer});
}
}
You must test for null in case no delegates have been added to the event. When you make the method call however all delegates which have been added will be called. This is why for events the return type is void - there is no single return value - so to feed back information you would have properties on the EventArgs which the event handlers would alter.
Another refinement would be to use the generic EventHandler delegate rather than declaring a concrete delegate for each type of args.
public class Thing
{
public event EventHandler<ComputerEventArgs> Started;
public event EventHandler<ComputerEventArgs> Stopped;
public event EventHandler<ComputerEventArgs> Reset;
public event EventHandler<ComputerErrorEventArgs> Error;
}
Thank you all so much for your answers! Finally I'm starting to understand what is going on. Just one thing; It seems that if each event had a different number/type of arguments I'd need to create a different :: EventArgs class to deal with it:
public void computerStarted(Computer computer);
public void computerStopped(Computer computer);
public void computerReset(Computer computer);
public void breakPointHit(Computer computer, int breakpoint);
public void computerError(Computer computer, Exception exception);
This would require three classses to deal with the events!? (Well two custom, and one using the default EventArgs.Empty class)
Cheers!
Ok, FINAL clarification!: So this is pretty much the best I can do code-wise to implement those events?
public class Computer {
public event EventHandler Started;
public event EventHandler Stopped;
public event EventHandler Reset;
public event EventHandler<BreakPointEvent> BreakPointHit;
public event EventHandler<ExceptionEvent> Error;
public Computer() {
Started = delegate { };
Stopped = delegate { };
Reset = delegate { };
BreakPointHit = delegate { };
Error = delegate { };
}
protected void OnStarted() {
Started(this, EventArgs.Empty);
}
protected void OnStopped() {
Stopped(this, EventArgs.Empty);
}
protected void OnReset() {
Reset(this, EventArgs.Empty);
}
protected void OnBreakPointHit(int breakPoint) {
BreakPointHit(this, new BreakPointEvent(breakPoint));
}
protected void OnError(System.Exception exception) {
Error(this, new ExceptionEvent(exception));
}
}
}