Итак, после некоторого исследования, основанного на ответах выше, дальнейший поиск в Google и опрос коллег, который немного знаком с C #, выбрал мое решение проблемы ниже. Я по-прежнему заинтересован в комментариях, предложениях и уточнениях.
Сначала некоторые дополнительные подробности о проблеме, которая на самом деле довольно общая в том смысле, что графический интерфейс пользователя контролирует что-то, что должно оставаться полностью абстрактным, через серию событий, на ответы которых графический интерфейс должен реагировать. Есть несколько явных проблем:
- Сами события, с разными типами данных. События будут добавляться, удаляться, изменяться по мере развития программы.
- Как соединить несколько классов, которые включают графический интерфейс (разные UserControls) и классы, которые абстрагируют аппаратное обеспечение.
- Все классы могут генерировать и потреблять события и должны оставаться настолько разъединенными, насколько это возможно.
- Компилятор должен выявлять кодовые ошибки как можно дальше (например, событие, которое отправляет один тип данных, но потребитель, ожидающий другой)
Первая часть этого - события. Поскольку GUI и устройство могут вызывать несколько событий, возможно, связанных с ними разных типов данных, диспетчер событий удобен. Это должно быть общим как для событий, так и для данных, поэтому:
// 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);
}
}
Теперь нам нужны некоторые события, происходящие из мира Си, мне нравятся enums, поэтому я определяю некоторые события, которые будет вызывать графический интерфейс:
public enum DEVICE_ACTION_REQUEST
{
LoadStuffFromXMLFile,
StoreStuffToDevice,
VerifyStuffOnDevice,
etc
}
Теперь в любом месте области действия (обычно пространства имен) статического класса EventDispatcher можно определить новый диспетчер:
public void Initialize()
{
foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST)))
EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action);
}
Это создает обработчик событий для каждого события в перечислении.
И потребляется подпиской на событие, как этот код в конструкторе объекта-устройства потребления:
public DeviceController( )
{
EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) =>
{
InControlThread.Invoke(this, () =>
{
ReadConfigXML(s, (EventArgs<string>)e);
});
});
}
Где InControlThread.Invoke - абстрактный класс, который просто переносит вызов invoke.
События могут быть вызваны с помощью графического интерфейса просто:
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));
}
Преимущество заключается в том, что в случае несоответствия типов, вызывающих и потребляющих события (здесь строка Filename), компилятор будет ворчать.
Есть много улучшений, которые можно сделать, но это суть проблемы. Мне было бы интересно, как я сказал в комментариях, особенно если есть какие-либо явные упущения / ошибки или недостатки. Надеюсь, это кому-нибудь поможет.