Tag Archives: qt

Qt4 et le design pattern Command

Bonjour,  ce petit tutorial va tenter de vous expliquer comment réaliser une opération longue tout en restant réactif dans une application Qt4.
Il existe plusieurs moyens mais je ne vais en décrire qu’un seul qui à mon sens est le plus propre. Comme cas pratique, j’ai choisi d’implémenter le Design patterns “Command”.
La première étape utilise des thread.

Communication entre un thread et une application Qt

Pas de solution miracle, pour faire “deux choses à la fois” dans une application, il faut passer par du multi-threading. Nous allons créer une classe thread qui hérite de QThread. Cette classe sera le support du traitement long à effectuer. Voyez ça comme une sorte encapsulation.

//.h de la classe

#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include "command.h"
class Thread : public QThread
{
    Q_ObJECT
        COMMAND* mycommand;
        bool undo;
public:

    Thread(COMMAND* mycommand,bool undo);

    protected:
        void run();
};

#endif // THREAD_H

Comme vous le voyez, c’est très simple. Nous héritons de QThread, nous avons deux membres dans cette classe: un pointeur vers une instance de la classe COMMAND (utile pour l’implémentation du patterns du même nom) et un booléen qui nous permet de déterminer le sens de l’action “annuler” (undo) ou “normal” (undo == false). bien entendu, il est possible d’avoir autant de paramètre que vous le souhaitez. Il ne faut pas oublié de redéfinir la fonction run().

// l’implémentation

#include "thread.h"

Thread::Thread(COMMAND* _mycommand,bool _undo)
        : mycommand(_mycommand),undo(_undo)
{

}
void Thread::run()
{
    if(undo)
        mycommand->undo();
    else
        mycommand->doCommand();
}

Dans le constructeur, je définis les données membres. La fonction run exécute la commande en fonction du paramètre undo. Il faut savoir que tout le code appelé dans run sera exécuté dans un thread différent.
À ce stade, nous avons une classe Thread qui est prête à recevoir et exécuter une “command”.

Nous allons voir maintenant comment créer un design pattern command.

Implémenter le design pattern command

Ce pattern est très utile pour gèrer l’annulation d’une action ou par exemple refaire la dernière action. Il consiste à créer une classe pour chaque action (du moins toutes les actions que vous voulez pouvoir modifier ou dans notre cas, exécuter dans un thread). Qt fournit des outils pour faire ça. Dans un cadre formateur, je préfère l’implémenter entièrement.
Il faut tout d’abord écrire la classe abstraite qui définit une commande. Cela permettra à notre classe thread de bien intéragir avec la commande.

#ifndef COMMAND_H
#define COMMAND_H
#include <QObject>
class COMMAND : public QObject
{
        Q_ObJECT

public:
    virtual void doCommand()=0;
    virtual void undo()=0;
signals:
        void Maximum(int M);
        void Minimum(int m);
        void valueChanged(int v);
        void done();
};

#endif // COMMAND_H

Il y a deux méthodes abstraites pures: l’une pour faire la commande, l’autre pour l’annuler. J’ai ajouté quelques signaux pour que la commande communique avec le thread principal pour informer l’utilisateur de l’avancée du la tâche en cours. Toutes les futures commandes de notre application doivent être des sous-classes de COMMAND. Vous l’avez deviné, il faut implémenter une commande, maintenant.
Notre commande sera vraiment basique, c’est une commande d’attente (so useless).

//wait.h

#ifndef WAIT_H
#define WAIT_H
#include "command.h"
class WAIT : public COMMAND
{
public:
    WAIT();

    virtual void doCommand();
    virtual void undo();

};

#endif // WAIT_H

Rien de particulier, juste la re-définition des fontions virtuelles pures. Voici leurs implémentations

#include "wait.h"
WAIT::WAIT()
{

}

void WAIT::doCommand()
{

int step = 10;
        emit Maximum(100);
        int i = 0;
        int k = 0;
        emit Minimum(i);
    for(int j = 0 ; j< 1000 ; j++)
    {
        sleep(0.5); //fake statement
        if(i>=step)
        {
                emit valueChanged(++k);
                i = 0;
         }
        i++;

    }
    emit valueChanged(++k);
    emit done();
}

void WAIT::undo()
{

}

Dans notre “doCommand”, nous calculons le pas de la notification de l’application principale. Ici, j’ai arbitrairement choisi 10 mais dans un contexte utile, le pas est égal à la taille de vos données à traiter divisé par le nombre de notification que vous voulez.
Nous émettons la valeur maximale. Initialisation des variables temporaires (i et k). On émet i (0). Nous faisons une bouble sur chaque élément à traiter, on fait le traitement et on calcule un peu pour savoir s’il faut ou pas prévenir l’application principale.
Il est bon de ne pas prévenir à chaque fois, car si vous travaillez sur plusieurs milliers ou millions de données le traitement des signaux ralentira un peu votre application. Comme exemple, imaginez que vous travaillez sur chaque pixel d’une grosse photo.

Il ne reste plus qu’a écrire la fenêtre principale qui affichera à l’utisateur l’avancée de notre traitement.

QProgressBar et QThread.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>
#include <QProgressBar>
#include <QDockWidget>
#include "thread.h"
namespace Ui
{
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QProgressBar *workinprogress;
    QDockWidget *Progressdock;
    Thread* myThread;
};

#endif // MAINWINDOW_H

Rien d’original, une mainwindow avec juste trois membres. Une QProgressBar qui affichera la progression.
Un DockWidget pour afficher la progesse bar, je ne l’ai pas implémenté dans cet exemple mais il peut être amusant d’afficher le dock quand une opération est en cours et la cacher quand c’est fini. Le dernier membre est une instance de notre classe Thread.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "wait.h"



MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    workinprogress = new QProgressBar;
    Progressdock = new QDockWidget(tr("progress panel"));
    workinprogress->setValue(0);
    Progressdock->setWidget(workinprogress);
    Progressdock->setAllowedAreas(Qt::BottomDockWidgetArea);
    addDockWidget(Qt::BottomDockWidgetArea,Progressdock);
    WAIT* mywait = new WAIT();

    myThread = new Thread(mywait,false);
    connect(mywait,SIGNAL(Maximum(int)),workinprogress,SLOT(setMaximum(int)));
    connect(mywait,SIGNAL(Minimum(int)),workinprogress,SLOT(setMinimum(int)));
    connect(mywait,SIGNAL(valueChanged(int)),workinprogress,SLOT(setValue(int)));

    connect(ui->pushButton,SIGNAL(clicked()),myThread,SLOT(start()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

Nous initialisons la QProgressbar et le QDockwidget. En suite, je paramètre un peu nos deux instances. Je crée alors une instance de WAIT et j’initialise le thread avec en paramètre l’adresse de notre commande.
Je connecte maintenant les signaux de la commande au slot de la QProgressBar.
Finalement, je connecte le clique sur “pushButton” sur le démarrage du Thread.
Ainsi, à chaque clique, il demarrera la commande wait.

Aller plus loin

Il serait préférable de créer une instance de WAIT à chaque clique et de l’ajouter dans une pile (dans la mainwindow). Cela est nécessaire pour finir le design pattern command. En haut de la pile, se trouve la dernière commande, si elle est annulée alors il faut la dépiler et exécuter undo().

Je proposerai en téléchargement ce petit exemple, d’ici quelques jours. En attendant, une petite capture d’écran. Capture d'écran Design Pattern Command, QThread et Qt.