I am trying to write a method that goes through a list of items - for each one adds it to a form, then waits for the user to input some data and click a button. However I have no idea of what I should/could be using to create this.
foreach (string s in List)
{
txtName.Text = s;
//wait for button click...
// When the user is ready, they click the button to continue the loop.
}
So far all I have found is the EventWaitHandle class, which seems to only apply for threads.
How can I achieve this?
There are ways of doing this using signalling if you're also using async/await - you could await a task which is completed by the button being clicked for example - but in general you should think about user interfaces in a more event-driven way.
Instead of having your foreach loop here, keep an index into the collection for which item is being displayed, and advance it each time the button is clicked. (Remember to check whether or not there are more items to display, of course.)
While I agree in general with Jon Skeet, there was an interesting presentation by Mads Torgensen demonstrating how async/await can be used to simplify such scenarios (using the techniques mentioned by Jon). After all, isn't that the same as with enumerators - we can write own enumerator class using state like index etc., but we almost never do that and use iterator blocks instead.
Anyway, here is the async/await technique we were talking about.
First, the reusable part:
public static class Utils
{
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
button.Click -= onClick;
tcs.TrySetResult(null);
};
button.Click += onClick;
return tcs.Task;
}
}
and your code using it (note that you need to mark your method as async)
foreach (string s in List)
{
txtName.Text = s;
await yourButton.WhenClicked();
}
Sample test putting it all together:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Samples
{
static class Test
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var txtName = new TextBox { Parent = form, Top = 8, Left = 8 };
var buttonNext = new Button { Parent = form, Top = txtName.Bottom + 8, Left = 8, Text = "Next" };
form.Load += async (sender, e) =>
{
var List = new List<string> { "A", "B", "C", "D " };
foreach (string s in List)
{
txtName.Text = s;
await buttonNext.WhenClicked();
}
txtName.Text = "";
buttonNext.Enabled = false;
};
Application.Run(form);
}
}
public static class Utils
{
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
button.Click -= onClick;
tcs.TrySetResult(null);
};
button.Click += onClick;
return tcs.Task;
}
}
}
I am with Jon Skeet on this one, trying to force a UI framework into a way of working that is not normal for it will often lead to problems. Even if it works, you will have code that is hard for anyone else to understand, as it will work in an unusual way.
Some web frameworks in Lisp worked in such a way as to make page web page displays look like method calls. I have not seen anything in .NET that does so.
My first thought is for you to call Application.DoEvents() within a loop checking for a flag that your button call-back sets. However your application would then use 100% of the CPU waiting while it is doing nothing. See http://blog.codinghorror.com/is-doevents-evil/
Your application is a finite state machine that responds to events from the user and moves into a new state whenever a required event arrives to match a state transition from the current state. There have been many attempts over the years to model this in code, none I have seen have come out with anything that is better than just using standard event processing.
Related
Sorry for the title, i didn't find it easy to resume.
My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.
So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.
In the dll i've come up with:
public class Reader
{
public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
public void Scan(String ReaderName)
{
AlertEventArgs alertEventArgs = new AlertEventArgs();
alertEventArgs.uuiData = null;
//Code with blocking scan function here
if (ScanFinnished)
{
alertEventArgs.uuiData = "Scan Finnished!";
}
alertEventArgs.cardStateData = readerState[0].eventState;
ReaderEvent(new object(), alertEventArgs);
}
public event ReaderEventHandler ReaderEvent;
}
public class AlertEventArgs : EventArgs
{
#region AlertEventArgs Properties
private string _uui = null;
private uint cardState = 0;
#endregion
#region Get/Set Properties
public string uuiData
{
get { return _uui; }
set { _uui = value; }
}
public uint cardStateData
{
get { return cardState; }
set { cardState = value; }
}
#endregion
}
While in the main app I do:
Reader reader;
Task polling;
String SelectedReader = "Some_Reader";
private void bButton_Click(object sender, EventArgs e)
{
reader = new Reader();
reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}
void reader_EventChanged(object sender, AlertEventArgs e)
{
MessageBox.Show(e.uuiData + " Estado: " + e.cardStateData.ToString("X"));
reader.Dispose();
}
So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.
Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.
What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?
When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.
You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.
For example:
private void RaiseReaderEvent(AlertEventArgs args)
{
var myEvent = ReaderEvent; // This prevents race conditions
if (myEvent != null) // remember to check that someone actually subscribes your event
myEvent(this, args); // Sender should be *this*, not some "new object()".
}
Though I personally like a bit more generic approach:
private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
var eventInstance = oEvent;
if (eventInstance != null)
eventInstance(this, args);
}
Which can then be used to raise all events in same class like this:
Raise(ReaderEvent, alertEventArgs);
Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.
In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.
So your UI-thread safe event handler should be something like this:
void reader_EventChanged(object sender, AlertEventArgs e)
{
if (InvokeRequired) // This true for others than UI Thread.
{
Invoke((MethodInvoker)delegate
{
Text = "My new title!";
});
}
else
Text = "My new title!";
}
In WPF there's Dispather that handles similar invoking.
I was testing a weird bug I encountered in my app, and finally was able to create a simple reproduction:
using System;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var notifyIcon1 = new NotifyIcon();
notifyIcon1.Icon = new Form().Icon;
notifyIcon1.Visible = true;
var contextMenuStrip1 = new ContextMenuStrip();
ToolStripMenuItem menu1 = new ToolStripMenuItem();
menu1.Text = "test";
contextMenuStrip1.Items.Add(menu1);
contextMenuStrip1.Items.Add("t1");
contextMenuStrip1.Items.Add("t2");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
var timer = new System.Timers.Timer();
timer.Interval = 3000;
timer.Elapsed += (sender, e) => /* Runs in a different thread from UI thread.*/
{
if (contextMenuStrip1.InvokeRequired)
contextMenuStrip1.Invoke(new Action(() =>
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
}));
else
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
}
};
timer.Start();
Application.Run();
}
}
Note that the context menu will not open, but doing any of the following allows it to open:
Removing "extra" dropdown items added per execution. (To be precise
adding only 0 or 1 per execution works)
Removing part of code on InvokeRequired == false (This allows to add multiple items per execution)
Removing t1 and t2 elements. (It still works without
additional items in root)
Is this a bug or am I doing something wrong?
EDIT:
additional found condition (thanks to #derape):
It works if you move else branch to separate method, but not if you use same method in InvokeRequired branch. However using 2 method with different name and same code works.
Possible workaround could be wearing tigers skin while dancing during full-moon.
If you look at InvokeRequired then you will see there is an explicit check for IsHandleCreated which returns false. That returned value doesn't means you don't have to invoke, it simply means you can not invoke.
To confuse you even more: you must invoke, but you can't yet.
You can either decide to don't do anything if handle is not created yet (and simply miss items) or organize separate queue to store items until handle is available, similar to:
var items = new List<string>();
timer.Elapsed += (sender, e) =>
{
if (contextMenuStrip1.IsHandleCreated) // always invoke, but check for handle
contextMenuStrip1.Invoke(new Action(() =>
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
contextMenuStrip1.Refresh();
}));
else
{
lock (items)
{
items.Add(e.SignalTime.ToString() + "extra");
items.Add(e.SignalTime.ToString());
}
}
};
contextMenuStrip1.HandleCreated += (s, e) =>
{
lock (items)
{
foreach (var item in items)
menu1.DropDownItems.Add(item);
contextMenuStrip1.Refresh();
}
items = null;
};
Another note: you will need to call Refresh if items were added to sub-menu, while menu is opened, but submenu is not yet, odd thing of winforms.
Basically, this is what happens. I have a thread(endless loop) that runs as a background process while the form is showing. The thread checks if there is a need to add a new ToolStripMenuItem.
If the conditions are met, I'll need to use Invoke in order to create the UI object right? Problem with this is, when the this.Invoke or BeginInvoke is called, the form became unresponsive while the thread that does the checking is still running fine. Any ideas?
This is the first time i'm trying with this multithreading thingee. I'm sure i've missed out something.
public void ThreadSetCom()
{
while (true)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = p;
tsmi.Name = p;
tsmi.Click += new EventHandler(itm_Click);
if (this.msMenu.InvokeRequired)
{
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
}
else
{
cpDropdownList.DropDownItems.Add(tsmi);
}
}
}
}
Your ThreadSetCom method never exits:
while (true)
... with no return or break statements. That's going to hang the UI thread forever.
It's not clear what you're trying to achieve, but you definitely don't want to be looping like that in the UI thread. I'd argue that you don't want to be looping like that in a tight way in any thread, mind you...
I think a better approach for you would probably be to use a BackgroundWorker. I say that because what you're experiencing isn't that uncommon when doing multi-threading in a Windows Forms application. Further, the BackgroundWorker is able to manage the thread switching properly. Let me give you an example of that code with the BackgroundWorker.
Build a private class variable
private BackgroundWorker _worker;
Add to the CTOR
public {ctor}()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.WorkerReportsProgress = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundThreadWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundThreadProgress);
}
DoWork handler
private void BackgroundThreadWork(object sender, DoWorkEventArgs e)
{
while (!_worker.CancellationPending)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
_worker.ReportProgress(1, p);
}
}
}
Report progress handler
private void BackgroundThreadProgress(object sender, ReportProgressEventArgs e)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = e.UserState as string;
tsmi.Name = e.UserState as string;
tsmi.Click += new EventHandler(itm_Click);
cpDropdownList.DropDownItems.Add(tsmi);
}
The Loop
However, one thing you're going to have to do is figure out how to get out of this loop. When should it exit? Whatever that means, you need to add to the if statement that exists there in my example because this loop will never end otherwise.
What the effect of this code snippet:
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
is that the method 'ThreadSetCom' will be invoked in the UI thread. And there is an infinitive loop in that method. That is why your form becomes unresponsive.
I suggest you that you should move the foreach clause to a separate method and invoke this method in the UI thread when the condition is hit, for example the diff.Count>0.
I'm using WatiN to parse my web site. I have a button that starts the process. I open a browser window and navigate where I need to go, then I create a new task that calls a method called DoWork.
My problem is that if I call a new method at the end of DoWork to do something else I get strange results when I try to have the program navigate my website, however, if I don't call this new method from DoWork and just hook the new method up to a button click all works fine. So my question is am I not properly calling my new method from the background process method, Dowork?
Code:
IE browser = new IE("http://www.mywebsite.com/");
string startYear;
string endYear;
int NumRows;
Task myThread;
public Form1()
{
InitializeComponent();
}
private void Start_Click(object sender, EventArgs e)
{
startYear = txtStartYear.Text;
endYear = txtEndYear.Text;
//website navigation work removed for brevity
browser.Button(Find.ById("ContentPlaceHolder1_btnApplyFilter")).Click();
int numRows = browser.Div(Find.ById("scroller1")).Table(Find.First()).TableRows.Count -1;
NumRows = numRows;
lblTotalRows.Text = numRows.ToString();
myThread = Task.Factory.StartNew(() => DoWork());
}
public void DoWork()
{
List<string> myList = new List<string>(NumRows);
txtStartYear.Text = startYear;
txtEndYear.Text = endYear;
for (int i = 1; i < NumRows; i++)
{
TableRow newTable = browser.Div(Find.ById("scroller1")).Table(Find.First()).TableRows[i];
string coll = string.Format("{0},{1},{2},{3},{4}", newTable.TableCells[0].Text, newTable.TableCells[1].Text, newTable.TableCells[2].Text, newTable.TableCells[3].Text, newTable.TableCells[4].Text);
myList.Add(coll);
label1.Invoke((MethodInvoker)delegate
{
label1.Text = i.ToString();
});
}
//database work removed for brevity.
browser.Button(Find.ById("btnFilter")).Click();
newMethod();
}
public void newMethod()
{
int start = int.Parse(startYear);
start++;
startYear = start.ToString();
int end = int.Parse(endYear);
end++;
endYear = end.ToString();
browser.SelectList(Find.ById("selStartYear")).SelectByValue(startYear);
browser.SelectList(Find.ById("selEndYear")).SelectByValue(endYear);
//removed for brevity
}
}
To reiterate, if I call newMethod from Dowork the line browser.SelectList(Find.ById("selStartYear")).SelectByValue(startYear) doesn't behave properly, but if I remove the call to newMethod from Dowork and just hook newMethod up to a button it works fine. I'm wondering if it has to do with DoWork being a background task?
When I say it doesn't behave properly I mean that when you select an item from the drop down list the page auto posts back, however the above line of code selects it but the page doesn't post back, which shouldn't be possible. If I don't call the method within DoWork I don't have this issue.
You're modifying a UI element from a non-UI thread. You've already got code which deals with that within DoWork, via Control.Invoke - you need to do the same kind of thing for newMethod. It would probably be easiest just to invoke the whole method in the UI thread:
// At the end of DoWork
Action action = newMethod;
label.BeginInvoke(action);
(I'm using label.BeginInvoke as I'm not sure whether the browser itself is a "normal" control - but using label will get to the right thread anyway. If browser.BeginInvoke compiles, that would be clearer.)
I suspect it's a problem with the select list control. When I browse websites, I sometimes select drop down items by keyboard. Sometimes, it just doesn't postback, while using mouse always guarantee a postback.
I think you might be better off putting an extra button and do a browser.Button(Find.ById("btnFilter")).Click(); kind of thing to invoke a postback.
If the functions in the browser doesn't perform the proper cross thread checking, what Jon Skeet said should help.
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.