1

yop,

Pour un petit projet perso il va me falloir une GUI, et comme mes besoins sont assez basiques j'aimerais en profiter pour la faire moi-même. Grosso modo il me faudra juste les composants de base : Form, Label, Textbox, Button et 2 ou 3 autres du même genre.

C'est au niveau de la gestion des évènements que je me demande quelle méthode choisir. J'aimerais bien un système comparable à ce qu'on trouve en C# ou en Delphi, avec des "event handlers" auxquels on puisse attacher des méthodes de traitement. Le truc c'est que le modèle n'est pas super clair pour moi, et je ne sais pas dans quelle direction partir pour éviter de me retrouver bloquer au cas où je décide de faire évoluer cette future petite bibliothèque.

Quand je regarde les évènements qui vont être à traiter, il y en a certains qui sont très génériques et vont exister pour chaque composant (OnMouseClic ou OnKeyDown par exemple) et d'autres qui au contraire vont être très lié au fonctionnement du composant auquel ils appartiennent (OnValueChanged pour une Textbox par exemple). Dans le second cas, pas de problème, l'évènement sera déclenché via une fonction spécifique au composant. Mais dans le premier cas c'est un peu moins clair pour moi : les évènements genre "OnKeyDown" ne sont à traiter que pour le composant qui a actuellement le focus, il va donc falloir trouver un moyen pour que quand une touche du clavier est enfoncée, l'évènement "OnKeyDown" du composant actuellement "focusé" soit déclenché. Par contre, un évènement comme "OnMouseClic" va fonctionner sur n'importe quel composant pour peu que la souris soit positionnée dessus, ce qui implique une petite recherche avant de savoir quoi déclencher.

Je pourrais partir sur une approche naïve qui traite un à un ces quelques cas, mais au-delà du simple fait d'être assez moche, il y a un premier problème : tous ces évènements doivent alors être connus de ma classe mère ("Composant") de laquelle héritent tous les composants. Je vais donc me retrouver avec une classe abstraite "Composant" qui va contenir tous ces évènements que je n'ai pas su mettre ailleurs. Ça me gène dans le sens où un composant peut avoir une notion de gestion d'évènement, mais tant que ça reste générique. Lui coller directement la gestion finalement très spécifique de "OnKeyDown" et autres me semble être assez mauvais d'un point de vue conception, mais je ne vois pour l'instant pas d'autre solution.

Auriez-vous des idées ?
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

2

Pourquoi la classe mère aurait-elle besoin de forcément connaître tous les évènements ? Elle pourrait ne connaître que la classe de base, qui serait ensuite surchargée selon les besoins par les composants. Après, il y a peut-être une distinction quand même à faire dès le début entre les évènements clavier et souris par exemple (ou d’autres classes d’évènements encore), qui ne se propagent peut-être pas de la même manière…
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

3

(Bon je voulais faire un petit dessin mais c’est chiant en fait).

J’imagine un truc comme ça : à chaque évènement destiné à l’application, le composant parent (la fenêtre quoi) le reçoit et le dispatche selon sa catégorie : s’il s’agit d’un évènement clavier, il est transmis au composant qui détient le focus, s’il s’agit d’un évènement souris, il est transmis au composant situé sous le curseur, etc. Ainsi, le composant parent n’aura pas besoin de connaître en détail l’évènement, mais juste à quelle catégorie à laquelle il appartient pour pouvoir le propager correctement.
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

4

Ça recoupe un peu ce que je disais non ? Si je comprends ta proposition, il y aurait quelques évènements "maitres" (clavier/souris, ce que j'ai évoqué dans mon post comme "OnKeyDown" et "OnMouseClic") qui sont connus par la classe mère et tous les autres évènements plus spécifiques qui ne sont connus que par les classes filles ?

Pour tenter de masquer le fait que je trouve cette distinction pas très naturelle, j'avais envisagé de séparer la partie "propagation" (qui n'existe, à ma connaissance, que pour les évènements clavier/souris justement) de la partie "traitement de l'évènement". Ainsi la classe mère n'aurait qu'une ou deux méthodes qui propagent un objet quelconque. Je ne sais pas pourquoi, mais je sens que je vais bloquer sur un détail au moment d'essayer une implémentation... j'essaie de faire un squelette dès que possible (demain au boulot si on me laisse du temps ^^), peut-être que ça m'aidera à être un peu plus précis dans mes questions.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

5

Alors déjà tu codes en quel langage?
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

6

A priori je vais partir sur du C++, mais pour l'instant c'est juste la partie algo qui m'intéresse, le langage n'a pas tellement d'importance au final.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

7

Pour la propagation des évènements tu aurait a priori deux choix: La propagation de proche en proche (le père qui transmet au fils qui transmet au fils etc...) et la transmission directe.
L'une de ces deux approche revient à faire de la récursivité, et l'autre revient à tout gérer au sein d'un seul objet.
Après, le clavier et la souris sont des éléments de base de l'interface, donc il est logique que tu doives les implémenter de manière interne dans le contrôle de base, même si les dérivés n'utiliseront peut-être que l'un des deux, voire aucun des deux. De la même manière tu pourrais également devoir ajouter des méthodes pour gérer les périphériques multitouch, les palettes graphiques, etc... ^^
Enfin bon la base est que tu n'as effectivement besoin que de 3 évènements (Down, Move, Up) pour la souris, et deux pour le clavier (Up, Down) qui doivent être gérés en interne par ton code. Tu peux vouloir ajouter Enter/Leave pour la souris assez simplement également, mais ce n'est pas nécéssaire à toutes les interfaces.

Pour la transmission de proche en proche, tu définis toute la gestion des messages vitaux au sein de ta classe de base (dans des méthodes internes non accessibles par les héritiers ou extérieurs bien sûr wink ) et tu exportes le résultat final dans des méthodes-évènement publiques. Ensuite il te reste à définir un élément racine (le bureau ?) dans lequel tous les contrôles de premier plan seront placés (donc les form quoi).
Pour la transmission "directe", il te faudra un type de contrôle maître qui possèdera - et il sera le seul - l'implémentation de la transmission des messages. Tous les autres contrôles ne seront que des receveurs bêtes. Pour que ça fonctionne il te faut garantir que les contrôles - et c'est très important - exportent des informations parfaitement fiables: liste exacte des contrôles fils, position et dimensions correctes (enfin ça doit concorder avec ce qui est dessiné à l'écran quoi, c'est super important ! wink ), pour le reste c'est juste une bête boucle while (en lieu et place d'appels de fonctions imbriqués) avec une boucle for à l'intérieur tongue

Et, comme tu devrais l'avoir deviné, ty peux sans problème fourer un des deux types de propagation l'un dans l'autre, ils s'emboitent parfaitement... (par contre au sein d'un même niveau d'imbrication, ils ne se mélangent absolument pas !) Donc c'est essentiellement à toi de décider quel type tu vas utiliser à quel endroit. (Celà dit prends aussi en compte les possibles stack overflow avec l'une des deux méthodes tongue)

Aussi je suppose que tu as pensé à ce qui est nécéssaire: connaître l'élément qui a le focus, soit globalement, soit à chaque niveau (selon le mode de propagation choisi); définir une information de positionnement fiable; permettre les contrôles non focusables; etc...
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

8

Je pense que je vais rester sur la 1ere solution, même si la seconde est probablement plus efficace ; d'un point de vue logique je préfère que la gestion des évènements reste à la charge des composants qui les reçoivent, et non d'un composant tiers.

Sinon pour les attributs des composants c'était déjà en place pour les besoins qui précédaient la gestion des évènements, donc a priori pas grand chose à ajouter de ce coté-là. Merci pour les explications smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

9

[nosmile]Bon, finalement ça s'implémente beaucoup plus naturellement que ce que je craignais ^^

Par contre j'ai occulté avec un ignoble cast un problème de conception auquel je n'ai pas encore de solution. Voilà grossièrement une construction comparable qui fait apparaître le même problème :

// Classe mère "Control"
class Control
{
    protected:
        typedef void (Control::*CallbackMouseMove) (const Point&);

        virtual void onMouseMove (CallbackMouseMove);

    private:
        CallbackMouseMove callbackMouseMove; // pointeur sur méthode, déclenchée par des évènements internes
};

// Assignation d'une méthode perso à l'évènement "MouseMove"
void onMouseMove (CallbackMouseMove callback)
{
    this->callbackMouseMove = callback;
}

// Classe "Window" générique (inutile pour l'exemple)
class Window : public Control
{
    [...]
};

// Classe "Window" spécialisée pour un programme donné
class TestWindow : public Window
{
    public:
        TestWindow ();

    private:
        void test (const Point&);
};

// Affectation de la méthode "test" à l'évènement "MouseMove" dans le constructeur
TestWindow::TestWindow ()
{
    this->onMouseMove (&TestWindow::test);

    // error: no matching function for call to 'TestWindow::onMouseMove(void (TestWindow::*)(const Point&))'
    // note: candidates are: virtual void Control::onMouseMove(void (Control::*)(const Point&))
}

// Méthode de test bidon
void TestWindow::test (const Point& point)
{
    std::cout << "kikoo from " << point.x () << ", " << point.y () << std::endl;
}

Pour le moment je supprime l'erreur (indiquée en commentaire dans le constructeur de "TestWindow") en forçant le cast de &TestWindow::test en CallbackMouseMove, mais c'est incorrect (onMouseMove est censé fonctionner sur n'importe quel Control, et TestWindow n'en est qu'une spécialisation). Quelle solution adopter pour continuer à utiliser les méthodes de TestWindow en tant que callbacks ?

[edit] si j'en oublie la moitié ça aide pas pour répondre triso
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

10

Je ne sais pas trop, ça me perd un peu ces restrictions de portées...

Sinon, pourquoi tu passes par un mécanisme de callback plutôt qu'une simple surcharge ?
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

11

J'aimerais rester assez proche du fonctionnement habituel des frameworks que j'ai un peu utilisé, et puis c'est un système plus souple que de la surcharge : on peut par exemple remplacer ou désactiver les callbacks en cours d'exécution, ou encore associer simultanément plusieurs callbacks à un même évènement (enfin pas avec ma pseudo-implémentation, mais ça peut s'ajouter facilement).
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

12

Sauf si tu utilises des langages plus dynamiques comme le pyhton où on peut remplacer une fonction membre d'une classe par une autre à l'exécution.
Et sinon, même avec un langage typé statiquement tu peux te démerder avec des systèmes d'interface/adapter (comme en Java). Mais c'est peut-être plus simple/pratique d'utiliser des callbacks au fond, si tu veux pouvoir changer le callback à l'exécution...
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

13

Un pattern commun pour les évènements en C++, ce sont les signaux et les slots. Il est possible de faire ça entièrement avec des templates, avec certaines limitations (notamment au niveau de la syntaxe, qui se retrouve redondante par endroits), cf. Boost.Signals pour une implémentation. Pour une solution optimale, il faut un bidouillage comme dans Qt avec son compilateur de méta-objets (moc).

Pour en savoir plus:
http://doc.trolltech.com/4.5/signalsandslots.html
http://www.boost.org/doc/libs/1_38_0/doc/html/signals.html
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

14

hmm ça a l'air de correspond à peu près à ce que je recherche, mais je vois plutôt des informations sur comment les utiliser dans tes liens, pas tellement d'explications sur la façon dont ça peut s'implémenter ? (ou alors j'ai pas regardé où il fallait)
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

15

Oui ce sont des doc utilisateur.
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

16

Bon, pour l'instant je m'en suis sorti avec une méthode un peu violente. Elle fonctionne comme je le voulais, mais si quelqu'un a une solution moins redondante je suis preneur ^^

Du coté du code final, j'obtiens une syntaxe pas trop désagréable à utiliser, voici par exemple une fenêtre dérivée de ma classe "Window" et deux évènements pour la souris :

main.hpp :
#include "controls/window.hpp"

class	MyWindow : public Controls::Window
{
	public:
		/**/	MyWindow (const Config&);

		void	mouseClic (const Point&, int);
		void	mouseMove (const Point&);
};


main.cpp :
#include "main.hpp"

MyWindow::MyWindow (const Config& config) :
	Window
	(
        	config.getGroup ("control"),
		config.getGroup ("drawing"),
		config.getGroup ("font")
	)
{
	this->onMouseClic += new OnMouseClic::Event<MyWindow> (this, &MyWindow::mouseClic);
	this->onMouseMove += new OnMouseMove::Event<MyWindow> (this, &MyWindow::mouseMove);
}

void	MyWindow::mouseClic (const Point& point, int button)
{
	std::cout << "mouse clic at (" << point.getX () << ", " << point.getY () << ") with button [" << button << "]" << std::endl;
}

void	MyWindow::mouseMove (const Point& point)
{
	std::cout << "mouse move at (" << point.getX () << ", " << point.getY () << ")" << std::endl;
}

Quand on regarde le code de la classe Window, ça reste à peu près lisible, mais de toutes façons celle-ci n'est pas censée être modifiée (pour faire une fenêtre personnalisée, on la fait dériver de Window et on override les méthodes à modifier) :

controls/window.hpp :
#include "control.hpp"
#include "config.hpp"

class	Window : public Control
{
	public:
		typedef EventHandler<void, int>				OnKeyDown;
		typedef EventHandler<void, int>				OnKeyUp;
		typedef EventHandler<void, const Point<Unit>&, int>	OnMouseClic;
		typedef EventHandler<void, const Point<Unit>&>		OnMouseMove;

		Window (const Config::Group&, const Config::Group&, const Config::Group&);

		OnKeyDown	onKeyDown;
		OnKeyUp		onKeyUp;
		OnMouseClic	onMouseClic;
		OnMouseMove	onMouseMove;

	protected:
		virtual bool	eventMouseClic (const Point<Unit>&, int);
		virtual bool	eventMouseMove (const Point<Unit>&);

	[...]
}

Je sais, les attributs publics c'est pas bien, mais sinon ça aurait imposé une écriture genre "myWindow.onKeyDown () += ..." pour attacher des évènements et c'est très moche (les propriétés n'existent pas en C++, c'est dommage). Là où les choses deviennent tout à coup beaucoup moins élégantes, c'est quand on regarde le code du "EventHandler" qu'on voit apparaître dans la classe Window... La lourdeur et la redondance du code est principalement liée au fait qu'à ma connaissance les templates en C++ permettent des paramètres de type variable, mais pas un nombre variable de paramètres. Du coup pour pouvoir gérer des évènements avec 0 à 4 paramètres quelconques je suis obligé de me taper 4 spécialisations avec 4 fois quasiment le même code :

eventHandler.hpp
eventHandler.hxx

Au final, ça marche comme je le voulais... mais je reste déçu de ne pas avoir trouvé plus élégant, là où dans d'autres langages (C# par exemple) il y aurait moyen de faire beaucoup plus concis. Je suis preneur de toutes vos suggestions ^^

(j'aurais bien voulu utiliser des spécialisations pour les paramètres constants, mais ça va vraiment faire trop bourrin... peut-être qu'avec des traits pour tenter de simplifier l'écriture ça serait mieux que rien)
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

17

Zephyr (./16) :
Je sais, les attributs publics c'est pas bien, mais sinon ça aurait imposé une écriture genre "myWindow.onKeyDown () += ..." pour attacher des évènements et c'est très moche (les propriétés n'existent pas en C++, c'est dommage).

Le operator= de la classe OnKeyDown peut être surchargé. Si on bidouille bien, on peut arriver à quelque chose qui ressemble à une propriété. (En général, le membre ne serait pas de la classe OnKeyDown directement, mais de quelque chose comme Property<OnKeyDown> qui a un operator=(const OnKeyDown &) et un operator OnKeyDown().)
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

18

Pour les propriétés, tu peux placer une fonction GetProperty() et SetProperty() dans ton objet racine et auquel tu peux passer une clé associé à la propriété. Tu peux ensuite tenir dans un dictionnaire les fonctions d'acces et d'ecriture de chaque propriété. J'avoue que c'est un peu lourd, mais il me semble que GObject utilise un système similaire?

19

C'est surtout un peu lent, si il faut faire une recherche dans un dictionnaire à chaque fois que je veux accéder à une propriété (ce qui peut potentiellement arriver très souvent) :/
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

20

Euh... un dictionnaire est lent? Tu sais que tu peux accéder en O(1) ?
Tout ce qui passe pas par le port 80, c'est de la triche.

21

Non. Mais si tu veux en parler, je t'invite à créer un autre topic : je préfère écarter toutes les solutions qui nécessitent d'effectuer un calcul pour accéder à une propriété (autre chose qu'un ou deux déréférencements).
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

22

Dans ce cas là je comprends pas le ./16. Tu veux que les propriétés soient natifs dans le langage mais pourquoi ils seraient plus efficace qu'un dictionnaire que tu coderais à la main? A moins que la VM garde un pointeur vers les méthodes get/set correspondant, mais dans ce cas tu fais tourner un VM je suis pas sur que ça soit bien plus efficace qu'un dico...
Tout ce qui passe pas par le port 80, c'est de la triche.

23

gni ?

je veux juste un truc de ce style (C#-like) :
class	Window : public Control 
{ 
	public delegate void OnKeyDown (void, int); 
 
	public OnKeyDown onKeyDown
	{
		get
		{
			return this.onKeyDown_;
		}
	}

	private OnKeyDown onKeyDown_;
}

Ça reste un accès direct, pas de dictionnaire ou autre. Et puis l'objectif était de *simplifier* la syntaxe... si je me retrouve avec un truc genre window->getProperty ("onKeyDown") += myListener, c'est pas franchement ce que j'appelle simple (et c'est tout sauf du O(1) d'ailleurs... il me faudrait des enums pour avoir un truc linéaire, donc une liste prédéfinie, je vois encore moins l'intérêt).
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

24

En plus, si tu connais les clés à l'avance ton dico peut être toujours en O(1) (un enum pour les clés + un tableau classique).

Tout ce qui passe pas par le port 80, c'est de la triche.

25

Cross:
Bah la syntaxe c'est une chose, mais tu sembles dire qu'un dico te fait faire trop de calcul...
Sinon je vois pas pourquoi c'est linéaire avec les enums. Un tableau de pointeur de fonction et des énums comme noms de propriétés que tu mets en index, c'est en O(1) non?
Tout ce qui passe pas par le port 80, c'est de la triche.

26

Il reste sinon toujours la solution à la Java, qui consiste à coder à la main ses accesseurs pour chaque attributs privés de tes classes.

27

En fait j'ai l'impression que vous avez mal compris le problème : dans le ./16 je disais juste que c'était dommage qu'il y ait pas de propriétés en C++ parceque ça m'aurait permis de garder une écriture simple tout en respectant l'encapsulation (que j'ai perdue avec mes attributs publics). Mais c'était uniquement un soucis d'écriture : si c'est pour me taper des "getProperty" et des appels de 3km ça n'a absolument aucune utilité, ça deviendrait encore pire que de me faire des accesseurs tout cons.

Ensuite ./25, histoire de clore la question : on ne parle peut-être pas de la même chose. Pour moi un dico c'est un "std::map" en C++ ou un "Dictionnary" en C#, c'est à dire un truc en O(log(N)) et certainement pas en O(1). Une table de pointeurs ça serait en O(1), mais je vois vraiment pas à quoi ça pourrait me servir ici.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

28

Ok. Moi quand je lis "dico" je pense pas forcéement à std::map, ca peut aussi etre un dico fait maison du genre:

private:
    void (*p[])(void, int); // Je suis pas sur de la syntaxe mais un tableau de void (*p)(void, int) quoi
    typedef enum Property { OnKeyDown = 0 };
public:
    void (*p)(void,int)getProperty(Property key){    // La aussi je suis pas sur de la syntaxe mais disons une fonction qui renvoit un void (*p)(void, int)
        return p[key];
    }

Bon après, la syntaxe ne te va pas visiblement. Simplement je trouvais abbérant que tu dise qu'il fallait 50 heures de calcul et que c'était en O(n).
Tout ce qui passe pas par le port 80, c'est de la triche.

29

Sinon pour la syntaxe, en C++, je pense que tu peux surcharger [] pour le binder directement à getProperty. (je me souviens plus trop, y a des gurus de C++ par ici?)
du genre a = maClasse[OnKeyDown];
Tout ce qui passe pas par le port 80, c'est de la triche.

30

50 heures de calcul ? j'ai juste dit (./19) que c'était trop couteux pour être une solution acceptable dans mon cas, puisque je compte accéder très souvent aux propriétés, et que donc rééxécuter sans cesse le même calcul était inadapté.

pour le [] c'est pas con, mais je risque d'avoir besoin de cet opérateur pour des cas bien plus adaptées à cette syntaxe (accéder aux éléments d'une ListBox, par exemple), donc je préfère le laisser libre.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)