So, after some research based on the answers above, further Google searching and asking a colleague who knows a bit about C# my chosen solution to the problem is below. I remain interested in comments, suggestions and refinements.
First some further detail about the problem, which is actually pretty generic in the sense that the GUI is controlling something, that must remain wholly abstract, through a series of events to whose responses the GUI must react. There a a few distinct problems:
- The events themselves, with different data types. Events will get added, removed, changed as the program evolves.
- How to bridge several classes that comprise the GUI (different UserControls) and the classes that abstract the hardware.
- All classes can produce and consume events and must remain decoupled as far as possible.
- The compiler should spot coding cockups as far as possible (eg. an event that sends one data type but a comsumer that expects another)
The first part of this is the events. As the GUI and the device can raise several events, possibly having different data types associated with them, an event dispatcher is handy. This must be generic in both events and data, so:
// Define a type independent class to contain event data
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
m_value = value;
}
private T m_value;
public T Value
{
get { return m_value; }
}
}
// Create a type independent event handler to maintain a list of events.
public static class EventDispatcher<TEvent> where TEvent : new()
{
static Dictionary<TEvent, EventHandler> Events = new Dictionary<TEvent, EventHandler>();
// Add a new event to the list of events.
static public void CreateEvent(TEvent Event)
{
Events.Add(Event, new EventHandler((s, e) =>
{
// Insert possible default action here, done every time the event is fired.
}));
}
// Add a subscriber to the given event, the Handler will be called when the event is triggered.
static public void Subscribe(TEvent Event, EventHandler Handler)
{
Events[Event] += Handler;
}
// Trigger the event. Call all handlers of this event.
static public void Fire(TEvent Event, object sender, EventArgs Data)
{
if (Events[Event] != null)
Events[Event](sender, Data);
}
}
Now we need some events and coming from the C world, I like enums, so I define some events that the GUI will raise:
public enum DEVICE_ACTION_REQUEST
{
LoadStuffFromXMLFile,
StoreStuffToDevice,
VerifyStuffOnDevice,
etc
}
Now anywhere within the scope (namespace, typically) of the static class of the EventDispatcher it is possible to define a new dispatcher:
public void Initialize()
{
foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST)))
EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action);
}
This creates an event handler for each event in the enum.
And consumed by subscribing to the event like this code in the constructor of the consuming Device object:
public DeviceController( )
{
EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) =>
{
InControlThread.Invoke(this, () =>
{
ReadConfigXML(s, (EventArgs<string>)e);
});
});
}
Where the InControlThread.Invoke is an abstract class that simply wraps the invoke call.
Events can be raised by the GUI simply:
private void buttonLoad_Click(object sender, EventArgs e)
{
string Filename = @"c:\test.xml";
EventDispatcher<DEVICE_ACTION_REQUEST>.Fire(DEVICE_ACTION_REQUEST.LoadStuffFromXMLFile, sender, new EventArgs<string>(Filename));
}
This has the advantage that should the event raising and consuming types not match (here the string Filename) the compiler will grumble.
There are many enhancements that can be made but this is the nuts of the problem. I'd be interested, as I said in comments, especially if there are any glaring omissions/bugs or deficiencies. Hope this helps someone.