Mémo sur la bibliothèque graphique C++ Qt
Michel Llibre – Octobre 2009.
Qt est une bibliothèque C++ qui offre des primitives graphiques : fenêtres, menus, gestion des évènements, … indépendante de la machine et du système d'exploitation. Elle est à mon avis mieux structurée et plus simple à utiliser que GTK.
Actuellement (2011) Qt est distribué gratuitement par Nokia.
Sous Windows on installe le SDK Qt qui contient un environnement MinGW/Msys (petit environnement de type linux) qui permet de compiler en ligne de commande et QtCreator qui un environnement de développement complet intégrant éditeur de texte avec auto-complétion,, système de compilation, linkage, exécution, debug, et surtout une superbe aide en ligne.
Sous Linux, je ne sais pas, mais cela doit être encore plus simple, car Qt fonctionne surtout avec des outils GNU (gcc, gdb, etc..).
En mode console : Mettre les sources dans un dossier, nommé par exemple bidon.
Ensuite, on utilise l'utilitaire qmake, par la commande :
qmake -project
pour générer un fichier projet bidon.pro du type ci-dessous :
######################################################################
# Automatically generated by qmake (2.00a) ti 8. aug 18:09:23 2006
######################################################################
TEMPLATE = app
TARGET +=
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += fichier.cpp
CONFIG += console
Ensuite la commande :
qmake
génère un fichier Makefile pour linux et deux fichiers Makefile.Debug et Makefile.Release pour windows, qui sont compilés par la commande :
make
Remarque : La dernière ligne "CONFIG += console" est à ajouter (au cas où elle serait absente) pour capturer les sorties vers stderr et stdout.
Nous décrivons ci_après des applications qui utilisent la gestion des événement de Qt lancée dans le main par l'instruction app.exec(). En l'absence de cette instruction, on est dans le cadre d'une application ordinaire qui ne peut pas utiliser tous les mécanismes mis en œuvre dans Qt.
#include <QCoreApplication>
#include <stdio.h>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv); // Ini obligatoire pour gestion Qt
puts("Hello Michel !");
return app.exec();
}
L'instruction app.exec() lance la boucle de gestion des évènements qui permet à la console de durer jusqu'à ce qu'on la ferme.
#include <qapplication.h> //Obligatoire ou #include <QApplication>
#include <qmessagebox.h> // ou bien #include <QmessageBox>
int main(int argc, char* argv[])
{
QApplication app(argc,argv); //Ini obligatoire pour gestion graphique
QMessageBox boite;
boite.information(0,"BOITE A MESSAGE","Bonjour Michel ");
app.quit();
}
Dans cet exemple, après avoir examiné le QMessageBox::information(..), on quitte l'application. Si on mettait return app.exec() il n'y aurait pas de moyen de terminait l'application, car plus rien n'est affiché.
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel *label = new QLabel("Hello Qt!");
label->show();
return app.exec();
}
Dans celui_ci, le label sans parent est une fenêtre principale avec X pour quitter. app.exec() lance la boucle de gestion des évènements dont on sort à la fin de l'appli.
Au lieu d'inclure <QLabel>, <QWidget>, ..., etc, on peut utiliser la ligne unique qui les inclut tous :
- avant QT5 :
#include <QtGui>
-depuis QT5
#include <QtWidgets>
Remarque : Pour appeler App.quit() depuis un autre module on peut utiliser qApp qui est une macro qui permet d'accéder à la QApplication. Il suffit alors d'appeler :
qApp→quit(); // inclure <QApplication> pour accéder à la macro
Une application CONSOLE doit inclure <QCoreApplication> et la fonction
int main(int argc, char *argv[])
doit débuter par :
QCoreApplication app(argc, argv);
et terminer par :
return app.exec();
Une application WINDOW doit inclure <QApplication> et la fonction
int main(int argc, char *argv[])
doit avoir pour première instruction:
QApplication app(argc, argv);
et pour dernière :
return app.exec();
L'application se termine par l'appel de la méthode quit() de l'application.
Dans QtCreator, les fichiers d'extension .c sont compilés en C et ceux d'extension c++ sont compilés en C++. On peut utiliser l'environnement QtCreator pour compiler du C/C++ ordinaire qui n'ont pas besoin des librairies Qt. Pour cela il faut adapter le project file *.pro dont voici un exemple qui compile le fichier main.c sans utiliser les librairies QT :
TEMPLATE = app
QT -= core
QT -= gui
CONFIG -= qt
CONFIG += console
TARGET = ctest
SOURCES += main.c
La première ligne TEMPLATE spécifie le type de la cible a réaliser. Ici app pour application, il y a également lib pour librairie.
La deuxième et troisième ligne QT -= core,gui spécifie qu'on ne veut pas réaliser une interface graphique QT (ce qui serait fait par défaut).
La quatrième ligne CONFIG -= qt, spécifie qu'on ne veut pas utiliser les includes et librairies Qt qui sont incluses et utilisées par défaut (cependant semble redondant avec les 2 lignes précédentes).
La quatrième ligne TARGET spécifie qu'on veut réaliser une application de type console.
La cinquième ligne spécifie le nom donné à la cible (ctest.exe)
La dernière ligne SOURCES spécifie le nom du fichier à compiler (chemin par rapport au répertoire contenant *.pro).
Pour compiler avec les librairies statiques ajouter :
win32 {
QMAKE_LFLAGS += -static-libgcc
}
En savoir plus : Dans l'aide QtCreator : Qt Reference Documentation/QMake Manual/Manual/Qmake réference.
Pour ajouter des librairies, un clic droit dans le fichier *.pro ouvre un menu dans lequel en choisissant "add library" on obtient un wizard qui est bien utile.
A partir de QT4 ajouter la ligne suivante dans le fichier *.pro si nécessaire :
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets.
Pour passer au compilateur un niveau d'optimisatioin, on ajoute au *.pro une ligne de ce type :
QMAKE_CXXFLAGS += -O2.
Pour empêcher la compression des fichiers resources on ajoute au *.pro la ligne suivante :
QMAKE_RESOURCE_FLAGS += -no-compress
Pour compiler un fichier *.c en C99 (par exemple pour pouvoir écrire for(int i=0;i<3;i++)…) on ajoute au *.pro la ligne suivante :
QMAKE_CFLAGS += -std=c99
Pour utiliser la librairie openGL glut, il y a des problèmes à partir de la version 5.5 de Qt qui suppose qu'on utilise des fonctions openGL enrobées par Qt. Pour continuer à faire tourner les anciens codes, faire :
win32 {
greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32
}
else {
greaterThan(QT_VERSION, 5.5): LIBS += -lGLU
}
Avant Qt5, il suffisait d'inclure <QtGui> pour que toutes les classes nécessaires soeint incluses, et rien de spécial dans le *.pro. Depuis Qt5, cela ne marche plus. Si on utilise les fonctions du gui Qt, il faut ajouter les lignes suivantes dans le *.pro :
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
Par ailleurs, dans le code, il ne faut plus mettre le #include <QtGui>. On peut soit le remplacer par #include <QtWidgets> , soit mettre tous les includes individuels nécessaires.
Des modules sont maintenant séparés et il faut signaler leur nécessité dans le *.pro, comme :
QT += webkitwidgets si dans les sources on utilise #include <QtWebKitWidgets>,
QT += printsupport si dans les sources on utilise #include <QPrinter> ou #include <QPrintDialog>.
Pour pouvoir éxécuter une application compilée en mode release sur un autre pc, il faut copier en plus de l'exécutable de l'application, dans le répertoire qui la contient, les dll suivantes provenant de ???/Qt/5.x/mingwXX_YYY/bin :
Qt5Core.dll, QtGui.dll, et éventuellement Qt5Widgets.dll, Qt5OpenGL.dll, Qt5PrintSupport.dll, …
En plus de ces dll, il faut copier d'autres dll provenant de répertoires en dessous du répertoire ???/Qt/5.x/mingwXX_YYY/plugins. Obligatoirement la dll qwindows.dll provenant du répertoire ???/Qt/5.x/mingwXX_YYY/plugins/platforms, et éventuellement d'autres dll provenant de répertoires ???/Qt/5.x/mingwXX_YYY/plugins/xxxxx. Ces dll ne doivent pas être doivent être copiées dans le même répertoire que l'exécutable de l'application, mais dans un sous-répertoire du même nom que celui qui les contenait : Ainsi qwindows.dll sera copiée dans ./platforms/qwindows.dll et par exemple la dll qjpeg.dll provenant de ???/Qt/5.x/mingwXX_YYY/plugins/imageformat sera copie dans ./imageformat/qjpeg.dll.
qreal est un typedef double sauf sur le cpu ARM où c'est un float.
qDebug(), qWarning(), QCritical() et QFatal() impriment sur le gestionnaire de
message par défaut, sinon sur stderr (et dans le debugger en mode debug). Sous windows qDebug() envoie les messages sur la console. La syntaxe est identique pour tous :
qDebug("Valeur = %lf",val);
Dans cette première forme le format doit être en Latin-1.
Pour les formes suivantes il faut inclure #include <QtDebug>
qDebug() << "Valeur = " << val;
qDebug() << QDir::currentPath();
Cette partie peut-être sautée dans un premier temps, pour y revenir si on désire comprendre ce qu'est un QscopedPointer que l'on trouve dans certains codes à propos des QLayouts.
Quasiment toutes les classes Qt héritent de QObject.
Les méthodes de la classe QObject sont accessibles par toutes les classes qui en dérivent sans avoir à préciser d'objet propriétaire. Exemples :
QString tr("Texte à internationaliser"); // mécanisme de traduction
Pour toutes les classes, il existe quasiment tout le temps un constructeur qui prend en argument un QObject*parent (ou un QWidget *parent pour les classes graphiques, QWidget héritant de QObject). Ce lien parental entraîne la destruction automatique de tous les enfants d'un parent quand celui-ci est détruit, ce qui est très pratique car il est alors inutile de faire un delete d'un enfant dans le destructeur de son parent, cela sera fait automatiquement.
A la sortie du constructeur, tous les objets créés dans ce constructeur par une déclaration simple (création directe dans la pile) deviennent inaccessibles, ce qui peut être gênant pour les objets passés renvoyés sous forme de pointeur, car ils n'existent plus. Ces objets doivent donc être créés par un new afin qu'ils perdurent aussi longtemps que nécessaire. Le mécanisme précédent permet leur destruction automatique sans avoir à faire de delete.
Pour certains objets (essentiellement les QLayout), il arrive qu'on ne puisse pas leur donner de parent (un QWidget ne peut être le parent que d'un seul Qlayout, ce qui fait que les autres sont orphelins). Il existe alors un autre mécanisme qui permet de transférer l'héritage QObject : Les QScopedPointer :
Les QScopedPointer sont des templates qui permettent de créer des pointeurs de n'importe quel objet et de transférer l'héritage "QObject" à un autre objet ce qui permettra leur destruction en chaîne.
Exemple :
TClasse *pC = new TClasse(...); // Création du pointeur sur TClasse, sans parent
QScopedPointer<TClasse> pS(pC); // Création d'un alias sur pC qui devient parent de pC.
pS.take(); // Transfert de la propriété à this (instance de la classe en cours).
pS->fonction1(...);
pC->fonction1(...); // On accède aux membres de TClasse par les pointeurs pC ou pS.
fonction2(pC); // En argument de fonction, on utilise pC ou pS.data() (= pC) :
fonction2(pS.data());
La procédure normale, plus compacte, mais moins intuitive est la manière suivante :
QScopedPointer<TClasse> pS(new TClasse(...));
TClasse *const pC = pS.take(); // Transfert de propriété et récupération d'un const pointeur.
pC->fonction1(...); // Accès aux membres de TClasse par le const pointeur pC
fonction2(pC); // également en argument de fonction.
Si un widget est explicitement détruit par le programmeur au moyen d'un "delete", le destructeur du widget est appelé et on peut faire le ménage dans ce destructeur, par exemple faire un close et un delete des QLabels pour lesquels on a fait un new et un show.
Mais si ce delete n'est pas explicitement programmé, il semble que le destructeur d'un widget indépendant (modeless) quelconque ne soit pas systématiquement appelé lors de sa fin de vie. Dans certains cas il est difficile de tuer des widgets affichés sans lien parental avec des widgets qui terminent leur vie. En particulier à la fin de vie d'une QMainwindow contenant un QGLWidget, le destructeur de ce QGLWidget n'est pas appelé, ni son closeEvent. Si des objets sans lien filial ont été créés dans ce QGLWidget, on ne pourra pas les détruire. Pour ce faire, il faut que la QMainWindow les connaisse, et elle pourra les fermer par un close() dans son protected void closeEvent. Exemple :
void myWindow::closeEvent(QCloseEvent *event)
{
if (daccordPourFermer)
{
// Faire le ménage ;
event→accept();
}
else // si pas d'accord pour fermer
{
// Faire autre chose
event→ignore() ; // on n'accepte pas la demande de fermeture
}
}
Mais attention, de ne pas faire de delete ici, car ce closeEvent peut être appelé sans que l'instance de la classe ne soit elle-même détruite, et si elle est ré-activée, il y aura un crash qund elle voudra accéder aux objets détruits.
Une autre possibilité, beaucoup plus simple, est d'en faire un widget lié à un parent qui sera détruit à la destruction de son parent. Mais dans ce cas le widget est modal, et pour le rendre modeless, il suffit d'appeler
this->setWindowFlags(Qt::Window) sur ce widget.
Avant de rentrer dans le graphique examinons des mécanismes particuliers, très performants offerts par Qt, en particulier pour répondre aux actions de l'usager (menus, boutons, etc...).
Les objets Qt (ou autres à condition de les faire dériver de QObject) peuvent communiquer par un système de signal-slot. Un slot est une fonction particulière qui peut être appelée, soit de manière ordinaire ou bien être réveillée lorsqu'un signal est émis au moyen de l'instruction emit monsignal.
Toute classe dérivant de QObject peut utiliser ce système.
Pour mettre en place ce mécanisme, Qt génère du code au moment de l'appel du précompilateur moc. Mais pour cela, les classes, par exemple Toto et Titi doivent être déclarées à part dans des entêtes toto.h et titi.h séparées des éventuels toto.cpp et titi.cpp et ces déclarations doivent être de la forme suivante :
#ifndef TOTO_H
#define TOTO_H
#include <QObject> // ou une classe plus appropriée qui en dérive
class Toto : public QObject { // doit dériver de QObject ou d'une classe dérivée
Q_OBJECT // Macro obligatoire
public:
Toto(...); // Le constructeur
...
public slots:
void mySlot(liste params); // Une méthode réceptrice
private:
...
};
#endif // TOTO_H
/*---------*/
#ifndef TITI_H
#define TITI_H
#include <QObject>
class Titi : public QObject {
Q_OBJECT
public:
Titi(...); // Le constructeur
...
signals:
void mySignal(liste params); // Une méthode émettrice (sans code associé!)
private:
...
};
#endif // TITI_H
Le canal de communication entre un objet Titi et un objet Toto est déclarée par l'instruction
QObject::connect(&titi, SIGNAL(mySignal(..)), &toto, SLOT(mySlot(..)));
Une méthode de Titi utilise l'instruction :
emit mySignal(..);
pour émettre le signal.
Le contrôle passe alors à la méthode mySlot de l'objet toto (ou passera ?).
Les arguments de mySignal(..) sont transmis à mySlot(..). La fonction signal doit avoir (au moins) les mêmes arguments que la fonction slot et dans le même ordre.
La déconnexion peut être faite par :
QObject::disconnect(orig, SIGNAL(signa1(..), dest, SLOT(callback(..)));
Remarques :
•Un même slot peut être activé par différents signaux
•Un même signal peut être envoyé à différents slots
•Un signal peut activer un autre signal, par la fonction du type :
QObject::connect(serveur, SIGNAL(signalA(..)), recepteur, SLOT(signalB(..)));
•Le formalisme précédent est obligatoire pour déclarer de nouveaux signaux ou de nouveaux slots. Il permet au précompilateur moc de générer des fichiers moc_objet1.cpp et moc_objet2.cpp qui contiendront les fonctions déclarées par QOBJECT :
moc objet1.h -o moc_objet1.cpp
moc objet2.h -o moc_objet2.cpp
L'exécutable final s'obtient en compilant les fichiers moc_objet#.cpp, moc_objet1.cpp et main.cpp :
g++ -o signal mocobjet1.cpp mocobjet1.cpp main.cpp –lqt
Avec l'environnement QtCreator, tout cela est transparent, et de même avec qmake/make.
C'est une classe interface qui permet d'associer une fonction de rappel (une callback) à différents widgets et en particulier pour les menus, boutons, barre d'outils, raccourcis clavier..). L'attribution de l'action au widget se fait par :
QWidget::addAction(QAction *).
Une action peut être attribuée à plusieurs widgets lorsqu'il y a plusieurs moyens de déclencher la même action.
La création de l'action se fait par un des constructeurs suivants :
QAction(QObject *parent)
QAction(const QString &text, QObject *parent)
QAction (const QIcon &icon, const QString &text, QObject *parent)
et peut être complétée par :
setText (const QString &text)
setIcon(const QIcon &icon)
setShortcut(const QList<QKeySequence> &shortcuts) // genre tr("CTRL+N")
setStatusTip(const QString & statusTip) // bulle explicative
setCkeckable(bool) //Crée une action avec coche d'activation
setChecked(bool)// Met ou enleve la coche d'activation
setData(const QVariant &userData) // Y enregistre des données usager
L'association entre la QAction et la fonction de rappel se fait par :
win.connect(action, SIGNAL(...), this, SLOT(...));
ou win est par exemple l'instance de QMainWindow (cf section suivante) où cette action intervient.
Signaux de QAction :
triggered(bool checked=false)//emis qd l'action est activée par l'usager.
toogled(bool checked)// emis qd une action checkable est modifiée
Slots de QAction :
setVisible(bool)// Permet de la rendre visible ou invisible
Nous présentons en premier la classe QMainWindow et ses classes associées QStatusBar, QMenuBar, QMenu, QToolbar,..
La classe QMainWindow dérive de la classe générique QWidget présentée à la section suivante.
Une QMainWindow est une fenêtre ayant un layout particulier avec un QMenuBar, un centralWidget, un QStatusBar...
Méthodes :
QMenuBar *menuBar() // -> pointeur sur le QMenuBar de la fenêtre
setcentralWidget(QWidget *w) // Le QWidget UNIQUE contenu dans la QMainWindow
QWidget *menuWidget()
QToolBar *addToolBar (const QString &title) // Ajoute une portion de ToolBar
QStatusBar *statusBar() // -> sur le QStatusBar
Pour mettre plusieurs choses dans une QMainWindow, on y met un widget que l'on transforme en layout et c'est dans ce layout qu'on mettra plusieurs choses. Exemple :
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout *mainLayout = new QVBoxLayout(); // on le configure en QVBoxLayout
centralWidget->setLayout(mainLayout);
mainLayout->addWidget(wGL);
QHBoxLayout *basLayout = new QHBoxLayout();
mainLayout->addLayout(basLayout);
basLayout->addWidget(play_pauseButton);
basLayout->addWidget(initButton);
basLayout->addWidget(label);
basLayout→addWidget(slider);
Autre Approche équivalente (plus simple) :
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
mainLayout = new QVBoxLayout(centralWidget);
mainLayout->addWidget(wGL);
QHBoxLayout *basLayout = new QHBoxLayout();
mainLayout->addLayout(basLayout);
basLayout->addWidget(play_pauseButton);
basLayout->addWidget(initButton);
basLayout->addWidget(label);
basLayout→addWidget(slider);
On peut obtenir la taille de la fenêtre pricipale par la méthode :
QSize size = qApp->screens()[0]->size();
La ligne d'état en bas d'une QMainWindow.
Méthodes :
addWidget(QWidget *, int stretch = 0) // 0 taille minimale, 1 s'étire au max
pour insérer un contenu (un Qlabel par exemple) dans la ligne d'état.
Slot :
showMessage(const QString &message, int timeout=0) //affiche un message temporairee pendant timeout ms (pas de timeout si 0)
clearMessage() // Efface le message temporaire
La barre de menu horizontale en haut d'une QMainWindow.
Méthodes :
QMenu *addMenu(const QString &title) // Titre du menu ajouté
void addAction(QAction *action) // l'action effectuée qd le menu est cliqué
Méthode :
QMenu *addMenu(const QString &title) //Titre du sous-menu ajouté
void addAction(QAction *action)
void addSeparator()
Dans le langage Qt un controle Microsoft s'appelle une action.
1) Créer et configurer les QAction :
Qaction("nomAction"), puis : setIcon, setShortCut, setStatusTip, connect.
2) Créer les menus et y introduire les actions :
QMenu *f = menuBar()->addMenu(...),
f->addAction(action1) ;....
3) Créer les barres d'outils et y introduire les actions :
QToolBar *t = addToolBar(tr("Titre"));
t->addAction(action1);...
Exemple :
// Les actions
QAction *openAction = new QAction(tr("&Open"), this);
connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
.....
QAction *saveAction = new QAction(tr("&Save"), this);
connect(openAction, SIGNAL(triggered()), this, SLOT(save()));
// Le menu
QMenu *fileMenu = menuBar()->addMenu(tr("&Fichier"));
fileMenu->addAction(openAction);
.....
fileMenu->addSeparator(); // ligne de séparation dans le menu
.....
fileMenu->addAction(saveAction);
// La barre d'outil
QToolBar *t = addToolBar(tr("Titre")); // clic-droit activer/désactiver
t->addAction(action1);
...
t->addSeparator()
...
Pour une MENU CONTEXTUEL c'est encore plus simple car le widget sert de menuBar :
widget->setContextMenuPolicy(Qt::ActionsContextMenu); // autorise le contextuel
widget->addAction(openAction);
widget->addAction(saveAction);
Tous les objets graphiques héritent de QWidget (qui hérite lui-même de QObject et donc du système de signaux et slots). Citons parmi les widgets déjà définis les QLabel, QPushButton, QSpinbox, QSlider, QCHeckBox, QDialog, QLineEdit,....
Le constructeur standard d'un QWidget est :
QWidget::QWidget(QWidget *parent=0, Qt::WindowFlags f=0)
Un QWidget sans parent est une fenêtre de haut niveau (avec X de fermeture).
Un QWidget ayant un parent n'est visible qu'à l'intérieur de la fenêtre de son parent. De plus, il est automatiquement détruit quand son parent est détruit.
Le flag f permet de spécifier l'apparence à l'aide de constantes prédéfinies :
Qt::Widget=0,Qt::Window=1,Qt::Dialog=2,.....
Slots de la classe QWidget :
close()
hide()
show()
setVisible(bool)
setEnabled(bool) // si false ne peut recevoir le focus
setFocus()
lower()
raise()
repaint() // Si le widget est visible envoie un évènement paint à effet immédiat
update() // Si le widget est visible envoie un évènement paint dans la queue
Remarque : Plusieurs update() consécutifs en queue sont remplacés par un seul.
setWindowTitle(const QString) // Mettre [*] à l'emplacement où on veut qu'apparaisse une * qd le contenu est à sauvegarder (cf utilisation méthode suivante)
setWindowModified(bool) // -> Contenu à sauvegarder
showFullScreen()
showMaximized()
showMinimized()
showNormal()
Autres méthodes de la classe QWidget :
- méthodes de gestion fenêtre :
setWindowIcon (const QIcon &icon )//dans le coin supérieur gauche de la fenêtre
activateWindow() // Met le widget visible au premier plan et lui donne le focus
bool isVisible(), isHidden(), isModal, ...... etc
bool isWindowModified()
setDefault(bool) // si true le widget sera activé par l'appui sur Entrée
updateGeometry() // indique aux containers que la geometrie du widget a changé
QSize sizeHint() // On surcharge cette fonction pour renvoyer la taille désirée pour le widget, taille qui sera + ou - prise en compte en fonction de la QSizePolicy.
setFixedHeight(int) // fixe la hauteur
setFixedWidth(int) // fixe la largeur
setMinimumSize(int)
addAction (QAction *action)// ajoute l'action à la liste des actions du widget
setContextMenuPolicy(p) // avec p =
Qt::PreventContextMenu pas de menu contextuel
Qt::NoContextMenu menu contextuel délégué au parent
Qt::ActionsContextMenu menu contextuel à partir des Actions
Qt::DefaultContextMenu
Qt::CustomContextMenu
La méthode suivante (setSizePolicy) n'est prise en compte que si le widget ne contient pas de layout qui gère le dimensionnement de son contenu. La valeur par défaut est Prefered,Prefered.
setSizePolicy(QSizePolicy h, QSizePolicy v) // Politique d'étirement du widget.
QSizePolicy::Minimum : le sizeHint est un minimum, auquel il se limite a priori
QSizePolicy::Maximum : le sizeHint est un maximum
QSizePolicy::Preferred : le sizehint est l'idéal, mais la taille peut varier
QSizePolicy::Expanding : idem, mais tendance à occuper tout l'espace libre
QSizePolicy::MinimumExpanding : le sizeHint est un minimum, mais tendance à occuper le max d'espace libre
QSizePolicy::Ignored : le sizeHint est ignoré, le widget à tendance à occuper le maximum d'espace.
setAttribute(att, bool on=true) // ci-après qqs att :
Qt::WA_DeleteOnClose Le widget est détruit si l'évènement close est accepté
Qt::WA_AcceptDrop
Qt::WA_StaticContents L'intérieur du widget n'est pas affecté par sa taille
il n'est pas agrandi ou rétréci qd on change la geometry du widget
......
setMouseTracking(bool enable) // si false, génère mouseMoveEvent que si un bouton est appuyé
- méthodes de dessin :
const QPalette &palette() const // Renvoi une reference sur la palette
palette().foreground.color() // get couleur de fond
protected :
virtual void closeEvent(QCloseEvent *event)//A surcharger pour gérer la fermeture du widget
virtual void mouseMoveEvent (QMouseEvent * event) // Voir QMouseEvent
virtual void mousePressEvent (QMouseEvent * event) // ....
void paintEvent(QPaintEvent *);
Remarque : A la plupart des fonctions setMembre(..) corespond souvent une fonction getMembre() qui dans Qt s'écrit simplement membre() sans le get.
Pour fixer la taille par défaut, ou préférée du widget ce n'est pas avec un setqqchose() mais (bizarrement) en surchargeant la méthode sizeHint() qui par défaut renvoie une taille (Qsize) invalide. Pour donner une taille par défaut, on la surcharge pour qu'elle renvoie la taille que l'on désire.
La disposition des autres widgets à l'intérieur d'un widget est gérée par la classe abstraite QLayout qui est la classe mère de QHBoxLayout, QVBoxLayout, QGridLayout, etc...
Un layout est normalement créé et affecté à un widget par les instructions :
QHBoxLayout *layout = new QHBoxLayout(); // Par exemple
leWidget->setLayout(layout);
mais, les constructeurs d'un Qlayout peuvent prendre un QWidget *parent comme en argument, ce qui est équivalent à affecter ce layout au Qwidget. Les deux instructions précédentes peuvent donc être remplacées par l'instruction unique suivante:
QHBoxLayout *layout = new QHBoxLayout(leWidget);
Méthodes de QLayout :
addLayout(QLayout *)
addwidget(QWidget *)
addStetch() //ajout zone vierge d'étirement
setSizeConstraint() // Cf ci-après :
/* enum SizeConstraint { SetDefaultConstraint, SetFixedSize,
SetMinimumSize, SetMaximumSize,
SetMinAndMaxSize, SetNoConstraint } */
Un QWidget peut inclure plusieurs QLayout mais il ne peut avoir qu'un seul QLayout enfant. S'il en contient plusieurs (pour l'agencement des widgets internes), les autres seront sans parent, ce qui fait qu'ils ne seront pas automatiquement détruits quand le widget sera détruit. Pour que this soit leur parent en tant que QObject, on peut utiliser des QscopedPointer présentés dans la section sur l'héritage des QObjects. Par exemple :
QScopedPointer<QVBoxLayout> scP(new QVBoxLayout());
scP.take();
scP->addWigdet(new QLabel("hello"),this));
setlayout(scP.data());
Ou bien de la manière suivante (plus claire à mon avis) :
QScopedPointer<QVBoxLayout> scP(new QVBoxLayout());
QVBoxLayout *const vlayout = scP.take();
vlayout->addWigdet(new QLabel("hello"),this));
setlayout(vlayout);
Exemple de QGridLayout :
#include <QApplication>
#include <QtCore>
#include <QtGui>
#include <QPushButton>
#include <QGridLayout>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// Grid layout with 3 buttons
QGridLayout *gridLayout = new QGridLayout;
QPushButton *b1 = new QPushButton("A");
QPushButton *b2 = new QPushButton("B");
QPushButton *b3 = new QPushButton("C");
QPushButton *b4 = new QPushButton("D");
QPushButton *b5 = new QPushButton("E");
QPushButton *b6 = new QPushButton("F");
// addWidget(*Widget, row, column, rowspan, colspan)
// 0th row
gridLayout->addWidget(b1,0,0,1,1);
gridLayout->addWidget(b2,0,1,1,1);
gridLayout->addWidget(b3,0,2,1,1);
// 1st row
gridLayout->addWidget(b4,1,0,1,1);
// 2nd row with 2-column span
gridLayout->addWidget(b5,2,0,1,2);
// 3rd row with 3-column span
gridLayout->addWidget(b6,3,0,1,3);
// Create a widget
QWidget *w = new QWidget();
// Set the grid layout as a main layout
w->setLayout(gridLayout);
// Window title
w->setWindowTitle("Grid Layouts (3x4)");
// Display
w->show();
// Event loop
return app.exec();
}
Dérivant de QFrame permet de créer une window de 1er niveau si elle n'a pas de parent.
Constructeurs :
QLabel(const QString &text, QWidget *parent=0, Qt::WindowFlags f=0)
Le caractère précédé d'un &, par exemple &Save permet de donner le focus au label en tapant le raccourci associé à la lettre qui suit l'&, ici CTRL+S.
Méthodes :
setAlignment(Qt::Alignment)
Qt::AlignLeft
Qt::AlignRight
Qt::AlignHCenter
Qt::AlignJustify
Qt::AlignTop
Qt::AlignBottom
Qt::AlignVCenter
Qt::AlignCenter = Qt::AlignHCenter | Qt::AlignVCenter
setIndent(int)// Fixe la marge gauche
setBuddy(QWidget *w) // Transfère le focus à w qd le raccourci le label est activé
setText (const QString &)
Signaux :
cursorPositionChanged (int old, int new)
editingFinished()
returnPressed()
selectionChanged()
textChanged(const QString &text)
textEdited(const QString &text)
Méthodes :
QString text() // renvoi le texte contenu
setValidator(const QValidator *v) // Permet de filtrer les entrées
bool hasAcceptableInput() // true Si l'entrée est valide
Méthodes :
bool isChecked()
setText(const QString & text)
QString text()
Constructeurs :
QPushButton(QWidget *parent=0)
QPushButton(const QString &text, QWidget *parent=0)
QPushButton(const QIcon &icon, const QString &text, QWidget *parent=0)
Signaux :
clicked()
pressed()
released()
toggled(bool)
Constructeurs :
QCheckBox(QWidget *parent=0)
QCheckBox(const QString &text, QWidget *parent=0)
Signal :
stateChanged(int)
Constructeurs :
QSpinBox (QWidget *parent=0)
Méthodes :
setRange(int min, int max)
Signaux :
valueChanged(int), valueChanged(QString &).
Slots :
setValue(int)
Constructeurs :
QSlider (Qt::Orientation orientation, QWidget *parent=0)
Méthodes :
setRange(int min, int max)
Signaux :
valueChanged(int), valueChanged(QString &)
Slots :
setValue(int)
Slots :
clear()//Efface le contenu de la liste
Méthodes :
addItem (const QString &text, const QVariant &userData=QVariant())
Nous avons déjà traité la QMainWindow.
Elle hérite de QDialog et possède des méthodes statiques (appelables directement sous la forme QMessageBox::methode(...) très pratiques :
QMessageBox::about(parent, title, text)
StandardButton critical(parent, title, text,
StandardButtons buttons=Ok,
StandardButton defaultButton=NoButton)
StandardButton information(parent, title, text,
StandardButtons buttons=Ok,
StandardButton defaultButton=NoButton)
StandardButton question(parent, title, text,
StandardButtons buttons=Ok,
StandardButton defaultButton=NoButton)
StandardButton warning(parent, title, text,
StandardButtons buttons=Ok,
StandardButton defaultButton=NoButton)
Toutes ces méthodes renvoient pour résultat une valeur StandardButton (celui qui a été appuyé) dont la liste est donnée ci-après. En argument, absent par défaut 1 seul bouton Ok. Si on en met 2, le 2ème sera pris comme bouton par défaut (sélectionné par Return), et on peut mettre plus de 2 boutons en ajoutant des arguments.
Les valeurs StandardButton renvoyées par la boite et utilisées en argument sont les suivantes:
QMessageBox::Ok
QMessageBox::Open
QMessageBox::Save
QMessageBox::Cancel
QMessageBox::Close
QMessageBox::Discard
QMessageBox::Apply
QMessageBox::Reset
QMessageBox::RestoreDefault
QMessageBox::Help
QMessageBox::SaveAll
QMessageBox::Yes
QMessageBox::YesToAll
QMessageBox::No
QMessageBox::NoToAll
QMessageBox::Abort
QMessageBox::Retry
QMessageBox::Ignore
QmessageBox::NoButton
Ouverture en MODELESS : Un dialogue est activé en mode non-modal par void dialog->show() qui retourne immédiatement.
Ouverture en MODAL : Un dialogue ouvert en mode modal accapare les entrées de tous ses parents jusqu'à sa terminaison. On peut l'ouvrir en mode modal par void dialog->open(), mais le plus simple pour l'ouvrir en modal, c'est par la méthode int dialog->exec() qui renverra le résultat (Accepted=1, Rejected=0) à la fin de son exécution qui se produira produira lors de l'appel d'une des fonctions suivantes :
Slots :
accept() // ferme le modal-dialog et met le résultat à Accepted
done(int r) // ferme le dialog et met le résultat à r.
reject() // ferme le dialog et met le résultat à Rejected
Généralement un dialogue est crée une fois pour toutes, puis activé (en modal ou en modeless) une ou plusieurs fois par l'appel d'un des slots show(), open(), exec(). S'il y a une réinitialisation à faire à chaque ré-activation, elle doit être appelée depuis ces slots.
Pour faire patienter pendant une boucle durant longtemp, un dialogue modal est très pratique. On le définit avant la boucle, on appelle l'avancement dans la boucle, et on peut même faire un abort sur intervention de l'usager, et après la boucle un appel avec la valeur finale fait disparaître le dialogue.
QProgressDialog progress("Copying files...", "Abort Copy", 0, numFiles, this);
progress.setWindowModality(Qt::WindowModal);
for (int i = 0; i < numFiles; i++)
{
progress.setValue(i);
if (progress.wasCanceled()) break;
//... copy one file
}
progress.setValue(numFiles);
Avant la boucle un appel de :
progress.setMinimumDuration(100);
retarde l'apparition du progress dialogue de 100 ms, ce qui fait que si la boucle est plus courte que 100 ms, il ne sera pas affiché (ce qui aurait été inutile).
Très utiles pour lire des données simples au moyen de routines statiques déjà prêtes :
double QinputDialog::getDouble(QWidget *parent, const QString &title, const QString &label, double value=0, double min=-2147483647, double max=2147483647, int decimals=1, bool *ok=0, Qt::WindowFlags flags=0);
Si Ok est présent et non null, il reçoit en retour true si Ok a été pressé et false si Cancel a été pressé.
Exemple :
bool ok;
double d = QInputDialog::getDouble(this, tr("Titre"), tr("Valeur:"), 37.56, -10000, 10000, 2, &ok);
if (ok) doubleLabel->setText(QString("$%1").arg(d));
int QinputDialog::getInt(QWidget *parent, const QString &title, const QString &label, int value=0, int min=-2147483647, int max=2147483647, int step=1, bool *ok=0, Qt::WindowFlags flags=0);
Exemple :
bool ok;
int i = QInputDialog::getInt(this, tr("Titre"), tr("Pourcentage:"), 25, 0, 100, 1, &ok);
if (ok) integerLabel->setText(tr("%1%").arg(i));
Qstring QinputDialog::getItem(QWidget *parent, const QString &title, const QString &label, const QStringList &items, int current=0, bool editable=true, bool *ok=0, Qt::WindowFlags flags=0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone);
Exemple :
QStringList items;
items << tr("Spring") << tr("Summer") << tr("Fall") << tr("Winter");
bool ok;
QString item = QInputDialog::getItem(this, tr("Titre"), tr("Season:"), items, 0, false, &ok);
if (ok &&!item.isEmpty()) itemLabel->setText(item);
Qstring QinputDialog::getMultiLineText(QWidget *parent, const QString &title, const QString &label, const QString &text=QString(), bool *ok= 0, Qt::WindowFlags flags= 0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone)
Exemple :
bool ok;
QString text = QInputDialog::getMultiLineText(this, tr("Titre"), tr("Address:"), "John Doe\nFreedom Street", &ok);
if (ok && !text.isEmpty()) multiLineTextLabel->setText(text);
Qstring QinputDialog::getText(QWidget *parent, const QString &title, const QString &label, QlineEdit::EchoMode mode= QLineEdit::Normal, const QString &text= QString(), bool *ok=0, Qt::WindowFlags flags=0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone)
Exemple :
bool ok;
QString text = QInputDialog::getText(this, tr("Titre"), tr("User name:"), QLineEdit::Normal, QDir::home().dirName(), &ok);
if (ok && !text.isEmpty()) textLabel->setText(text);
Elle hérite de QDialog et possède des méthodes statiques très pratiques :
QString QFileDialog::getExistingDirectory (
QWidget *parent = 0,
const QString &caption = QString(),
const QString &dir = QString(),
Options options = ShowDirsOnly)
QString QFileDialog::getOpenFileName (
QWidget *parent = 0,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = 0,
Options options = 0)
QStringList getOpenFileNames (
QWidget *parent = 0,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = 0,
Options options = 0)
QString getSaveFileName (
QWidget *parent = 0,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = 0,
Options options = 0)
Les options sont les suivantes :
QFileDialog::ShowDirsOnly (pour getExistingDirectory uniquement)
QFileDialog::DontResolveSymlink
QFileDialog::DontConfirmOverwrite
QFileDialog::ReadOnly
QFileDialog::HideNameFilterDetails
et selectedFilter est par exemple :
tr("Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)")
Le QString q renvoyé (ou QStringList dans le cas multiple) est valide si !q.isEmpty()
Couleurs : On peut préciser des couleurs quelconques avec les deux typedef suivants :
qRgb(int,int,int) et qRgba(int,int,int,int)
Un style regroupe un ensemble de paramètres décrivant l'apparence de l'application et de widgets. On peut modifier le style de l'ensemble de l'application en utilisant :
QApplication::setStyleSheet(),
ou d'un widget spécifique (ou de plusieurs) et de tous ses (leurs) widgets descendants en utilisant par exemple :
QLineEdit, QPushButton { color: red; background-color: white} ,
ou d'une instance particulière de widget par exemple :
monwidget→setStyleSheet("background-color:yellow;color:rgb(255,128,64);")
Voici quelques estyles supportés :
alternate-background-color, background-color, color (pour le texte), font, …
QPalette : L'aspect et certaines couleurs du QWidget sont fixées par sa QPalette qui a deux principaux attributs, le colorGroup et le colorRole.
•L'attribut colorGroup mémorise les 3 états d'un QWidget :
◦Qpalette::Active ou Qpalette::Normal(couleurs utilisées si la fenêtre est active),
◦QPalette::Inactive (couleurs utilisées si la fenêtre est inactive).
◦QPalette::Disabled (couleurs utilisées si la fenêtre est disablée, active ou non),
•L'attribut ColorRole spécifie des couleurs symboliques utilisées pour les différents élément colorés d'un widget. ces éléments sont spécifiés par le rôle qu'ils jouent dans le widget. Les principaux rôles sont (dans l'ordre de l'enum QPalette::ColorRole) :
◦QPalette::WindowText ou QPalette::Foreground (couleur des textes noir en général)
◦QPalette::Button (couleur de fond des boutons)
◦QPalette::Light (plus clair que Button)
◦QPalette::Midlight (entre Button et Light)
◦QPalette::Dark (plus sombre que Button)
◦QPalette::Mid (entre Button et Dark)
◦QPalette::Text (couleur des traits des textes en général)
◦QPalette::BrightText (idem, mais plus contrasté)
◦QPalette::ButtonText ( couleur des traits des textes des boutons)
◦QPalette::Base (couleur de fond blanc en général)
◦QPalette::Windows ou QPalette::Background (couleur de fond gris clair en général)
◦QPalette::Shadow (très foncé, généralement c'est Qt::black)
◦QPalette::Highlight (couleur de fond d'un item sélectionné, généralement c'est Qt::darkBlue)
◦QPalette::HighlightedText (couleur du texte qui contraste avec Highlight, généralement c'est Qt::white)
◦QPalette::Link (pour un lien pas encore visité, généralement c'est Qt::blue)
◦QPalette::LinkVisited (pour un lien déjà visité, généralement c'est Qt::magenta)
On peut spécifier des aspects particuliers à la Palette pour ces différents états. Pour spécifier une couleur de fond particulière, on fera :
setAutoFillBackground(true); // On demande le remplissage automatique du fond
setBackgroundRole(QPalette::Base); // On spécifie un fond ordinaire
Pour modifier cette couleur on peut faire :
QPalette pal = palette();
pal.setColor(QPalette::Base, Qt::red); // On spécifie la couleur de ce fond ordinaire
this→setPalette(pal);
Trouvé sur internet :
QPalette pal = widget.palette();
//couleur de fond de l'application
pal.setColor(QPalette::Window, Qt::black);
// Couleur du texte
pal.setColor(QPalette::WindowText, Qt::white);
widget.setPalette(pal);
// applique le style
app.setStyle(new MyStyle);
On dessine dans un Qwidget en redéfinissant la fonction protected :
void paintEvent(QPaintEvent *);
Cette fonction est automatiquement appelée dès qu'il faut redessiner le contenu du Qwidget.
Tous les tracés se font à l'aide d'un objet Qpainter qui est créé comme ceci :
QPainter painter(this);
On trace ensuite avec cet objet. Exemples :
painter.drawLine(5,10,295,190);
painter.drawPoint(150,100);
painter.drawText(rectf, Qt::AlignCenter,"Bonjour");
L'origine des coordonnées est le coin Top-Left, x vers la droite et y vers le bas.
Les tracés dépendent des caractéristiques des propriétés du QPainter et en particulier de son crayon (QPen) pour l'épaisseur et la couleur des traits , de sa brosse (QBrush) pour le motif et la couleur de remplissage des surfaces, de sa police (QFon) pour l'écriture des textes...
La couleur du QPen et de la QBrush peuvent être modifiées directement sans passer par leur inermédiaire à l'aide des fonctions painter.setPen(Qt::laCouleur) et painter.setBrush(Qt::laCouleur). Mais pour d'autres caractéristiques telles que l'épaisseur, il faut créer un nouvel élément (à partir de celui qui existe pour l'initialiser plus rapidement), le modifier et l'affecter au QPainter. Exemple :
QPen pen = painter.pen(); // On crée une copie du QPen en cours.
pen.setColor(Qt::red); // On modifie la couleur
pen.setWidth(10); // On modifie l'épaisseur
painter.setPen(pen); // On l'affecte au QPainter
Les formes comme les rectangles, ellipses (et cercles), arcs, etc sont spécifiées par le rectangle qui les contient. Il est spécifié par les coordonnées (x,y) de son coin Top-Left, puis par sa taille (w,h). Une portion angulaire est précisée par l'angle de départ a par rapport à l'axe 0x, et sa taille angulaire D, le tout en degrés décimaux.
On peut déplacer l'origine des coordonnées à l'aide de la méthode
painter.translate(dx,dy);
Le point Xo = dx, Yo = dy sera la nouvelle origine pour les arguments des primtives de trécé drawLine, drawText, etc...
On peut également tourner les axes du repère de coordonnées autour du point Xo,Yo (éventuellement translaté) par la méthode :
painter.rotate(angDeg);
La rotation est comptée en degrés dans le sens des aiguilles d'une montre !!
Comme translation et rotation ne sont pas commutatives, il faut effectuer les opérations inverses dans l'ordre inverse ( comme on ferme les parenthèses) si on veut retrouver le fonctionnement initial.
Pour dessiner des surfaces complexe on dispose de la classe QPainterPath. On crée une instance :
QPainterPath qpp; On y ajoute des contours à l'aide des méthodes moveTo, lineTo, arcTo (ix, iy, w,h,startAngle, tailleAngle),... Si les contours sont disjoints, ils sont automatiquement reliés, ce qui parfois ne correspond pas à ce qu'on aurait voulu, et en particulier pour les arcTo, il faut penser à se placer par un moveTo au début de l'arc, sinon un segment de droite sera ajouté pour y parvenir, éventuellement depuis l'origine des coordonnées, si c'est le début du contour. Le QPainterPath est automatiquement cloturé et l'intérieur est rempli par la couleur de la brosse. Si le contour se replie sur lui-même il est difficile de prévoir ce qui est intérieur et ce qui ne l'est pas.
Remarque : painter.drawArc et painter.arcTo prennent les mêmes arguments, mais dans le premier les angles sont des doubles en degrés et dans le second ses ints en 1/16 de degrés (multiplier les degrés par 16).
Pour provoquer par programmation le dessin du widget, c'est-à-dire l'appel de la fonction paintEvent() on dispose des fonctions :
leWidget->repaint() ; // appel direct de paintEvent
leWidget->update() ; // met un QpaintEvent dans la queue des évènements
La deuxième forme est préférable. De plus plusieurs QPaintEvents empilés se traduiront par un seul appel à la fonction paintEvent().
Si le Qwidget est caché ou si les updates sont interdits ces deux fonctions seront sans effet.
Les classes dérivées de QGLWidget permettent d'utiliser les fonctions de la bibliothèque openGL.
Il faut redéfinir les 3 fonctions protected suivantes :
protected:
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
Dans initializeGL() on place les initialisations, par exemple le choix de la couleur d'effacement :
glClearColor(0.,0.,0.,0.);
Avec resizeGL(int w, int h) on récupère les dimensions effectives du viewport openGL.
Dans paintGL() on met tous les ordres de tracé.
On produit un nouveau tracé en appelant updateGL() qui génèrera l'évènement qui produira l'appel de paintGL().
L'utilisation des facilités openGL, pour le widget créé, doivent se faire après (ou dans) l'appel de initializeGL(), et en particulier la génération des DisplayList. Si elles sont créées avant, elles ne sont pas dessinées par les appels de glCallList(..).
Dans le *.pro, il faudra ajouter les lignes suivantes :
QT += opengl
greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32
Pour QT_VERSION ≤ 5.4 la première ligne suffit à provoquer l'inclusion des bibliothèques.
Sous Ubuntu la deuxième ligne sera :
greaterThan(QT_VERSION, 5.5): LIBS += -lGLU
Le tout peut être écrit sous la forme :
win32 {
greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32
}
else {
greaterThan(QT_VERSION, 5.5): LIBS += -lGLU
}
Pour faire une animation dans une classe héritée d'un QWidget, il suffit de créer un QTimer dans son constructeur et de le connecter à un slot comme ceci :
timer = new QTimer(this);
timer->setInterval(25);
connect(timer, SIGNAL(timeout()), this, SLOT(timeOutSlot()));
time = 0; maxtime = 1000;
timer->start();
ce qui suppose que la ligne Q_OBJECT est placé au début de la déclaration de la classe dans le .h et qu'il y a les déclarations suivantes :
private:
QTimer *timer;
int time, maxtime;
private slots:
void timeOutSlot();
La routine slot étant par exemple la suivante :
void MyWidget::timeOutSlot()
{
update(); // Appel du paintEvent.
if (time++ > maxtime) timer.stop();
}
Pour placer un widget perso, par exemple MyWidget, dans une fenêtre graphique conçue avec QtDesigner, placer à sa place un QWidget de base avec le QtDesigner et lui donner un nom quelconque, par exempe Widget_bid, puis dans le constructeur cpp de la fenêtre graphique, créer le widget MyWidget en lui donnant comme père ui->Widget_bid, comme ceci :
MyWidget *myWidget = new MyWidget(ui→widgete2D);
myWidget→show(); // ne pas oublier !!
On dérive un thread de la classe QThread. Exemple de déclaration d'une classe Thread, qui émet un signal d'avance au fur et a mesure de l'avancement des calculs, un signal chaque fois qu'elle a atteint un certain résultat.
#include <QThread>
class Bidon;
class MyThread : public QThread
{
Q_OBJECT
public:
// Constructeur avec un arg pour récupérer des paramètres
MyThread(Bidon *arg, QObject *parent = 0);
// Pour démarrer le Thread
void run();
private:
Bidon *arg;
signals:
void avance(int i); // Pour signaler l'avance
void trouve(int i); // Autre signal d'avance
public slots:
};
MyThread::MyThread(Bidon *arg, QObject *parent) : QThread(parent)
{this->arg = arg;}
void MyThread::run()
{
int nbtrouv = 0;
for (int j = 0; j < arg->number; j++)
{
.. .. ..
emit avance(i);
if (arg→cancel) break; // cancel usager depuis l'appelant
.. .. ..
emit trouve(++nbtrouv);
.. .. ..
}
// Sortie du Thread
}
Ce thread est créé ailleurs dans son parent par :
MyThread *job = new MyThread(bidon, this);
connect(job, SIGNAL(avance(int)), this, SLOT(aff_avance(int)));
connect(job, SIGNAL(trouve(int)), this,SLOT(aff_trouve(int)));
connect(job, SIGNAL(finished()), this, SLOT(aff_solution()));
C'est par l'appel du slot aff_solution() qu'on sait que le thread a terminé son travail.
Il est activé, par exemple à la demande de l'usager par :
if(!job→isRunning()) job→start();
pour éviter de l'activer s'il est déjà en cours.
Pour faire dormir le thread principal (non conseillé) pendant 10 ms, on peut faire :
QThread *t = QApplication::instance()->thread();
t->usleep(10000);
Classe permettant de voir ses objets sous différents types (comme une union en Fortran).
Constructeurs : Quantité de constructeurs avec beaucoup de types de bases.
Exemples de méthodes :
isValid()
bool canConvert(Type t) // vrai si la convesion vers le type t est possible
...
int toInt() // Conversion vers un entier
double toDouble()
QString toString()
....
Utilisée pour préciser la taille d'objets 2D.
Constructeurs :
QSize::QSize()
QSize::QSize(int width, int height)
Méthodes :
setHeight(int), setWidth(int),
int height(), int width(),
int &rheight(), int &rwidth() // référence qui permet de modifier les valeurs.
Constructeurs :
QPoint ()
QPoint ( int x, int y )
Méthodes :
bool isNull () const
int manhattanLength () const
int &rx ()
int &ry ()
void setX ( int x )
void setY ( int y )
int x () const
int y () const
QPoint & operator*= ( qreal factor )
QPoint & operator+= ( const QPoint & point )
QPoint & operator-= ( const QPoint & point )
QPoint & operator/= ( qreal divisor )
Constructeurs :
QPointF ()
QPointF ( const QPoint & point )
QPointF ( qreal x, qreal y )
Méthodes :
bool isNull () const
qreal & rx ()
qreal & ry ()
void setX ( qreal x )
void setY ( qreal y )
QPoint toPoint () const
qreal x () const
qreal y () const
QPointF & operator*= ( qreal factor )
QPointF & operator+= ( const QPointF & point )
QPointF & operator-= ( const QPointF & point )
QPointF & operator/= ( qreal divisor )
Méthodes :
int removeAll(const T &value) // Enleve les éléments == à value
append(const T &value)
append(const QList<T> &value)
prepend(const T &value)
Itérateur sur une liste qui ne sera pas modifiée. Si on désire modifier la liste utiliser un QmutableListIterator.
Constructeur :
QListIterator (const QList<T> &list)
Méthodes :
bool findNext( const T & value)
bool findPrevious( const T & value)
bool hasNext() const
bool hasPrevious() const
const T &next ()
const T &peekNext () const
const T &peekPrevious () const
const T &previous ()
void toBack()
void toFront()
QListIterator &operator= (const QList<T> &list)
Itérateur sur une liste que l'on va modifier. Si on ne fait que parcourir la liste, utiliser plutot un QlistIterator.
Constructeur :
QMutableListIterator(QList<T> &list)
Méthodes : idem QListIterator plus les suivantes:
void insert(const T &value)
void remove()
void setValue(const T &value) const
const T &value() const
Encapsule un caractère unicode
Méthodes :
ushort &QChar::unicode () // La valeur numérique du caractère
char toAscii() const // convertit en char
Encapsule un string de caractères 16 bits unicode.
Constructions :
QString str = "Hello";
QString str("Hello");
QString *str = new QString("Hello");
Méthodes :
QCharRef QString::operator[] (int index) // permet de faire str[1] = QChar('a');
int size() // Nb de caractères.
bool isEmpty()
QString arg(const QString &a,int fieldWidth=0,const QChar &fillChar=QLatin1Char(' ')) const
Dans le QString this remplace %1 par a avec un minimum de fieldWidth caractères avec si length(a)<fieldWidth ajout de caractères fillChar.
Ex :
tr("Nom %1, Prénom %2").arg("Dupont").arg("Jean");
QString mid(int debut, int n=-1) const
Extraction d'une sous-chaine commençant à debut, de n caractères (ou tous si -1)
QString left(int n) const // Renvoi les n 1er caractères
QString right(int n) const // Renvoi les 5 dernier caractères
QString toUpper () cons
.....
int toInt(bool *ok=0, int bas=10) const
....
Pour convertir un QString en char* , le convertir d'abord en QByteArray par les méthodes toLocal8bit(), toAscii() ou toUtf8(), ... et ensuite convertir le QByteArray en char* par les méthodes constData() ou data(), soit par exemple : qstring.toAscii().data().
Conversion de nombre en string : La classe QString offre plusieurs méthodes pour convertir des nombre en string, dont setnum, arg et number.
Méthodes statiques pour conversion nombres entiers, doubles etc. :
QString QString::number(long n, int base = 10)// convertit l'entier n en string
QString QString::number(double v, char [static] format = 'g', int precision =6 )
Equivalent à Qlist<QString>
Classe mère de QIntValidator, QDoubleValidator et QregExpValidator utilisée trés simplement par Linedit par exemple. On lui affecte un QValidator (voir création plus loin) qui se chargera de vérifier la validité des entrées :
LineEdit::setValidator(QValidator *);
et ensuite on utilisable la méthode suivante :
LineEdit::hasAcceptableInput();
pour voir si l'entrée est conforme au QValidator.
Méthodes génériques aux QValidator :
virtual State validate (QString & input, int & pos ) const = 0
pour interroger sur la validité d'un QString &input. Renvoi :
Invalid : si l'entree est incorrecte
Acceptable : si elle est valide
Intermediate : partiellement valide
pos n'est inutilisé pour QRegExpValidator où pour une entrée incorrecte il renvoi le nombre de caractères d'input (utilité ???).
Hérite de QValidator
QIntValidor(QObject *parent) //Accepte tout entier
QIntValidator (int min, int max, QObject *parent)// min <= int <= max
Hérite de QValidator
QDoubleValidator (QObject *parent ); // acceptable tout double
QDoubleValidator(double bot, double top, int ndec, QObject *parent)
// accepte un bot<=double<=top ave au plus ndec décimales après le point
Hérite de QValidator
QRegExpValidator(const QRegExp &rx, QObject *parent )
Mère de QcloseEvent, QmouseEvent,...
accept() //l'évènement est propagé
ignore() //l'évènement est supprimé
Evènement généré lorsque l'usager veut fermer le widget (X par exemple)
Evènement généré par l'appui sur une touche du clavier, traité par le slot :
void keyPressEvent(QKeyEvent *);
On accède à la touche et aux shift, Ctrl etc par :
int key = event->key();
bool majuscule = (Qt::SHIFT == event->modifiers());
etc. Pour qu'un widget reçoive les évènements du clavier (keyboard), il faut les autoriser par :
setFocusPolicy(Qt::StrongFocus);
à mettre dans son contructeur (il y a d'autres options que Qt::StrongFocus).
Généré par un clic, un move souris ou un clic-move.
Traité par les slots suivants :
void mousePressEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
void mouseMoveEvent(QMouseEvent *);
Méthodes :
Qt::MouseButton button() const // méthode get qui renvoi :
Qt::NoButton
Qt::LeftButton
Qt::RightButton
Qt::MidButton
Qt::XButton1
Qt::XButton2
Qt::MouseButtons buttons() const // renvoi un OR des précédents
const QPoint& globalPos() const
int globalX() const
int globalY() const
const QPoint &pos() const
QPointF posF() const
int x() const
int y() const
Interface pour accéder à un fichier en lecture ou écriture
Méthodes statiques :
bool QFile::copy(const QString &fileName, const QString &newName)
bool QFile::exists(const QString &fileName)
bool QFile::remove(const QString &fileName)
bool QFile::rename(const QString &oldName, const QString &newName)
bool QFile::resize(const QString &fileName, qint64 sz)
....... etc
QDir QDir::Current() retourne le répertoire courant sous forme de QDir.
QString QDir::CurrentPath() retourne le répertoire courant sous forme de String.
QFileInfoList QDir::drives() renvoie la liste des racines
QDir QDir::home() renvoie le répertoire home de l'usager sous forme de QDir
et pour avoir le nom de l'usager :
QString QDir::home().dirName() renvoie le nom de l'usager
Remarque :
QDir::currentPath()==QDir::current().absolutePath()==QDir::current().path();
Récupérer le FILE pour les routines classiques C :
Mauvais exemple :
void foo(QString filename)
{
QFile qf(filename);
qf.open(QIODevice::ReadOnly);
int fd = qf.handle();
FILE* f = fdopen(fd, "rb");
// Faire son travail avec f
fclose(f); // !!! undefined behaviour !!!
}
Il faut détruire l'objet QFile avant le fclose. Si fclose(f) est appelé avant que l'objet QFile soit détruit, il y a : QTBUG-20372.
Si le Qfile est détruit (par ex si local à une routine utilisée pour l'ouverture) avant les lectures, celles-ci échoueront.
Pour éviter ces écueils, il faut dupliquer le descripteur de fichier retourné par QFile::handle():
Bon exemple :
void foo(QString filename)
{
QFile qf(filename);
qf.open(QIODevice::ReadOnly);
int fd = qf.handle();
FILE* f = fdopen(dup(fd), "rb"); // !!! use dup()
// Faire son travail avec f
fclose(f); // correct
}
Qt permet d'intégrer des fichiers quelconques, par exemple data/tata.txt, dcin/titi.jpg et pict/toto.jpg dans le code C++ qui seront accessible au moyen de QFILE comme les fichiers normaux. Pour cela, il faut écrire un fichier XML avec l'extension .qrc, qui nous appellerons, par exemple, myres.qrc, qui précise comment accéder à ces fichiers. Supposons que toto.txt se situe dans
<RCC>
<qresource prefix="/blabla">
<file alias="readme">data/tata.txt</file>
</qresource>
<qresource prefix="/images">
<file alias="bird">dcin/titi.jpg</file>
<file alias="gars">pict/toto.jpg</file>
</qresource>
</RCC>
et ajouter ce fichier au fichier projet .pro de Qt avec l'instruction :
RESOURCES += myres.qrc
Les fichiers seront accessibles par :
QFile *file_readme = new QFile(":/blabla/readme",this);
QFile *file_bird = new QFile(":/images/bird",this);
QFile *file_gars = new QFile(":/images/gars",this);
Les attributs prefix et alias sont optionnels :
•prefix permet d'ajouter un pseudo nom de répertoire, par exemple pour identifier un groupe de types, ici blabla pour des textes et images pour des images.
•alias permet de donner un nouveau nom au chemin d'accès au fichier, ici readme, bird et gars.
On peut se passer de ces attributts et mettre simplement :
<RCC>
<qresource>
<file>data/tata.txt</file>
<file>dcin/titi.jpg</file>
<file>pict/toto.jpg</file>
</qresource>
</RCC>
et accéder aux fichiers par :
QFile *file_readme = new QFile(":/data/tata.txt",this);
QFile *file_bird = new QFile(":/images/bird",this);
QFile *file_gars = new QFile(":/images/gars",this);
Remarque : Il semble que le / après le : ne soit pas significatif.
Exemple de lecture d'un fichier de type dictionnaire :
QFile filein(":frdico/dicofr",this);
if (!filein.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::warning(this,"ERREUR OPEN","dicofr absent");
return;
}
QTextStream in(&filein);
QString line;
while (true)
{
line = in.readLine();
if(line.isNull()) break;
dico.append(line);
}
filein.close();
Les routines C standard d'accès aux fichiers ne peuvent accéder aux fichiers resources Qt. Pour leur permettre cet accès le plus simple est de copier la resource dans un autre fichier. Exemple :
QFile::copy(":frdico/dicofr","tempo.txt");
et pour empêcher la compression (en cas de complication ?) du fichier resource on ajoutera au *.pro la ligne suivante :
QMAKE_RESOURCE_FLAGS += -no-compress
Attention : QFile::copy crée un fichier en lecture seule qu'il est ensuite diffficile à détruire. Pour le rendre accessible en lecture et écriture pour tous on fera :
QFile::setPermissions("tempo.txt", (QFileDevice::Permission) 0x6666);
Une autre solution est de programmer la copie: par exmple :
QResource *qr = new QResource(":frdico/dicofr");
QFile qftempo("tempo.txt");
if (!qftempo.open(QIODevice::WriteOnly))
{QMessageBox::warning(0,"ERREUR OPEN","Echec ouverture tempo.txt"); return 1;}
qftempo.write((const char *) qr->data(), qr->size()) ; qftempo.close();
Informations sur un fichier
Constructeurs :
QFileInfo()
QFileInfo(const QString &file)
QFileInfo(const QFile &file)
QFileInfo(const QDir &dir, const QString &file)
QFileInfo(const QFileInfo & fileinfo)
Méthodes :
Soit le fichier C:/rep/dir/truc.tar.gz alors :
QString absoluteFilePath() const C:/rep/dir/truc.tar.gz
QString filePath() const C:/rep/dir/truc.tar.gz
QString absolutePath() const C:/rep/dir
QString path() const C:/rep/dir
QString fileName() const truc.tar.gz
QString baseName() const truc
QString completeBaseName() const truc.tar
QString completeSuffix() const tar.gz
QString suffix() const gz
QDir absoluteDir() const
QDir dir() const
bool exists() const
bool isDir() const
bool isExecutable() const
bool isFile() const
bool isHidden() const
bool isWritable() const
qint64 size() const
..... etc
Dans Qt l'architecture MVC (modèle-vue-controleur) est réduite à deux éléments : le modèle et le controleur+vue.
Qt offre 3 classes controleur+vue :
•QlistView : Simple colonne.
•QTableView : Tableau d'éléments,
•QTreeView : Arbre d'éléments.
Les classes modèles héritent de QAbstractItemModel. Par exemple les 4 classes suivantes :
•QStringListModel : modèle composé de QStringList (simple liste de chaines).
•QStandardItemModel : modèle composé QStandardItem organisés en :
•simple colonne a plusieurs lignes (liste model)
•une matrice (table model)
•un Arbre (tree model)
•QDirModel : Arborescence des fichiers de l'ordinateur,
•QSqlQueryModel, QSqlTableModel et QSqlRelationalTableModel : données issues d'une base de données
Un modèle est connecté à un controleur de vue par la méthode vue->setModel(modele).
Un item est affecté à un modèle par diverses méthode dont setItem :
model->setItem(3,1, new QStandardItem("hello")); // cas d'un QStandardItemModel
On ajoute une ligne à un QStandardItemModel ou une sous-ligne à un QStandardItem par la méthode appendRow :
QStandardItem *item = new QStandardItem("Sylvie");
model->appendRow(item);
item->appendRow(new QStandardItem("blonde"));
On peut sélectionner un item d'un modèle par la méthode index :
i = model->index("toto");
La vue commence à sa racine qui est l'élément racine du modèle (le premier) ou un élément quelconque. On fait commencer la vue à un item particulier par la méthode setRootIndex :
vue->setRootIndex(model->index("toto");
Un modele peut être affiché par n'importe laquelle des 3 vues, mais certaines sont mieux adaptées que d'autre. Chaque vue ne montre que les éléments qu'elle sait afficher.
Exemple :
Wppale::Wppale()
{
QVBoxLayout *layout = new QVBoxLayout;
QStringList listePays;
listePays << "France" << "Espagne" << "Italie" << "Portugal";
QStringListModel *modele = new QStringListModel(listePays);
QListView *vue = new QListView;
vue->setModel(modele);
layout->addWidget(vue);
setLayout(layout);
}
Pour supprimer l'en-tête :
vue->header()->hide();
La sélection d'un élément se fait par un QItemSelectioModel. Par exemple, en réponse à un SIGNAL clicked(), on connectera le SLOT suivant :
QItemSelectionModel *selmodel = vue->selectionModel();
QModelIndex ixSel = selmodel->currentIndex();
QVariant selvar = modele->data(ixSel, Qt::DisplayRole);
QMessageBox::information(this, "Elément sélectionné", selvar.toString());
}
La méthode selectionModel d'une vue renvoie un QItemSelectionModel qui contient des infos sur la partie sélectionnée. On récupère son index sous forme d'un QModelIndex, puis l'élément sélectionné sous forme d'un QVariant par la méthode data du modèle, grace au paramètre Qt::DisplayRole. Puis il ne reste qu'à l'afficher sous la forme adéquate.
Pour qu'une vue autorise la sélection multiple, il faut l'indiquer , par la méthode selectionMode :
vue->setSelectionMode(QAbstractItemView::ExtendedSelection);
puis pour récupérer les indices des éléments sélectionnés, on fera par exemple :
QItemSelectionModel * selmodel = vue->selectionModel();
QModelIndexList lixSel = selmodel->selectedIndexes();
QString sels;
for (int i = 0 ; i < lixSel.size() ; i++)
{
QVariant selvar = modele->data(lixSel[i], Qt::DisplayRole);
sels += selvar.toString() + "<br />";
}
QMessageBox::information(this, "Eléments sélectionnés", sels);
Ainsi, au lieu d'appeler currentIndex(), on utilise selectedIndexes().
Elle fournit un moyen de sortie vers la carte son. Il y a deux modes d'utilisation le mode pull et le mode push. Le plus simple étant a priori le premier.
En pullmode, il faut redéfinir la fonction virtuelle qint64 QIODevice::readData(char *data, qint64 maxlen), par exemple en faisant hériter notre source de son la classe QIODevice. Cette fonction se comporte alors comme une callback, ou nous devons remplir le buffer "data" de "len" octets provenant de la source.
Notre source sera par exemple un objet de la classe SourceIO qui hérite de la classe QIODevice qui devra surcharger la fonction readData(char *data, qint64 maxlen).
Ensuite, il faudra appeler dans l'ordre :
sourceio.open(QIODevice::ReadOnly);
audioOutput->start(sourceio);
Le reste est automatique, dans la mesure ou notre fonction de rappel readData est bien écrite.
Pour lire un fichier wav, le fichier sous forme QFile dérivant d'un QIODevice, il n'y a rien à faire que l'ouvrir et le passer en argument de audioOutput->start(sourceio);
Il me semble plus compliqué, avec a priori l'utilisation d'un Timer.