Introduction

Depuis Qt4, le designer a repris sa fonction première : la création WYSIWYG de forms(terme générique pour désigner soit une fenêtre principale, soit une boîte de dialogue, soit un widget personnalisé). L'utilisation du designer en lui-même et les différences entre ces forms ne seront pas abordés ici.

Une fois que l'utilisateur a créé une form et l'enregistre sur le disque, il crée un fichier .ui qui n'est autre qu'un fichier XML décrivant la form :

  • widgets qui composent la form
  • organisation (layout) et position des différents widgets
  • connexions entre les différents widgets
  • ...

Ce fichier .ui n'est pas directement compilable : pour être utilisé, il doit être converti en fichier C++ à l'aide de l'outil uic. Avec l'utilisation combinée de uic et de qmake, le code C++ est généré automatiquement lors de la compilation de l'application.

Il existe trois méthodes différentes pour intégrer les forms créées avec le designer. Pour l'exemple, vous pouvez utiliser n'importe quelle form, vous en trouverez une en annexe de cet article (qui s'appelle mywidget.ui) :

designer-exemple.png

Fichier généré

Le fichier généré par uic n'est pas un QWidget (ou QMainApplication/QDialog), mais c'est juste une classe de description de l'interface contenant le code de création et d'organisation des widgets, ainsi que les connexions définies dans le designer.

Par exemple, à partir du fichier mywidget.ui :

  • un fichier ui_mywidget.h va être créé,
  • ce fichier contient une classe dans le namespace Ui : Ui::MyWidget (pour rappel, MyWidget est le nom que j'ai donné à la form dans le designer),
  • cette classe contient une fonction importante : void setupUi(QWidget *MyWidget).

Méthode directe

La méthode directe est relativement simple : un widget va être créé pour servir de conteneur à notre form. Voici le .pro utilisé.

TEMPLATE    = app
FORMS       = mywidget.ui
SOURCES     = main.cpp

Note : il ne faut pas indiquer le fichier créé par uic, ici ui_mywidget.h, ce dernier étant implicitement déclaré par le FORMS

La fonction main va créer un widget qui servira de conteneur à l'interface : c'est la ligne ui.setupUi(window);. Voici le fichier complet :

#include <QApplication>
#include "ui_mywidget.h"
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget *window = new QWidget;
    Ui::MyWidget ui;
    ui.setupUi(window);
 
    window->show();
    return app.exec();
}

Bien que rapide et simple d'utilisation, cette méthode est très limitée car elle ne permet pas de définir des signaux et slots, de connecter des éléments entre eux... cette méthode est utile pour des fenêtres statiques, comme par exemple des fenêtres A propos.

Méthode héritage simple

Ici, plutôt que de créer un widget puis de lui appliquer une interface à l'aide de la fonction setupUi, nous allons créer une classe qui hérite de QWidget (ou QDialog/QMainWindow, en fonction du type de form créée) et lui appliquer l'interface dans le constructeur : une instance de la classe Ui::MyWidget est déclarée comme membre de notre classe.

#include "ui_mywidget.h"
#include <QWidget>
 
class MyWidget : public QWidget
{
    Q_OBJECT
 
public:
    MyWidget(QWidget *parent = 0);
...
private:
    Ui::MyWidget ui;
};

Il est toujours nécessaire d'appeler la fonction setupUi, mais cette fois-ci dans le constructeur de notre classe MyWidget :

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
}

L'avantage ici, c'est que la déclaration de l'interface est un membre de la classe, les différents widgets ainsi que les layouts/signaux/méthodes sont donc directement accessible dans notre classe MyWidget. Nous allons par exemple ajouter un slot clickMe() qui sera connecté sur un clic sur le bouton Cliquez moi !! (qui porte bien son nom) :

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(clickMe()));
}
 
void MyWidget::clickMe()
{
    ui.lineEdit->setText("YEAH !!!");
}

Note : remarquez ici que tous les widgets qui composent la form sont appelés à l'aide du membre ui.

Cette méthode offre de nombreux avantages au développeur :

  • accès aux différents widgets de la form,
  • encapsulation de l'interface dans un widget facilement utilisable,
  • possibilité d'utiliser plusieurs interfaces...

A propos des noms

Comme vous l'avez remarqué, j'ai employé le même nom partout :

  • 3 fichiers : mywidget.ui, mywidget.h et mywidget.cpp,
  • dans le designer, l'interface s'appelle MyWidget et lors de la compilation, la classe Ui::MyWidget est créée,
  • la classe dérivée s'appelle aussi MyWidget (pas de problème de nommage, la classe précédente étant dans un namespace).

C'est une préférence personnelle, mais il est tout à fait possible d'utiliser des noms différents entre l'interface et la classe dérivée. On peut par exemple imaginer deux classes différentes qui partagent la même interface.

Compilation

Il est nécessaire de modifier notre fichier .pro utilisé pour la compilation, en ajoutant la classe créée :

TEMPLATE    = app
FORMS       = mywidget.ui
SOURCES     = main.cpp \
              mywidget.cpp
HEADERS     = mywidget.h

Note : il ne faut pas indiquer le fichier créé par uic, ici ui_mywidget.h, ce dernier étant implicitement déclaré par le FORMS

Méthode héritage multiple

Cette méthode est très proche de la méthode précédente (les notes sur le choix des noms et sur la compilation sont toujours valables), la différence étant que l'interface n'est plus un membre de la classe héritée, mais elle est aussi héritée par le widget :

#include "ui_mywidget.h"
#include <QWidget>
 
class MyWidget : public QWidget, private Ui::MyWidget
{
    Q_OBJECT
 
public:
    MyWidget(QWidget *parent = 0);
};

Nous avons effectué un héritage privé, pour ne pas exposer l'interface dans les éventuelles sous-classes de notre classe. Mais nous pourrions très bien faire un héritage protégé ou publique afin qu'elle soit accessible.

Là encore, il est nécessaire d'appeler la méthode setupUi, mais les méthodes et les widgets sont directement accessibles dans le code du widget (il n'est plus nécessaire d'utiliser ui) :

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
    setupUi(this);
}

Personnellement, c'est ma méthode préférée : elle offre une meilleure lisibilité du code, par contre il n'est plus possible de modifier l'interface (mais était-ce vraiment un problème ?).

Connexions automatiques

Depuis Qt4, il n'est plus possible de créer des slots directement dans le designer et de les connecter graphiquement... il est donc nécessaire de définir la connexion dans le constructeur (comme nous l'avons fait pour la méthode héritage simple). Mais il est possible de définir des connexions automatiques avec des noms de slots utilisant une certaine convention :

 on_<nom du widget>_<nom du signal>(<paramètres du signal>);

Lors de la compilation de la form par uic, il génère le code nécessaire à la création automatique des connexions. Dans notre exemple, nous aurions pu utiliser le slot suivant :

void MyWidget::on_pushButton_clicked()
{
    ui.lineEdit->setText("YEAH !!!");
}