IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Qt Discussion :

Conception d'un éditeur de propriétés


Sujet :

Qt

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 145
    Points : 392
    Points
    392
    Par défaut Conception d'un éditeur de propriétés
    Bonjour !

    Je me retrouve avec un petit problème dans mon éditeur de propriétés, je ne parviens pas à déterminer comment le résoudre. Pire, je ne sais pas s'il s'agit au final d'un problème de conception, ou d'un problème de compréhension du C++ ! (et dans ce dernier cas, ça me ferait bien ****)

    Cet éditeur est censé permettre d'éditer les propriétés de disons, tous les objets dérivés d'une classe Geometry (non héritée de QObject).


    Ma classe, appelons-la PropertyEditor, contient un slot "setCurrentEditable(Geometry *)". Cette fonction affiche dans les propriétés de l'instance de Geometry dans des widgets et permet de les éditer.

    A ce slot est connecté le signal "objectSelected(Geometry *)", émis par une autre classe.

    Le problème est le suivant :

    lorsqu'un objet de type Cube, dérivé de Geometry, est sélectionné, la modification des propriétés n'est pas répercutée...car j'opère à coups de dynamic_cast...qui me retourne un nouveau pointeur et pas le pointeur vers l'instance de l'objet sélectionné.

    Je sèche vraiment, et c'est la première fois.

    Merci à tous.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Salut,
    Citation Envoyé par GilbertLatranche Voir le message

    lorsqu'un objet de type Cube, dérivé de Geometry, est sélectionné, la modification des propriétés n'est pas répercutée...car j'opère à coups de dynamic_cast...qui me retourne un nouveau pointeur et pas le pointeur vers l'instance de l'objet sélectionné.

    Je sèche vraiment, et c'est la première fois.

    Merci à tous.
    Rien qu'à lire ces dernières lignes, j'ai presque envie de dire que c'est un problème de conception

    De manière générale, l'utilisation d'un dynamic_cast est souvent le symptome d'un problème de conception, surtout si elle prend une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    if (un test quelconque)
        dynamic_cast<UnTypeDerive *>(ptr)->uneFonction();
    else if (un autre test)
        dynamic_cast<UnAutreTypeDerve *>(ptr)->uneAutreFonction();
    else if(...)
       /* bon ben, tu as compris hein :-D */
    Typiquement, la solution à ce genre de problème tient en deux mots : double dispatch.

    L'idée générale est de se dire que tout objet qui est connu "comme étant de type Geometry" car connu grâce à un pointeur de ce type sait parfaitement de quel type réel il est : un cube sait qu'il est un cube et non une sphere ou un triangle, et il en va de même pour chaque type dérivé de Geometry.

    On peut donc s'organiser pour demander à l'objet lui-même d'invoquer un comportement spécifique. La manière dont on s'y prend n'a finalement que peu d'importance, tout ce qui importe, c'est que la classe Geometry expose un comportement qui pourra être redéfini dans les classes dérivées.

    Une solution "classique" (mais qui n'est pas forcément la meilleure ) est d'utiliser le patron de conception connu sous le nom de visiteur (Visitor en anglais).

    Le principe en est relativement simple et se base sur deux hiérarchies de classes distinctes :
    • les objets visités d'une part, qui correspond à tous les types qui dérivent de Geometry et
    • les visiteurs d'une autre, qui correspond à "tout ce qui doit être en mesure de faire la différence entre les différents types d'objets dérivés de Geometry".
    Pour savoir de quoi on parle, je vais nommer la classe de base de la deuxième hiérarchie "Visitor". Ce sera plus simple pour la compréhension .

    On rajoute à la classe Geometry une fonction virtuelle (éventuellement pure) qui prend en argument un pointeur (ou mieux une référence, constante si possible ) vers un objet de type Visitor. Pour savoir de quoi on parle, nommons cette fonction accept .Cela pourrait donc prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Geometry{
        public:
            virtual void accept(Visitor  &) const = 0 ;
        /* tout le reste de ta classe Geometry qui ne change absolument pas */
    };
    Cette fonction sera redéfinie de manière très simple, identique dans tous les types dérivés de Geometry sous une forme qui ressemblerait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Cube : public Geometry{
        public:
            void accept(Visitor  & v) const{
                v.visit(*this);
            }
    };
    class Sphere : public Geometry{
        public:
            void accept(Visitor  & v) const{
                v.visit(*this);
            }
    };
    class Paralelogram : public Geometry{
        public:
            void accept(Visitor  & v) const{
                v.visit(*this);
            }
    };
    Comme tu peux le remarquer, cette fonction invoque systématiquement une fonction visit en lui transmettant ce qui est pointé par le pointeur this, qui correspond à l'objet (réel) au départ duquel la fonction accept a été invoquée.

    Notes que le nom de la fonction visit est choisi tout à fait arbitrairement et que chaque classe dérivée de Geometry pourrait sans aucun problème décider d'appeler une fonction portant un nom différent. L'implémentation de la fonction accepte pourrait d'ailleurs décider d'appeler une fonction libre quelconque

    Il ne reste plus qu'à nous intéresser au visiteur. L'idée est que le visiteur "de base" devra être en mesure de faire la distinction entre la classe Sphere et la classe Cube. La classe Visitor qui servira, comme je te l'ai dit de classe de base à "tout ce qui doit être en mesure de faire la différence entre les différents types d'objets dérivés de Geometry" devra donc exposer une fonction "visit" prenant comme paramètre une référence (éventuellement constante) vers chacune des classes dérivées de Geometry.

    Elle ressemblera donc à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Visitor{
        public:
            virtual void visit(Cube const &) = 0;
            virtual void visit(Sphere const &) = 0;
            virtual void visit(Paralelogram const &) = 0;
            /* et tous les autres types dérivés de Geometry  */
    };
    Une fois que tu es rendu à ce point, il ne te reste plus qu'à définir les comportements spécifiques aux différents visiteurs dont tu as besoin.

    Ainsi, ta classe PropertyEditor pourrait ressembler à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class PropertyEditor : public QWidget, // tu as peut etre choisi une classe de base moins générique ;)
                    public Visitor{
        public:
            virtual void visit(Cube const & c){
                /* ce qui doit etre fait, spécifique à un cube */
            }
            virtual void visit(Sphere const & s){
                /* ce qui doit etre fait, spécifique à une sphère */
            }
            virtual void visit(Paralelogram const & p){
                /* ce qui doit etre fait, spécifique à un parallélogramme */
            }
            /* et tous les autres types dérivés de Geometry  */
        public slots :
            /* n'oublions pas le slot auquel tu connecte le signal */
            setCurrentEditable(Geometry * g){
                /* c'est simple : on appelle la fonction accept de geometry */
                g->accept(*this);
            }
    };
    Et voilà, le tour est joué! Tu obtiens quelque chose qui ne pourra pas se tromper de type et qui pourra évoluer très facilement.

    Il y a cependant une chose un peu contraignante à prendre en compte : Si tu décides de rajouter un nouveau type dérivé de Geometry, tu devras, forcément, déclarer une nouvelle fonction "visit" prenant le type correspondant en argument dans la classe de base Visitor, mais tu devras aussi rajouter une définition de cette fonction dans tous les visiteurs existants

    D'un autre coté, comme il y a de fortes chances pour que tous les visiteurs doivent effectivement adapter leur comportement pour le nouveau type dérivé de Geometry, cela t'obligera surtout à t'assurer que ce soit bel et bien le cas, car le compilateur sera plus têtu que toi si ce n'est pas le cas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 145
    Points : 392
    Points
    392
    Par défaut
    Tout d'abord, je voudrais te remercier vraiment pour la longue réponse que tu m'apportes, j'ai aperçu que tu me proposais d'utiliser le pattern Visiteur (que je pensais utiliser hier).

    Avant de m'attaquer à cette lecture, mon "problème" de dynamic_cast est résolu. En utilisant QtPropertyBrowser et les cast, c'est fonctionnel. Les adresses différentes obtenues venaient du fait que la mémoire n'était pas correctement désalouée lorsqu'on changeait de scène 3D...

    (c'est quand même dingue, 3 sujets d'affilée que je ponds, et 3 bugs dus à la patate qui se trouve sur la chaise...)

    Merci encore

Discussions similaires

  1. Éditeur de propriétés
    Par Gébix dans le forum Débuter
    Réponses: 1
    Dernier message: 08/11/2011, 19h03
  2. Réponses: 0
    Dernier message: 10/11/2010, 13h03
  3. Réponses: 0
    Dernier message: 10/11/2010, 12h57
  4. [jvInspector] Ajouter éditeur de propriété
    Par SergioMaster dans le forum Composants VCL
    Réponses: 3
    Dernier message: 13/06/2008, 09h45
  5. Gestion OnChange sur une sorte d'éditeur de propriétés
    Par Clorish dans le forum Composants VCL
    Réponses: 8
    Dernier message: 29/09/2004, 10h59

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo