Ainsi, après quelques recherches sur la base des réponses ci-dessus, plus Google recherche et demander à un collègue qui connaît un peu C# ma solution choisie au problème est ci-dessous. Je reste intéressé par les commentaires, les suggestions et les améliorations. D'abord quelques détails supplémentaires sur le problème, qui est en fait assez générique dans le sens où l'interface graphique contrôle quelque chose, qui doit rester totalement abstrait, à travers une série d'événements auxquels l'interface graphique doit réagir. Il y a quelques problèmes distincts:
- Les événements eux-mêmes, avec différents types de données. Les événements seront ajoutés, supprimés, modifiés au fur et à mesure de l'évolution du programme.
- Comment faire le pont entre plusieurs classes qui composent l'interface graphique (différents UserControls) et les classes qui résument le matériel.
- Toutes les classes peuvent produire et consommer des événements et doivent rester découplées autant que possible.
- Le compilateur doit repérer le codage cockups dans la mesure du possible (par exemple. Un événement qui envoie un type de données, mais un comsumer qui attend une autre)
La première partie de c'est les événements. Comme l'interface utilisateur graphique et le périphérique peuvent générer plusieurs événements, avec éventuellement des types de données différents associés, un répartiteur d'événements est pratique.Cela doit être générique dans les deux événements et les données, donc:
// 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);
}
}
Maintenant, nous avons besoin de certains événements et qui viennent du monde C, j'aime énumérations, donc je définir certains événements que l'interface graphique soulèvera:
public enum DEVICE_ACTION_REQUEST
{
LoadStuffFromXMLFile,
StoreStuffToDevice,
VerifyStuffOnDevice,
etc
}
maintenant partout dans le champ (espace de noms, généralement) de la classe statique du EventDispatcher il est possible de définir un nouveau répartiteur:
public void Initialize()
{
foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST)))
EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action);
}
Cela crée un gestionnaire d'événements pour chaque événement dans la salle um.
et consommé en vous inscrivant à l'événement comme ce code dans le constructeur de l'objet consommation de l'appareil:
public DeviceController()
{
EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) =>
{
InControlThread.Invoke(this,() =>
{
ReadConfigXML(s, (EventArgs<string>)e);
});
});
}
Lorsque le InControlThread.Invoke est une classe abstraite qui enveloppe tout simplement l'appel Invoke.
événements peuvent être déclenchés par l'interface utilisateur graphique simple:
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));
}
Ceci a l'avantage que si l'événement de collecte et de consommer les types correspondent pas (ici la chaîne Nom du fichier) le compilateur grogner.
Il existe de nombreuses améliorations qui peuvent être apportées, mais c'est le cœur du problème. Cela m'intéresserait, comme je l'ai dit dans les commentaires, surtout s'il y a des omissions ou des bogues flagrants ou des lacunes. J'espère que cela aide quelqu'un.
C'est probablement la méthode la plus simple, et ça marche bien au début et je l'ai déjà utilisé, mais ça a l'inconvénient qu'une fois l'interface graphique agrandie et constituée de plus de classes ou UserControls (ce qui semble inévitable) ça ne va pas bien. Peut-être aurais-je dû préciser dans ma question que la raison pour laquelle je cherche une solution générique est que je m'attends à ce que cette application grandisse avec le temps et qu'elle aimerait quelque chose qui évolue. – 0xDEADBEEF