Les nouvelles façons d'utiliser les dialogues sous Qt 4.5

http://qt-quarterly.developpez.com/qq-15/modele-donnees-sql/images/logo.png

Une des nouvelles fonctionnalités de Qt 4.5 est une collection de nouvelles méthodes dans QDialog qui rendent plus facile à utiliser les boîtes de dialogue. Ceci inclut les boîtes de dialogue modales (les fiches dans Mac OS X) et les dialogues de « commentaires en direct ». Ils sont venus à la suite d'un réexamen des dialogues, qui ont permis de créer une façon plus unifiée de présenter les dialogues et de rendre plus flexible l'utilisation des dialogues natifs - en premier sur Mac OS X et dernièrement sur les autres plateformes.

Commentez Donner une note à l'article (5)

Article lu   fois.

Les trois auteurs et traducteur

Traducteur :

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. L'article original

Le blog KDAB est rédigé par les ingénieurs de KDAB s'occupant des formations, de la consultance ainsi que du développement (de Qt et de produits additionnels). Vous pouvez trouver les versions originales.

Cet article est une traduction de l'article original écrit par Trenton Schultz paru lors du deuxième trimestre 2009.

Cet article est une traduction de l'un des articles en anglais écrits par KDAB. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à KDAB.

II. Introduction

Une des nouvelles fonctionnalités de Qt 4.5 est une collection de nouvelles méthodes dans QDialog qui rendent plus facile à utiliser les boîtes de dialogue d'application au-delà de la manière traditionnelle des dialogues modaux. Ceci inclut les boîtes de dialogues modales (les fiches dans Mac OS X) et les dialogues de « commentaires en direct ». Ils sont venus à la suite d'un réexamen des dialogues, qui ont permis de créer une façon plus unifiée de présenter les dialogues et de rendre plus flexible l'utilisation des dialogues natifs.

III. Dialogues et modalités

La documentation Qt fournit la description suivante pour les dialogues : « une fenêtre de dialogue est une fenêtre de haut niveau utilisée principalement pour les tâches de faible coût et de communications brèves avec l'utilisateur. Les classes QDialog peuvent être modales ou non.

Traditionnellement, quand on utilise les boîtes de dialogues Qt, le code ressemble à ceci :

 
Sélectionnez
MyQDialogSubclass dialog;
// Quelques initialisations
if (dialog.exec() == QDialog::Accept) {
    // Ajout de quelques valeurs et traitement du résultat en fonction des éléments saisis par l'utilisateur
}

Ce code crée une boîte de dialogue et appelle ensuite la méthode exec(), qui arrête toute progression dans la fonction jusqu'au retour de la méthode exec(). Ceci est connu comme une boucle d'événements modale. Les événements « utilisateur », comme l'appui sur un bouton de la souris ou sur une touche du clavier, sont ensuite envoyés sur la fenêtre qui a démarré la boucle d'événements modale, et non sur d'autres fenêtres de l'application (ceci est le « mode » dont les dialogues modaux se réfèrent).

Une autre façon d'utiliser les boîtes de dialogue est de les traiter comme une fenêtre normale. Ceci s'effectue en utilisant les méthodes standards de QWidget (c'est-à-dire show(), close(), etc.). Quand cette façon est utilisée, les dialogues n'ont pas de blocs d'interaction avec les autres fenêtres de l'application. Elles sont à proprement parler non modales, mais elles offrent une autre manière de rejeter les fenêtres utilisées par le dialogue et de lui communiquer le résultat de l'exécution.

Il existe une autre façon d'interagir appelée « interaction avec une fenêtre modale » ou « interaction avec un document modal ». Ce mode est entre l'application modale et non modale. L'idée derrière une boîte de dialogue modale est que le dialogue bloque l'interaction avec la fenêtre dont elle est enfant, mais ne bloque pas les interactions avec les autres fenêtres de l'application. Un exemple qui peut être utilisé dans une application où chaque document est présenté dans une fenêtre séparée. Ouvrir une boîte de dialogue modale qui pose une question, par exemple sauvegarder un fichier, permet à l'utilisateur de continuer à travailler avec les autres fenêtres de l'application (ou sinon d'en créer une nouvelle). Cet exemple montre que la modalité de la fenêtre peut être préférable comparé aux applications qui ont besoin de mettre un dialogue modal pour une fenêtre unique. Naturellement, ce concept prend tout son sens pour les dialogues qui ont une fenêtre parente.

Qt supporte le concept de dialogues modaux avec la propriété windowModality de la classe QWidget et qui a été introduit dans Qt 4.1. Cette propriété permet au programmeur de sélectionner les modalités mentionnées précédemment. Cette modalité prend effet quand la fenêtre est affichée. Par défaut, toutes les fenêtres sont créées avec la propriété windowModality initialisée à Qt::NonModal. Donc, par défaut, les fenêtres qui sont affichées, sont non-modales et ne bloquent pas les interactions avec les autres fenêtres, qui est-ce que nous attendons.

La propriété windowModality est aussi utilisée dans l'implémentation de QDialog::exec(), quand la modalité courante du dialogue est enregistrée, ensuite initialisée à Qt::ApplicationModal, et enfin restaurée lors que la méthode exec() est terminée. En supposant qu'on ne modifie pas la propriété par nous-même, on peut voir un tableau de correspondance entre les méthodes de QDialog et la propriété windowModality :

Qdialog::show() Qt::NonModal
Qdialog::exec() Qt::ApplicationModal

Le tableau précédent n'inclut cependant pas Qt::WindowModal. Par le passé, la solution était d'initialiser la propriété windowModality à Qt::WindowModal avant d'appeler show() ou exec(). Ceci fonctionne, mais est un peu différent que d'appeler une méthode et requiert que le développeur doive se souvenir de cette étape supplémentaire.

image
Figure 1 - Une feuille sous Mac OS X

Cette cartographie, ci-dessus, est incomplète quand on regarde les concepts de feuilles chez Mac OS X. Les lignes de conduite d'interfaces graphiques d'Apple disent : « une feuille est une boîte de dialogue modal attachée à un document particulier ou une fenêtre assurant que l'utilisateur ne perdra jamais le fil auquel la fenêtre s'applique. Les feuilles autorisent aussi les utilisateurs à effectuer d'autres tâches avant de rejeter le dialogue, avec dans le sens du système d'être rejeté par l'application. Un exemple de feuille est montré dans l'image ci-dessus.

Si l'on regarde bien, on voit bien que les feuilles sont simplement une spécialisation des fenêtres modales ce qui rend plus facile de savoir à quelle fenêtre les feuilles sont destinées à être modales.

Qt offre la possibilité d'utiliser les feuilles depuis Qt 4.0. Comme les fenêtres modales, cela requiert une réflexion dans la partie du développeur. Un exemple d'utilisation des feuilles est disponible dans le Qt Quarterly 18, ceci étant une reprise du code :

 
Sélectionnez
void MainWindow::maybeSave()
{
    if (!messageBox) {
        messageBox = new QMessageBox(tr("SDI"),
            tr("Le document a été modifié.\n"
            "Voulez-vous enregistrer les changements?"),
             QMessageBox::Warning,
             QMessageBox::Yes | QMessageBox::Default,
             QMessageBox::No,
             QMessageBox::Cancel | QMessageBox::Escape,
             this, Qt::Sheet);
        messageBox->setButtonText(QMessageBox::Yes,
            isUntitled ? tr("Sauvegarder...") : tr("Sauvegarder "));
        messageBox->setButtonText(QMessageBox::No,
            tr("Ne pas sauvegarder"));
        connect(messageBox, SIGNAL(finished(int)),
            this, SLOT(finishClose(int)));
    }
    messageBox->show();
}


Ceci crée un objet de type QMessageBox avec comme type Qt::Sheet, une connexion du signal finished() vers un slot de la fenêtre principale pour terminer la fermeture, et affiche la boîte de message. Ce code est très simple et il n'est pas déraisonnable de penser qu'un développeur Mac OS X écrira ce code, mais il est peu probable qu'un développeur multiplateforme se donnera la peine de l'écrire - On utilisera probablement une méthode statique.

Cet exemple, ci-dessus, montre comment utiliser les feuilles correctement - ceci est, comme une fenêtre modale qui se ferme immédiatement et ne bloque pas la méthode. Le résultat de l'exécution de la feuille est traité plus tard dans un slot. Ceci assure que l'interface « utilisateur » fonctionne correctement avec d'autres fenêtres qui ne sont pas bloquées par la feuille.

Nous mentionnons ci-dessus que la méthode peut être retournée après l'affichage de la feuille et ne pas bloquer la feuille jusqu'à ce qu'elle termine son exécution. Il vaut la peine d'explorer le fonctionnement. La façon typique d'empêcher la fonction de revenir alors que les événements ne sont pas encore traités est de créer une variable locale de type QEventLoop et de quitter cette boucle d'événements lorsque la fenêtre se ferme. Un mauvais exemple de comparaison avec le code, ci-dessus, est montré ci-dessous :

 
Sélectionnez
// ATTENTION! A NE PAS FAIRE, LES FEUILLES N'AIMENT PAS CECI!
void MainWindow::maybeSave()
{
    if (!messageBox) {
        messageBox = new QMessageBox(tr("SDI"),
            tr("Le document a été modifié.\n"
            "Voulez-vous enregistrer les changements?"),
            QMessageBox::Warning,
            QMessageBox::Yes | QMessageBox::Default,
            QMessageBox::No,
            QMessageBox::Cancel | QMessageBox::Escape,
            this, Qt::Sheet);
        messageBox->setButtonText(QMessageBox::Yes,
            isUntitled ? tr("Sauvegarder...") : tr("Sauvegarder "));
        messageBox->setButtonText(QMessageBox::No,
            tr("Ne pas sauvegarder"));
    }
    QEventLoop eventLoop;
    connect(messageBox, SIGNAL(closed()),
        &eventLoop, SLOT(quit()));
    messageBox->show();
    eventLoop.exec();
    finishClose(messageBox->result());
}


Tout cela fonctionne dans certains cas, mais ne fonctionne pas dans toutes les situations. Considérons la situation selon laquelle un utilisateur a deux documents avec des modifications non sauvegardées et on veut traiter la fermeture de la fenêtre en utilisant le code précédent. L'utilisateur clique sur le bouton de fermeture de l'un des documents et récupère une feuille pour celui-ci. L'utilisateur clique ensuite sur le bouton de fermeture de l'autre document et une feuille est affichée pour cette fenêtre.

L'utilisateur revient ensuite en arrière et clique sur le bouton « Ne pas sauvegarder » de la première fenêtre qui est affichée. La feuille est rejetée, mais la fenêtre n'est pas fermée, car elle est bloquée dans la seconde boucle d'événement créée par la deuxième feuille. Clairement, ce n'est pas le comportement désiré.

Un autre endroit possible où les feuilles peuvent être utilisées à mauvais escient est d'utiliser une feuille d'application modale. Qt a autorisé ceci par le passé (et les boîtes de dialogue statique de Qt font exactement cela), ceci n'est pas recommandé par les lignes de conduite d'interfaces d'Apple. Cela créé aussi de la confusion, car la feuille n'a pas ce fonctionnement dans d'autres applications. En d'autres termes, les boîtes de dialogue statique de Qt ne peuvent pas utiliser les feuilles.

IV. Introduction à QDialog ::open()

Durant le portage Cocoa, un certain temps sera pris pour résoudre le problème des feuilles et tenter de rendre ensuite ceci plus simple.

Une autre chose qui a été remarquée est que QDialog::exec() rend le résultat du travail du dialogue dans une application modale si la modalité de la fenêtre est Qt::NonModal. Il semble logique vu que l'on peut ajouter une méthode pour faire quelque chose de similaire pour les boîtes de dialogue modal. Ceci résulte de la méthode QDialog::open().

QDialog::open() affiche un dialogue comme une fenêtre modale. Sur Mac OS X, le dialogue sera une feuille. Pour coopérer correctement dans toute la boucle d'événements, la méthode open() retourne immédiatement. Ceci requiert pour nous d'utiliser plusieurs sortes de connexions signaux-slots pour terminer le travail sur la fenêtre. Par chance, QDialog dispose d'un signal finished() qui contient la valeur qui est modifiée quand la méthode finish() est appelée, comme lorsque les méthodes accept() et relect() sont appelées.

Bien sûr, rien ne vous empêche d'ajouter vos propres signaux dans vos sous-classes de QDialog. La meilleure solution est d'effectuer un remplacement du code qui a été écrit dans le dernier article de Qt Quarterly, et, comme un bonus, on n'est pas obligé de spécifier la valeur Qt::Sheet lors de la création de la boîte de message. Plutôt que d'appeler la méthode show(), on peut appeler la méthode open() et utiliser une feuille qui fonctionne comme une fenêtre modale. On a donc le bénéfice que l'application est activée pour faire le travail avec d'autres fenêtres. L'utilisateur peut donc revenir sur la feuille quand il le souhaite. Ceci fonctionne bien sur toutes les plateformes.

Avec l'avènement de la méthode QDialog::open(), on a maintenant une bonne cartographie des méthodes pour faire correspondre les différents types de modalités.

QDialog::show() Qt::NonModal
QDialog::exec() Qt::ApplicationModal
QDialog::open() Qt::WindowModal

Ceci permet de choisir les modalités des fenêtres pour les boîtes de dialogue plus simplement que par le passé. Créer une boîte de dialogue et appeler la méthode qui correspond à la modalité que l'on souhaite. Il est aussi important qu'une autre partie du puzzle, que nous verrons plus loin.

V. Méthodes statiques des sous-classes

Un autre but est de rendre impossible une mauvaise utilisation des feuilles. La manière pour y arriver est de réaliser un couplage étrange avec les modalités de fenêtres. Cela veut dire que spécifier le type de feuille n'est plus nécessaire. Ainsi, si on essaye d'ouvrir une application à base de feuilles modales ; on veut aussi obtenir une boîte de dialogue normale d'application. Cela veut aussi dire que, à chaque fois qu'on demande une boîte de dialogue modale, on obtient une feuille. Ceci est le résultat du portage de Cocoa comme Cocoa n'a pas de mécanisme pour afficher les boîtes de dialogue modal sans utiliser une feuille.

La décision prise que les dialogues modaux d'applications ne seront jamais des feuilles a une conséquence pour toutes les méthodes statiques qui existent dans les sous-classes de QDialog (par exemple QColorDialog, QFontDialog, QFileDialog, QInputDialog et QMessageBox). On pense en particulier aux méthodes de création, c'est-à-dire la méthode QColorDialog::getColor(), issue de la famille des méthodes qui crée un QColorDialog et l'affiche avec une façon modale. Chaque méthode retourne la couleur choisie par l'utilisateur dans le dialogue - ou un objet QColor invalide si l'utilisateur a annulé l'opération.

Le problème de ces méthodes statiques est que, depuis qu'elles requièrent une boîte de dialogue modale d'application, on ne peut plus utiliser les feuilles. Ceci peut être un choc pour les utilisateurs de QFIleDialog depuis que Qt, dans certains cas, exécute ce dialogue comme une feuille (en dépit des effets utilisés pour la convivialité). Depuis que d'autres applications de Mac OS X peuvent exécuter le dialogue de fichiers comme une feuille, il pourrait être bien d'avoir cette compétence dans Qt.

La méthode QDialog::open() nous donne un moyen d'accéder aux trois types de modalités, on décide alors de regarder si on peut inclure certaines fonctionnalités additionnelles dans les sous-classes de QDialog. On est heureux avec le résultat et de croire qu'il est offert d'accéder à plus d'idiomes pour interagir avec les documents qui requièrent plus de travail que par le passé.

Pour plusieurs de ces sous-classes, on ajoute quelques surcharges de la méthode open() pour plus de commodité, qui autorise les développeurs à spécifier les slots pour traiter les résultats. Les dialogues prendront soin de connecter les signaux appropriés à ces slots, sans que du code supplémentaire soit nécessaire.

Ces surcharges nous donnent la liste suivante :

  • QFileDialog::open(QObject *receiver, const char *slot)
  • QColorDialog::open(QObject *receiver, const char *slot)
  • QFontDialog::open(QObject *receiver, const char *slot)
  • QPrintDialog::open(QObject *receiver, const char *slot)
  • QPageSetupDialog::open(QObject *rec, const char *slot)
  • QInputDialog::open(QObject *receiver, const char *slot)
  • QProgressDialog::open(QObject *receiver, const char *slot)
  • QPrintPreviewDialog::open(QObject *rec, const char *slot)

L'idée est de connecter les arguments passés aux signaux caractéristiques qui sont utilisés pour le dialogue. Ci-dessous se trouve une liste qui montre quels signaux des dialogues se connecteront sur quels slots :

  • QColorDialog connectera le slot passé par la méthode colorSelected(QColor) ;
  • QFontDialog connectera le slot au signal fontSelected(QFont) ;
  • QFileDialog connectera le slot aux signaux fileSelected(QString) ou filesSelected(QStringList), dépendant du mode sélectionné.
  • QProgressDialog connectera le slot au signal cancelled().

On peut également vérifier ceci sur la documentation officielle de Qt pour en apprendre plus sur les signaux que les dialogues peuvent connecter. Réaliser le paramétrage dans la méthode open() permet de garder le code propre et simplifie le paramétrage du dialogue. On peut aussi garder le bénéfice d'une boîte de dialogue native dans les cas où elles sont fournies, comme c'est le cas couramment avec QFileDIalog, QColorDialog, QFontDialog et QPrintDialog.

VI. Nouvelles interactions

Depuis que l'on a fait ce gros effort pour faire fonctionner les boîtes de dialogue natives avec la méthode open(), il ne sera pas plus compliqué de faire également ce travail quand les dialogues seront affichés de façon non modale avec la méthode show(). Bien qu'au premier coup d'œil, cela ne semble pas d'une grande utilité, ceci ouvre un nouveau paradigme d'interaction qui est une métaphore standard sur Mac OS X, les dialogues de retour « live feedback ». Quelque chose de très facile à faire avec Qt.

Les méthodes statiques « get » des sous-classes de QDialog nous encouragent à écrire des applications où on interrompt l'utilisateur pour lui poser une question (comme « Quelle police voulez-vous utiliser ? »). Cependant, il se peut que dans certaines situations cela ralentisse le travail et aussi ennuyeux pour l'utilisateur.

Par exemple, considérons un scénario dans lequel l'utilisateur expérimente avec une couleur en utilisant un dialogue QColorDialog. Il va avoir besoin de cliquer dans le dialogue pour choisir la couleur, fermer le dialogue et de regarder où il doit l'appliquer. S'il n'est pas content de son choix de couleur, le dialogue doit être rouvert avant qu'une autre couleur soit sélectionnée. Naturellement, cela prend du temps et fatiguant de répéter ce processus plutôt que de garder le dialogue ouvert pour choisir une couleur et voir les effets immédiatement.

On peut bien sûr offrir des alternatives pour utiliser les dialogues en réalisant des expérimentations avec des dialogues non modaux comme l'est décrit ci-dessus. Dans le cas d'une sélection de polices, par exemple, Qt dispose de la classe QFontComboxBox qui donne la possibilité de sélectionner les polices d'une façon non modale. Mais cela ne donne pas toute la puissance et la simplicité de l'utilisation dont dispose QFontDialog, et l'utilisation des dialogues est souvent plus la façon la plus intuitive.

Avec l'ajout du signal QFontDialog::currentFontChanged()QFontDIalog qui est émis lorsque la police est modifiée dans le dialogue , on peut se connecter au signal, et, en appelant la méthode show(), on obtient un dialogue qui ne bloque pas les autres fenêtres et cela fournit un retour à l'application. La classe QColorDialog bénéficie aussi de cette configuration similaire. On peut voir comment il est terminé en retravaillant l'exemple « plug-and-paint » pour utiliser le retour du dialogue de choix de couleur. Il est simple et rend le programme un peu plus rapide.

 
Sélectionnez
class MainWindow
{
    Q_OBJECT
    //...
private:
    // ...
    QColorDialog *globalColorDialog;
    // ...
};
class PaintArea
{
    Q_OBJECT
    //...
public slots:
    void setBrushColor(const QColor &color);
    // ..
} ;


Depuis qu'on ne peut plus utiliser le dialogue QColorDialogQColorDialog::getColor() créé avec l'une des méthodes statiques de création (par exemple), on a besoin de garder un pointeur pour y accéder. On le fait dans une sous-classe de la fenêtre principale. On peut aussi avoir besoin de déplacer la méthode QPainterAreas:setBrushColor() vers un slot.

 
Sélectionnez
void MainWindow::brushColor()
{
    if (!globalColorDialog) {
        globalColorDialog = new QColorDialog(this);
        globalColorDialog->setCurrentColor(paintArea->brushColor());
        globalColorDialog->setOption(
            QColorDialog::NoButtons, true);
        connect(globalColorDialog,
            SIGNAL(currentColorChanged(QColor)),
            paintArea,
            SLOT(setBrushColor(QColor)));
    }
    globalColorDialog->show();
}


On met la couleur courante sur le pinceau courant, et on met l'option QColorDialog::NoButtons pour éviter d'afficher les boutons OK et Annuler. La principale raison pour cela, mais qui n'a que peu de sens ici, est qu'on sait paramétrer la couleur immédiatement (L'annulation ne peut effectuer la modification de la couleur). Cependant, ceci peut être problématique dans certains gestionnaires de fenêtres sous X11 quand le décorateur de fenêtre ne peut inclure de bouton de fermeture. Finalement, on peut configurer une connexion entre le signal currentColorChanged() et le slot setBrushColor() que l'on a promu. On appelle ensuite la méthode show() du dialogue. SI la couleur du dialogue est toujours visible, l'appel à la méthode show() est annulé dans les autres fenêtres.

Ceci n'est pas une surprise, car ce n'est pas différent de faire la même chose avec QFontDialog. On crée un dialogue de choix de police auquel on récupère le pointeur et on se connecte à la classe PaintArea qui sélectionne la police. On peut aussi regarder plus loin sur l'exemple modifié pour voir comment cela est fait pour un retour du dialogue de police, et quand utiliser les feuilles pour ouvrir et sauvegarder les fichiers.

VII. Conclusion

Beaucoup d'efforts ont été nécessaires pour réaliser cette nouvelle façon de travailler avec les dialogues et les faire fonctionner sans heurts, et on pense que la plupart des personnes trouveront ces nouvelles façons d'interagir plus cohérente et utilisable. Bien qu'il y ait encore quelques problèmes avec Qt 4.5.0 et 4.5.1, la plupart d'entre eux ont été enregistrés et ils n'y ont pas de meilleurs moments pour les traiter. Nous espérons aussi qu'il y aura plus de dialogues natifs dans les futures versions de Qt.

VIII. Remerciements

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !

Je tiens à remercier Thibaut Cuvelier pour ses conseils et zoom61 pour sa relecture.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2009 Trenton Schultz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.