I. Introduction

QVariant est un type très puissant permettant de stocker n'importe quel type de variable sous une forme abstraite. Depuis Qt4, il est possible d'y rajouter ses propres types, ainsi que celui de librairies tierces. Il y a 2 restrictions :

  • Connaître le nom du type (facile ;-) )
  • Dans le cas d'une classe, elle doit répondre aux contraintes suivantes : avoir un constructeur par défaut public (ne prenant aucun argument, ou tous les arguments ayant une valeur par défaut),avoir un constructeur par copie public (c-à-d MaClasse(const MaClasse&) ),avoir un destructeur public.

Il suffit d'ajouter la macro Q_DECLARE_METATYPE(type), après avoir inclus QVariant, et après la déclaration du type. Si le type à gérer est déclaré dans un en-tête que vous ne pouvez/voulez pas toucher (celui d'une bibliothèque tierce par exemple), il suffit de la saisir quelque part après l'inclusion dudit en-tête.

II. Exemples

Voici un exemple illustrant cette fonctionnalité. Imaginons que nous voulions nous servir de QVariant pour transmettre les données d'un type Vector3 défini dans une bibliothèque tierce (bien que les sources soient fournis ici pour éviter les multiples projets...).

II-1. Vector3

Ce type est défini dans le fichier suivant vector3.h.

vector3.h
Sélectionnez

#ifndef VECTOR3_H
#define VECTOR3_H
class Vector3
{
public:
    Vector3() : m_x(0.0), m_y(0.0), m_z(0.0) {}
    Vector3(float x, float y, float z) : m_x(x), m_y(y), m_z(z) {}
    Vector3(const Vector3 &v) : m_x(v.m_x), m_y(v.m_y), m_z(v.m_z) {}
    ~Vector3() {}

    float getX() const { return m_x; }
    float getY() const { return m_y; }
    float getZ() const { return m_z; }

    void setX(float x) { m_x = x; }
    void setY(float y) { m_y = y; }
    void setZ(float z) { m_z = z; }

private:
    float m_x, m_y, m_z;
};
// S'il s'agissait de votre propre classe et que vous vouliez la coupler pour de bon à Qt,
// vous voudriez très probablement ajouter ces 2 lignes:
// #include <QVariant>
// Q_DECLARE_METATYPE(Vector3);
#endif

II-2. Utilisation de Vector3 avec QVariant

Pas grand chose à expliquer ci-dessous, la documentation citée en début de document illustre la conversion vers et à partir d'un QVariant avec type personnalisé.

 
Sélectionnez

#include <iostream>
#include <string>
#include <QVariant>
#include "vector3.h"
// Nous enregistrons le type Vector3 pour son utilisation avec QVariant
// seulement nécessaire si ce n'est pas déjà dans le fichier d'en-tête
Q_DECLARE_METATYPE(Vector3);
using namespace std;
inline void displayVector3(const string &name, const Vector3 &v)
{
    cout << name << "={"
        << v.getX() << ","
        << v.getY() << ","
        << v.getZ() << "}" << endl;
}
int main(int argc, char **argv)
{
    Vector3 v1(1.0, 1.0, 0.5);
    displayVector3(string("v1"), v1);
    QVariant var;
    var.setValue(v1); // var contient donc maintenant v1(1.0, 1.0, 0.5);
    Vector3 v2(5.0, 0.75, 0.25);
    displayVector3(string("v2a"), v2);
    v2 = var.value<Vector3>(); // récupération dans v2 du Vector3 contenu dans var (ie v1)
    displayVector3(string("v2b"), v2);
    cin.get();
    return 0;
}

II-3. Centralisons un peu

Voilà une première façon de faire. La récupération du Vector3 contenu dans le QVariant se fait à l'aide d'une fonction template. Pour rendre les choses un peu plus lisible, et/ou si vous prévoyez de gérer un certain nombre de classes de cette façon, on peut tout simplement mettre ces macros à part, et regrouper les méthodes de conversion QVariant vers type personnalisé avec.

En voici un exemple :

 
Sélectionnez

#ifndef DECL_METATYPE_H
#define DECL_METATYPE_H
// Doit être inclus pour la macro Q_DECLARE_METATYPE et la définition de la classe Helper
#include <QVariant>
// Pour que Vector3 soit un type connu...
#include "vector3.h"
// Nous enregistrons le type Vector3 pour son utilisation avec QVariant
Q_DECLARE_METATYPE(Vector3);
// Namespace ajoutant des fonctions de conversion à partir de QVariant
namespace QVariantHelper
{
    inline Vector3 ToVector3(QVariant v)
    {
        return v.value<Vector3>();
    }
};
#endif

La seule modification à apporter au main serait la suivante :

 
Sélectionnez

[...]
    Vector3 v2(5.0, 0.75, 0.25);
    displayVector3(string("v2a"), v2);
    // remplace l'utilisation d'un template par quelque chose
    // de plus significatif  mes yeux... ;))
    v2 = QVariantHelper::ToVector3(var);
    displayVector3(string("v2b"), v2);
[...]

II-4. Et les pointeurs ?

Quid des pointeurs vers des types personnalisés?

Eux aussi sont transformables en QVariant. Il suffit de les déclarer comme type eux aussi. En voici un exemple :

 
Sélectionnez

#ifndef DECL_METATYPE_H
#define DECL_METATYPE_H
#include <QVariant>
#include "vector3.h"
// Nous enregistrons le type Vector3 pour son utilisation avec QVariant
Q_DECLARE_METATYPE(Vector3);
// ... ainsi que le type pointeur vers Vector3
Q_DECLARE_METATYPE(Vector3*);
// Namespace ajoutant des fonctions de conversion à partir de QVariant
namespace QVariantHelper
{
    // renvoit le Vector3 contenu dans v, ou le Vector3 par défaut
    // si la conversion n'est pas faisable
    inline Vector3 ToVector3(QVariant v)
    {
        return v.value<Vector3>();
    }
    // renvoit le Vector3* contenu dans v, ou 0 si la conversion n'est pas faisable
    inline Vector3* ToVector3Ptr(QVariant v)
    {
        return v.value<Vector3*>();
    }
};
#endif

Et voici l'assignation d'un pointeur à QVariant et son extraction :

 
Sélectionnez

[...]
    QVariant varVec3;
    varVec3.setValue(v1);
    QVariant varVec3Ptr = QVariant::fromValue(&v1); // Notation générique alternative pour l'assignation d'une valeur à un QVariant
    Vector3 v2(5.0, 0.75, 0.25);
    displayVector3(string("v2a"), v2);
    v2 = QVariantHelper::ToVector3(varVec3);
    displayVector3(string("v2b"), v2);
    Vector3 *v3 = QVariantHelper::ToVector3Ptr(varVec3Ptr);
    // TOUJOURS vérifier si v3 est valide avant utilisation !
    if(v3)
        displayVector3(string("v3(ptr)"), *v3);
    else
        cout << "v3 is an invalid pointer to Vector3!" << endl;
[...]

Et enfin, si votre type est une classe dérivant de QObject, vous devez utiliser QPointer : en effet, QObject n'a pas de constructeur par copie.

Pour cela, il faut juste déclarer le metatype avec le QPointer représentant votre classe (ie Q_DECLARE_METATYPE(QPointer<MyObject>)) en plus des déclarations de MyObject et MyObject*.

QPointer<VotreClasse> et VotreClasse* sont incompatibles !

III. Conclusion

L'article original est situé ici : Etendre le type QVariant.

Si vous avez des questions, n'hésitez pas à vous rendre sur le forum Qt de Developpez.