Académique Documents
Professionnel Documents
Culture Documents
gtkmm
Introduction
Vous ne connaissez pas du tout gtkmm ?
Vous ne savez même pas ce qu'est un widget ?
Pas de problème, car c'est ici que ça commence !
Dans ce chapitre, vous découvrirez GTK+ et gtkmm, vous apprendrez ce que sont les widgets et,
par-dessus tout, vous compilerez votre première application utilisant gtkmm !
Présentation de GTK+
Présentation générale
GTK+ est un ensemble de bibliothèques permettant de créer des interfaces graphiques, donc des
applications fenêtrées, en C.
Au début, GTK+ avait été créé pour le logiciel GIMP, d'où son nom d'ailleurs .
GTK+ est libre, ce qui vous permet de créer des logiciels propriétaires sans contrainte.
De plus, GTK+ est portable ; il fonctionne sur la plupart des systèmes d'exploitation : GNU/Linux,
Mac OS X et Windows.
Présentation technique
GTK+ utilise la programmation orientée objet même si elle est programmée en C.
Elle est utilisable dans de nombreux autres langages tels C++, PHP, Java et Python pour ne citer que
ceux-ci.
Pour vous convaincre d'utiliser cette bibliothèque, sachez que GTK+ est utilisée par de nombreux
projets de qualité comme GIMP (bien sûr ) et le fameux gestionnaire de bureau GNOME.
Étant donné que ces projets sérieux utilisent GTK+ et qu'elle existe depuis plus d'une décennie,
vous pouvez être sûr que cette bibliothèque est de qualité.
Possibilités de GTK+
Cette bibliothèque vous permettra de créer des fenêtres avec des boutons, des images, des menus,
des barres d'outils, des barres d'état, des zones de texte, etc.
Elle gère également les événements, les threads et bien d'autres choses que nous verrons plus tard.
Les différentes bibliothèques de GTK+
GTK+ est basée sur plusieurs bibliothèques :
• GLib : la base de GTK+ (boucle d'événements, threads, système orienté objet, etc.) ;
• Pango : pour le texte (mise en forme, internationalisation) ;
• Cairo : pour le dessin 2D ;
• Glade : pour la création d'interfaces graphiques avec un logiciel ;
• ATK : pour l'accessibilité (lecteurs d'écran, loupes, ...) ;
• et quelques autres.
Il est certain que je vais vous parler des 4 premières.
Cependant, n'ayant jamais utilisé ATK, je ne pourrai pas vous en parler.
ATTENTION !
Bien qu'il existe un logiciel pour créer ses propres fenêtres, il est préférable de savoir en créer en C+
+ avant.
Présentation de gtkmm
Gtkmm est une surcouche de GTK+ pour le langage C++.
Elle permet de créer des interfaces graphiques en utilisant les mécanismes du C++ plutôt que ceux
du C.
Elle utilise, bien sûr, la programmation orientée objet (héritage, polymorphisme, etc.).
Il existe également les surcouches des autres bibliothèques présentées plus haut :
• glibmm ;
• libglademm ;
• etc.
Pourquoi utiliser gtkmm à la place de GTK+ ?
Premièrement, cela vous permet de programmer en C++ avec la programmation orientée objet.
Autre point important : le compilateur C++ détectera des erreurs non détectées en C.
De plus, vous pouvez utiliser l'héritage pour créer de nouveaux widgets (nous verrons dans la
prochaine sous-partie ce que c'est) à partir de ceux existants.
Enfin, le code à écrire en C++ est plus court qu'en C.
Pourquoi utiliser gtkmm à la place de Qt (la bibliothèque graphique expliquée dans le cours de
M@teo21) ?
Gtkmm est écrit dans un style plus proche du C++ que Qt.
Par exemple, Qt utilise la classe QVectorau lieustd::vector, classe présente dans la
bibliothèque standard.
Donc, si vous connaissez cette dernière, inutile d'en réapprendre une nouvelle.
De plus, gtkmm ne modifie pas le langage C++ comme le fait Qt avec ses signaux et ses slots.
Finalement, vous pouvez avoir une bonne intégration avec le bureau GNOME.
À quoi ressemble un programme utilisant gtkmm ?
Je vais vous montrer un programme qui affiche une fenêtre à l'écran :
#include <gtkmm.h>
Gtk::Window fenetre;
Gtk::Main::run(fenetre);
return 0;
Les widgets
La case à cocher
Une case à cocher est un bouton à deux états : activé (coché) et désactivé (décoché) :
La fenêtre
Je pense que vous savez très bien ce que c'est :
Le menu
Un menu est un widget présentant plusieurs éléments que l'utilisateur peut choisir :
La barre d'outils
La barre d'outils sert à rendre plus accessibles certains des éléments les plus utilisés du menu :
La barre d'état
La barre d'état affiche des informations que nous savons déjà :
Les zones de texte uniligne et multiligne
Les zones de texte permettent à l'utilisateur d'entrer du texte.
Voici une zone de texte uniligne :
La barre de progression
La barre de progression indique à l'utilisateur l'avancement d'une opération plus ou moins longue :
La barre d'onglets
La barre d'onglets permet d'afficher un seul widget à la fois, dans une fenêtre ayant une barre
d'onglets :
L'étiquette
Une étiquette affiche simplement du texte pour dire, par exemple, ce qu'il faut écrire dans une zone
de texte.
Elle sert donc à donner des informations utiles à l'utilisateur.
En voici une :
Installation de gtkmm
Sous Linux...
Je vais détailler l'installation sous Ubuntu ; pour les autres distributions, ce devrait être semblable.
Pour ceux qui veulent installer gtkmm depuis les sources, cliquez ici.
Donc, sous Ubuntu, si vous n'avez pas de compilateur C++ (ça ne devrait pas être le cas), lancez la
commande suivante :
sudo apt-get install g++
Sous Windows...
Gtkmm 3 (dernière version de gtkmm expliquée dans ce cours) n’est toujours pas sortie sous
Windows. Par conséquent, il se peut que certains codes ne compilent pas chez vous.
Il vous faudra donc patienter en attendant sa sortie.
Téléchargement
Veuillez télécharger gtkmm depuis le site officiel de GNOME.
Cliquez ici, puis choisissez la dernière version (2.22 à l'heure où j'écris ces lignes) et téléchargez le
fichier gtkmm-win32-devel-x.y.z.exe.
Installation
Double-cliquez sur le fichier téléchargé et installez le programme.
Pour ce faire, choisissez la langue et vous devriez arriver sur une fenêtre semblable à celle-ci :
Ensuite, cliquez sur Suivant, J'accepte, Suivant et vous devriez arriver sur une fenêtre vous
demandant dans quel dossier doit se trouver gtkmm.
Vous pouvez garder celui par défaut ou le modifier à votre guise.
Cliquez sur Suivant et vous devriez arriver sur une fenêtre ayant plusieurs cases à cocher :
Sauf si vous savez ce que vous faites, gardez toutes les cases cochées.
Cliquez sur Suivant et enfin sur Installer.
L'installation devrait durer quelques minutes :
À la fin de l'installation, cliquez sur Fermer :
Voilà, tout est installé, rendez-vous à la prochaine sous-partie pour tester votre installation toute
fraîche, toute belle.
Le premier programme que vous allez compiler affichera une fenêtre de 200px par 200px.
Super !
Commençons tout de suite.
Veuillez copier-coller le code source suivant (il sera détaillé au prochain chapitre) :
#include <gtkmm.h>
Gtk::Window fenetre;
Gtk::Main::run(fenetre);
return 0;
Sous Linux…
Sous linux, j'utilise la ligne de commande ; si ce n'est pas votre cas, vous devrez configurer votre
EDI.
Si vous utilisez Code::Blocks, allez voir la compilation sous Window et suivez ces instructions, car
elles devraient être semblables.
Rendez-vous au répertoire de votre fichier .cpp :
cd vers/dossier/de/mon/projet
Ensuite, il vous suffit d'entrer la commande suivante pour compiler votre projet (si votre fichier ne
s'appelle pas main.cpp, veuillez adapter la commande en conséquence, je vous fait confiance ):
g++ main.cpp -o Fenetre `pkg-config gtkmm-3.0 --cflags --libs`
ATTENTION !
Il faut utiliser les accents graves (`) et non de simples apostrophes (') autour de pkg-
config gtkmm-3.0 --cflags --libs pour que ce soit exécuté comme une commande.
De plus, si ça ne fonctionne pas, veuillez recopier cette ligne à la main, car il peut y avoir des
problèmes avec les tirets.
Pour ceux qui ne savent pas ce que fait cette commande, je vais la détailler un peu.
Après g++, il faut mettre les fichiers sources (.cpp) ; ici, il n'y en a qu'un : main.cpp.
Ensuite, après -o, il faut donner un nom à notre exécutable ; ici, je l'ai appelé Fenetre.
À la fin, c'est ce qu'il y a de nouveau :
`pkg-config gtkmm-3.0 --cflags --libs`
Après la compilation, exécutez votre premier programme (./Fenetre) et vous devriez obtenir, à
quelques détails près, ceci :
Votre première fenêtre !
project(Fenetre)
find_package(PkgConfig)
pkg_check_modules(GTKMM gtkmm-3.0)
if(NOT GTKMM_FOUND)
endif()
link_directories(
${GTKMM_LIBRARY_DIRS}
include_directories(
${GTKMM_INCLUDE_DIRS}
)
file(
GLOB_RECURSE
source_files
src/*
add_executable(
Fenetre
${source_files}
target_link_libraries(
Fenetre
${GTKMM_LIBRARIES}
Sous Windows…
Compilation
Je vais détailler la compilation pour Code::Blocks ; si vous utilisez un autre EDI, vous devrez
adapter les prochaines manipulations en conséquence.
Créez un nouveau projet C++ en console.
Copiez le code source que je vous ai donné plus haut et coller-le dans le fichier main.cpp.
Une fois que c'est fait, enregistrez et aller dans le menu Project->Properties…
Ensuite, allez dans l'onglet Build target.
À droite de Type:, choisissez GUI application dans la liste déroulante.
Validez.
Ensuite, allez dans le menu Project->Build options… et allez dans l'onglet Other options de la
deuxième barre d'onglets.
Dans la grande zone de texte, ajoutez la ligne suivante :
`pkg-config gtkmm-2.4 --cflags`
ATTENTION !
Il faut utiliser les accents graves (`) et non de simples apostrophes (') autour de pkg-
config gtkmm-2.4 --cflags pour que ce soit exécuté comme une commande.
De plus, si ça ne fonctionne pas, veuillez recopier cette ligne à la main, car il peut y avoir des
problèmes avec les tirets.
Et un dernier point :
si vous utilisez un Windows 64 bits, vous devez ajouter -m32 devant le premier accent grave :
-m32 `pkg-config gtkmm-2.4 --cflags`
(Merci à paul161 pour l'astuce.)
Enfin, allez dans l'onglet Linker settings et collez cette ligne dans la zone de texte intitulée Other
linker options :
`pkg-config gtkmm-2.4 --libs`
Pour ne pas avoir à refaire tout le temps les mêmes manipulations, vous pouvez enregistrer le projet
en tant que template.
Pour ce faire, cliquez sur le menu File->Save project as template…, entrez un nom (par exemple,
gtkmm) et validez.
Lorsque vous créerez un nouveau projet utilisant gtkmm, vous n'aurez qu'à cliquer sur l'icône New
file, choisir From template…, choisir votre nouveau template (gtkmm) et valider.
Distribution de votre application
Pour distribuer votre application à d'autres personnes, vous devrez leur fournir des dll.
Pour cela, votre application devra avoir une structure particulière (la même que GIMP).
Si votre programme est dans un dossier Fenetre, votre structure sera celle-ci :
• Fenetre\bin\
• Fenetre\etc\
• Fenetre\lib\
• Fenetre\share\
Votre exécutable devra se trouver dans le dossier bin\.
De plus, dans tous ces dossiers, il devra y avoir des dll et quelques autres fichiers.
Voici toutes les fichiers que vous devez ajouter (vous trouverez ces fichiers dans votre dossier
d'installation de gtkmm - ici, c'est C:\gtkmm\) :
• C:\gtkmm\redist\*.dll ;
• C:\gtkmm\etc\* ;
• C:\gtkmm\lib\gtk-2.0\2.10.0\* ;
• C:\gtkmm\share\themes\* ;
• C:\gtkmm\share\locale\*.
Lorsque j'écris *.dll, ça signifie que vous devez copier tous les fichiers ayant l'extension .dll.
Lorsque j'écris *, vous copier tout, y compris les dossiers et sous-dossiers.
Les fichiers du dossier redist\ devront se trouver dans le dossier Fenetre\bin\ de votre programme.
Les autres vont dans leur répertoire respectif (etc\ dans Fenetre\etc\, lib\ dans Fenetre\lib\, …).
Vous devez faire une telle structure pour chaque projet que vous voulez distribuer, car vos clients
n'ont probablement pas installé gtkmm.
Vous êtes maintenant prêtes et prêts pour commencer votre apprentissage sur la création
d'interfaces graphiques avec gtkmm !
Dans le prochain chapitre, vous créerez une fenêtre et apprendrez à modifier son titre, sa taille, etc.
Cependant, ce n'est pas du tout adapté, car cela inclut toutes les classes de gtkmm.
Il est préférable d'inclure seulement ce dont nous avons besoin pour nos projets.
Par exemple, à la place de ce code :
#include <gtkmm.h>
Gtk::Window fenetre;
Gtk::Main::run(fenetre);
return 0;
#include <gtkmm/window.h>
Gtk::Window fenetre;
Gtk::Main::run(fenetre);
return 0;
Vous voyez ?
Nous avons remplacé #include <gtkmm.h> par #include <gtkmm/main.h> et
#include <gtkmm/window.h>.
De cette manière, nous n'incluons pas toutes les classes de gtkmm, mais seulement celles
nécessaires.
En quoi est-ce plus adapté ?
Je trouve que cela va faire un code beaucoup plus gros si nous devons ajouter une ligne par widget
utilisé.
Si nous incluons seulement le nécessaire, l'exécutable sera plus petit et la compilation, moins
longue.
Par exemple, pour le programme que vous avez fait au chapitre précédent, si j'inclus seulement le
nécessaire, l'exécutable est près de 2 fois plus petit et la compilation, près d'une demi-fois moins
longue.
Cela signifie que nous créons un objet Main de l'espace de noms Gtk.
C'est la même chose pour #include <gtkmm/window.h> qui inclut le nécessaire pour cette
ligne :
Gtk::Window fenetre;
Pour conclure, il est préférable d'inclure seulement les classes qui sont nécessaires au projet.
#include <gtkmm/window.h>
Gtk::Window fenetre;
Gtk::Main::run(fenetre);
return 0;
Ces arguments peuvent être utiles à gtkmm, car certains arguments peuvent lui être destinés.
Ces arguments seront enlevés et ceux qu'il ne connaît pas resteront dans le tableau de telle sorte que
vous pourrez les traiter par la suite.
Ensuite, il faut initialiser gtkmm avec la ligne suivante :
Gtk::Main app(argc, argv);
Et la dernière instruction avant le return affiche la fenêtre et entre dans le boucle principale :
Gtk::Main::run(fenetre);
Qu'est-ce que la boucle principale ?
Pour que l'application puisse tourner en continu, c'est-à-dire pour qu'elle ne se ferme pas lorsque
toutes les instructions sont effectuées, il faut qu'il y ait une boucle infinie.
Cette boucle s'arrêtera lorsque l'utilisateur fermera le programme en cliquant sur la petite croix se
trouvant sur la barre de titre de votre fenêtre.
Elle peut également s'arrêter lors d'une autre action que vous aurez prédéfinie (par exemple : lors du
clic sur ce bouton, le programme doit se fermer - vous apprendrez à gérer les événements dans le
prochain chapitre).
C'est tout pour les explications du code de base.
Passons maintenant à la modification de votre fenêtre.
Modifier un widget
Pour modifier un widget, il suffit d'appeler des méthodes qui font tout ce qu'on veut (ou presque
).
Pour modifier notre fenêtre, il faudra appeler des méthodes de la classe Gtk::Window.
Après avoir créer un objet fenetre de type Gtk::Window, nous pouvons donc l'appeler :
fenetre.set_title("Ma belle fenêtre !");
Et vous pouvez ensuite l'afficher dans la console comme n'importe quel objet string :
std::cout << titre << std::endl;
Modifier l'icône de votre fenêtre
Pour modifier l'icône de votre fenêtre, il faudra... appeler une autre méthode.
En l'occurrence, il s'agit de set_icon_from_file() avec comme paramètre le chemin vers
votre icône.
Exemple :
fenetre.set_icon_from_file("icone.png");
Minimiser
Pour minimiser la fenêtre (réduire dans la barre des tâches), c'est-à-dire la rendre invisible jusqu'à
temps que l'utilisateur appuie sur son nom dans la barre des tâches, il faut appeler la méthode
iconify().
Pour faire l'opération inversion, donc pour la rendre de nouveau visible, appelez la méthode
deiconify().
Maximiser
Je suis sûr que vous pouvez deviner le nom de la méthode pour maximiser la fenêtre :
fenetre.maximize();
Cela donne la taille maximale à la fenêtre de telle sorte qu'elle prend toute la taille de l'écran.
L'opération inverse lui rend sa taille d'avant :
fenetre.unmaximize();
fenetre.unfullscreen();
Pour la déplacer à une position précise, il faudra appeler la méthode move(int x, int y).
Cette position est relative au coin en haut à gauche de l'écran.
Exemple pour les deux méthodes précédente :
fenetre.resize(640, 480); //Redimensionner la fenêtre : nouvelle largeur =
640px, nouvelle hauteur = 480px.
D'ailleurs, il serait illogique de vouloir placer la fenêtre en même temps au centre et à (100, 50).
Il existe beaucoup, beaucoup d'autres méthodes, mais je ne peux pas vous les apprendre, car ce
serait long et... inutile .
Pour ajouter un widget à notre fenêtre, nous devrons... appeler une méthode (je pense que vous
commencez à comprendre le principe ).
Mais avant, nous devrons créer un widget.
Pour cet exemple, nous allons afficher un bouton (Gtk::Button).
Cela veut-il dire que nous devrons ajouter un #include <gtkmm/button.h> ?
Oui !
Vous comprenez bien le principe
Donc, ajoutez cette ligne en haut de votre fichier main.cpp.
Ensuite, il faudra créer un objet de type Gtk::Button.
Nous allons lui passer un paramètre : son texte.
Voici comment faire :
Gtk::Button bouton("Clique-moi !");
Cela créera un bouton sur lequel est affiché le texte "Clique-moi !".
Hé ! J'ai compilé mon programme en ajoutant cette ligne et ma fenêtre est toujours vide...
Quel est le problème ?
Le problème, c'est que vous êtes impatients qu'il faut l'ajouter à la fenêtre.
Pour ce faire, appelez la fonction add() sur la fenêtre avec comme paramètre le widget à ajouter :
fenetre.add(bouton);
#include <gtkmm/main.h>
#include <gtkmm/window.h>
return 0;
Je me suis permis d'ajouter une bordure intérieure (invisible) à la fenêtre pour qu'il y ait un peu
d'espace autour du bouton.
Enlevez cette instruction pour voir la différence.
Le code précédent donne ceci :
Pour afficher plusieurs widgets, il faudra utiliser des conteneurs ; je vous présenterai ceci dans trois
chapitres.
#include <gtkmm/main.h>
#include <gtkmm/stock.h> //Ne pas oublier d'inclure ce fichier pour pouvoir
utiliser les Stock Items.
#include <gtkmm/window.h>
Gtk::Main::run(fenetre);
return 0;
Voici le résultat :
Utiliser l'héritage !
Pour terminer ce second chapitre, parlons de l'héritage dans gtkmm.
Comme je l'ai mentionné plus tôt, nous pouvons utiliser l'héritage pour créer de nouveaux widgets.
Ces widgets dériveront des widgets existants.
Le fichier main.cpp
Ce fichier restera toujours identique :
#include <gtkmm/main.h>
#include "Fenetre.hpp"
Fenetre fenetre;
Gtk::Main::run(fenetre);
return 0;
Comme vous pouvez le constater, nous utilisons une classe Fenetre que nous allons créer à
l'instant.
Le fichier Fenetre.hpp
C'est dans ce fichier que nous allons créer la classe Fenetre :
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <gtkmm/window.h>
public :
Fenetre();
};
#endif
Ensuite, nous incluons seulement le nécessaire (si nous aurions utilisé un bouton, il aurait fallu
ajouter un #include <gtkmm/button.h>).
Ensuite, nous créons notre classe Fenetre, qui hérite de Gtk::Window, de façon public.
Nous pouvons donc dire que Fenetre est une Gtk::Window (c'est vrai, je vous le jure ).
Le fichier Fenetre.cpp
Enfin, voici le fichier Fenetre.cpp.
C'est dans ce fichier que nous allons modifier notre fenêtre et ajouter des fonctions de rappel (nous
verrons plus tard ce que c'est - leur prototype étant, bien sûr, dans le fichier Fenetre.hpp).
Le voici :
#include "Fenetre.hpp"
Fenetre::Fenetre() {
//Modification de la fenêtre.
Nous ne sommes plus obligé d'écrire fenetre. devant un appel de méthode, car ce sont des
méthodes de la classe même.
Sous Linux, si vous utilisez la ligne de commande, n'oubliez pas d'ajouter le fichier Fenetre.cpp
après main.cpp, sinon cela ne compilera pas !
Mais, c'est la même fenêtre qu'avant.
Quelle est la différence ?
La différence, c'est que c'est plus lisible pour vous (et ça, je peux vous dire que c'est une grande
différence lors d'un grand projet ).
J'ai une autre question.
J'ai ajouté un bouton dans le constructeur, mais rien ne s'affiche.
J'ai pourtant appelé les méthodes add(bouton) et bouton.show().
Quel est le problème ?
Lorsque nous utilisons l'héritage avec gtkmm, nous ne pouvons plus créer des widgets comme
avant.
La bibliothèque gtkmm permet aux programmeurs de contrôler la durée de vie des widgets de la
même manière qu'en C++.
Il y a plusieurs façons de créer des widgets dans une classe et je vais vous en montrer deux.
#define DEF_FENETRE
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public :
Fenetre();
private :
Gtk::Button bouton; //Déclaration du bouton.
};
#endif
//Modification de la fenêtre.
#define DEF_FENETRE
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public :
Fenetre();
private :
};
#endif
C'est le même fichier que précédemment, sauf que nous créons un pointeur sur Gtk::Button à la
place d'un objet Gtk::Button.
En outre, nous ajoutons un destructeur pour pouvoir détruire le bouton à la destruction de la fenêtre.
Voici le nouveau fichier Fenetre.cpp :
#include "Fenetre.hpp"
Fenetre::Fenetre() {
bouton->show();
add(*bouton);
}
Fenetre::~Fenetre() {
delete bouton;
Puisque nous utilisons un pointeur, nous devons utiliser new dans le constructeur et delete dans
le destructeur (pour le détruire en même temps que la fenêtre).
La méthode add() prend un Gtk::Widget et non un pointeur ; c'est pourquoi nous ajoutons une
étoile (*) devant le pointeur.
Cependant, il y a un moyen de faire plus rapide que l'exemple précédent.
Dans l'exemple précédent, nous détruisons le widget en même que la fenêtre.
Il y a un moyen d'automatiser cela.
Le fichier Fenetre.hpp devient très simple :
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public :
Fenetre();
};
#endif
Fenetre::Fenetre() {
Gtk::Button* bouton = Gtk::manage(new Gtk::Button("Hello World!"));
add(*bouton);
bouton->show();
Dans ce tutoriel, pour que le code soit plus clair, j'utiliserai la première façon de faire (les widgets
déclarés en tant que attribut) sauf lorsque ce sera impossible.
Vous savez maintenant créer et modifier votre fenêtre à votre guise (ou presque ). Il suffit d'utiliser
des méthodes déjà écrites par d'autres. C'est simple, non ?
Mais... il y a pleins d'autres choses que je voudrais faire avec ma fenêtre... par exemple, empêcher
l'utilisateur de la redimensionner, ou bien enlever les bordures du système. Y a-t-il un moyen de
faire cela ?
Mais bien sûr.
Cependant, je ne peux guère tout vous apprendre sur la création d'interfaces graphiques avec
gtkmm.
C'est tellement immense... et je ne connais pas tout sur cette bibliothèque.
Alors, comment vais-je faire pour le savoir ?
Vous devrez lire la documentation !
(Dites donc, pourquoi tout le monde est parti. )
N'ayez pas peur, je vais vous apprendre à la lire.
Elle vous dira tout ce dont vous avez besoin de savoir.
Pour ceux qui veulent savoir tout de suite à quoi elle ressemble, cliquez ici (en anglais, comme
toujours ).
Pour les autres, je vous apprendrai, dans un chapitre ultérieur, à lire la documentation.
Définition
Un signal est un message envoyé par un widget lorsqu'un événement s'est produit sur ce dernier
(par exemple, lorsque l'utilisateur clique sur un bouton).
Pour traiter les événements, nous devons connecter un signal à une fonction de rappel.
Ce peut être n'importe quelle fonction, qui prend des paramètres ou non.
Ce peut également être une méthode d'une classe.
Il est à noter que chaque signal a un prototype de fonction de rappel.
En connectant un signal à une telle fonction, nous devons vérifier qu'il envoie autant de paramètres
que son prototype en contient.
Toutefois, il existe une méthode pour ajouter des paramètres supplémentaires à un signal.
Je vais vous montrer cette technique dans ce chapitre.
Pour gérer les événements, nous utiliserons la bibliothèque sigc++ que vous avez installée (sans
vous en rendre compte ) en même temps que gtkmm.
Cette bibliothèque détecte des erreurs de type à la compilation, ce qui permet d'éviter des bogues
qu'il est possible d'avoir avec Gtk+.
Elle vérifie aussi le nombre de paramètres envoyés par le signal et reçus par la fonction de rappel à
la compilation.
Nous pouvons donc détecter des erreurs à la compilation.
Mais, où devons-nous placer ce code pour que le programme se ferme lorsque l'utilisateur clique sur
le bouton ?
Voici la ligne qu'il faut ajouter :
boutonQuitter.signal_clicked().connect(sigc::ptr_fun(&Gtk::Main::quit));
Puis, nous indiquons sigc::ptr_fun() pour dire que nous connectons le signal à une fonction
et non à une méthode.
Pour info, sigc::ptr_fun() retourne un foncteur.
À la fin, il y a le nom de la fonction, sans parenthèses, précédé d'une esperluette (&).
Dans le but d'alléger le code, je vous montre un projet qui ne contient qu'un seul fichier —
main.cpp.
Mais, vous, dans vos projets, vous créerez une classe pour votre fenêtre (comme je vous ai montré
au chapitre précédent).
Voici un code complet :
#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
bouton.signal_clicked().connect(sigc::ptr_fun(&Gtk::Main::quit));
//Connexion du signal clicked() à la fonction Gtk::Main::quit() qui ferme le
programme.
Gtk::Main::run(fenetre);
return 0;
Je le sais !
Et si nous essayions ceci :
boutonQuitter.signal_clicked().connect(sigc::ptr_fun(&unfullscreen
));
La classe Fenetre hérite de Gtk::Window, il ne devrait pas y avoir de problème, non ?
Malheureusement, le compilateur n'aime pas trop...
Et comment saurait-il quel objet doit appeler cette méthode ?
En effet, nous pouvons connecter un signal à une méthode de n'importe quel objet.
Au lieu d'utiliser ptr_fun(), nous utiliserons mem_fun().
Le premier paramètre est l'objet et le deuxième, la méthode.
Voici ce qu'il faut écrire pour qu'un clic sur le bouton quitte le mode plein écran :
bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::unfullscreen));
Ceci appelle la méthode unfullscreen() de l'objet this qui est de type Fenetre.
Pour ceux qui utilisent C++ 2011, ce code fait la même chose :
bouton.signal_clicked().connect([this]() { unfullscreen(); });
Il ne faut pas oublier de capturer this pour que la fonction puisse appeler unfullscreen() sur
la fenêtre courante.
Est-il possible de connecter un même signal à deux fonctions de rappel ?
Oui, ceci est tout à fait possible :
bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::fullscreen));
bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::unfullscreen));
Pour corriger l'erreur, il faudra aller voir dans la documentation pour vérifier le prototype de la
fonction de rappel du signal en question.
Je vous apprendrai comment vérifier cela lorsque je vous apprendrai à lire la documentation.
#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
bouton.signal_clicked().connect(sigc::bind<std::string>(sigc::mem_fun(fenetre,
&Gtk::Window::set_title), "Titre de la fenêtre")); //Lorsque l'utilisateur
cliquera sur le bouton, le titre de la fenêtre changera.
Gtk::Main::run(fenetre);
return 0;
Encore une fois, c’est bien plus simple avec la nouvelle norme :
bouton.signal_clicked().connect([&fenetre]() { fenetre.set_title("Titre de la
fenêtre"); });
Exemple
Pour terminer ce chapitre, nous allons concevoir un petit programme qui utilise les signaux.
Ce programme sera très simple et, bien que inutile, vous fera pratiquer ce que vous avez vu dans ce
chapitre.
Le programme affichera un bouton :
Et lorsque l'utilisateur cliquera dessus, nous allons modifier le texte sur le bouton et le titre sur la
fenêtre :
Si vous vous sentez capable, essayez de faire ce programme par vous-même.
Je vous donne la correction.
Le fichier main.cpp
#include <gtkmm/main.h>
#include "Fenetre.hpp"
Fenetre fenetre;
Gtk::Main::run(fenetre);
return 0;
Le fichier Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <string>
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public :
Fenetre();
private :
Gtk::Button bouton;
};
#endif
Le fichier Fenetre.cpp
#include "Fenetre.hpp"
set_title("Hello World!");
set_border_width(10);
add(bouton);
bouton.show();
bouton.set_can_focus(false);
bouton.signal_clicked().connect(sigc::bind<string>(sigc::mem_fun(*this,
&Fenetre::set_title), "Bonjour le monde !")); //Lorsque l'utilisateur clique sur
le bouton, modifier le titre et...
bouton.signal_clicked().connect(sigc::bind<string>(sigc::mem_fun(bouton,
&Gtk::Button::set_label), "Bonjour le monde !")); //... le texte sur le bouton.
});
Après avoir vu comment connecter un signal à une fonction de rappel, nous allons voir comment en
créer.
Dans vos projets, vous créerez plus souvent des fonctions de rappel que des signaux, mais il est bon
de savoir comment créer les deux.
La fonction de rappel
Une fonction de rappel est une fonction comme les autres.
Elle retourne une valeur et a des paramètres.
Pour cette sous-partie, nous allons utiliser une fonction de rappel qui agrandit la taille de la fenêtre.
Voici son prototype (que vous pouvez ajouter dans le fichier Fenetre.hpp) :
void augmenterTaille();
int largeur(0);
int hauteur(0);
get_size(largeur, hauteur);
Ensuite, nous redimensionnons la fenêtre grâce à la méthode resize() (nous ajoutons 10px à la
taille actuelle).
C'est très simple, n'est-ce pas ?
Il ne vous reste plus qu'à connecter un signal à cette fonction et le tour est joué :
bouton.signal_clicked().connect(sigc::mem_fun(*this,
&Fenetre::augmenterTaille));
Cette fois, nous allons envoyer à cette fonction la largeur et la hauteur que nous voulons ajouter à la
fenêtre.
Vous devriez être capable de modifier la méthode, mais je vous la donne tout de même (que je suis
gentil ):
void Fenetre::augmenterTaille(int augmentationLargeur, int augmentationHauteur)
{
int largeur(0);
int hauteur(0);
get_size(largeur, hauteur);
Et comme vous avez appris plus haut comment appeler une fonction de rappel qui ne comprend pas
les mêmes paramètres que le signal, vous devriez être capable d'écrire la connexion.
Je vous la donne également :
bouton.signal_clicked().connect(sigc::bind<int, int>(sigc::mem_fun(*this,
&Fenetre::augmenterTaille), 130, 140));
Comme il y a plusieurs paramètres, nous devons les séparer par des virgules.
À chaque clic sur le bouton, la fenêtre s'agrandira de 130 pixels horizontalement et 140
verticalement.
Cette fois, je vous donne un code complet avec 3 fichiers (main.cpp, Fenetre.cpp et Fenetre.hpp).
main.cpp
#include <gtkmm/main.h>
#include "Fenetre.hpp"
Gtk::Main::run(fenetre);
return 0;
Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public:
Fenetre();
private:
Gtk::Button bouton;
};
#endif
Fenetre.cpp
#include "Fenetre.hpp"
Fenetre::Fenetre() : bouton("Agrandir") {
add(bouton);
bouton.signal_clicked().connect(sigc::bind<int, int>(sigc::mem_fun(*this,
&Fenetre::augmenterTaille), 130, 140));
show_all();
int largeur(0);
int hauteur(0);
get_size(largeur, hauteur);
À la ligne 10, nous connectons le signal clicked() (encore lui :colere2: ) du bouton à notre fonction
de rappel augmenterTaille() ; nous lui transmettons les deux paramètres demandés par cette
dernière.
À la ligne 14, il y a notre fonction de rappel.
Je viens de vous expliquer son principe ; je ne me répèterai pas.
Passons à la création de signaux.
Créer un signal
Un signal est un objet de type sigc::signal.
Lorsque nous utilisons sigc::signal, nous ajouterons toujours des chevrons après pour
indiquer le type de retour et celui des paramètres.
Par exemple, si nous voulons créer un signal qui ne renvoie rien et n'envoie aucun paramètre, nous
le créerons comme ceci :
sigc::signal<void> signal_nom;
Pour le signal que nous venons de créer nous devons faire cela :
objet.signal_nom.connect()
Premièrement, comme le type de retour et celui des paramètres peut parfois être long à écrire, nous
commencerons par créer un typedef :
typedef sigc::signal<void> type_signal_nom;
type_signal_nom
Dans ce cas-ci, ce n'est pas vraiment un gain niveau longueur, mais, parfois, ce sera le cas.
C'est au moins un gain niveau lisibilité.
Ensuite, nous pouvons créer notre accesseur :
type_signal_nom signal_nom();
Vous voyez, l'accesseur retourne un objet de type type_signal_nom, donc notre signal.
Dans la partie protected, nous allons créer notre signal :
type_signal_nom m_signal_nom;
Vous voyez, nous l'utilisons souvent, ce typedef (il n'est donc pas si inutile que ça ).
return m_signal_nom;
Maintenant, nous pouvons utiliser notre signal avec des parenthèses, comme ceci :
signal_nom().connect(...);
Comme c'est un signal de notre fenêtre, nous n'avons pas besoin d'écrire le nom d'un objet devant
signal_nom().
Il est tout à fait possible de créer des signaux pour d'autres widgets ; il faut juste créer une classe
héritant de ce widget et faire ce je viens de vous montrer.
Émettre un signal
Pour émettre un signal, il suffit d'appeler la méthode emit() de ce signal :
signal_nom().emit();
L'émission du signal
Il suffit d'ajouter un argument de type std::string à la méthode emit() :
signal_nom().emit("Texte");
Exemple
Pour l'exemple, nous allons reprendre notre fonction de rappel augmenterTaille() (celle sans
paramètre) et allons émettre un signal lorsque la taille de la fenêtre est supérieure ou égale à 200px
(car nous trouvons que c'est assez gros, un bouton de 200px ).
Voici les fichiers :
main.cpp
#include <gtkmm/main.h>
#include "Fenetre.hpp"
Fenetre fenetre;
Gtk::Main::run(fenetre);
return 0;
Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <gtkmm/button.h>
#include <gtkmm/window.h>
public:
Fenetre();
void augmenterTaille();
type_signal_taille_max signal_taille_max();
protected:
type_signal_taille_max signalTailleMax;
private:
Gtk::Button bouton;
};
#endif
Fenetre.cpp
#include "Fenetre.hpp"
set_default_size(200, 200);
set_border_width(10);
bouton.set_can_focus(false);
add(bouton);
bouton.signal_clicked().connect(sigc::mem_fun(*this,
&Fenetre::augmenterTaille));
signal_taille_max().connect(sigc::bind<int, int>(sigc::mem_fun(*this,
&Fenetre::resize), 100, 100));
show_all();
void Fenetre::augmenterTaille() {
int largeur(0);
int hauteur(0);
get_size(largeur, hauteur);
resize(nouvelleLargeur, nouvelleHauteur);
signal_taille_max().emit();
Fenetre::type_signal_taille_max Fenetre::signal_taille_max() {
return signalTailleMax;
}
Dans le constructeur, nous configurons notre fenêtre et nous connectons le signal clicked() et
notre signal perso à des fonctions de rappel (nous ajoutons des paramètres à l'une d'elle avec
sigc::bind).
Puis, nous avons notre fonction de rappel augmenterFenetre() que j'ai modifié un peu.
Après avoir redimensionné la fenêtre — pas avant, car le code n'aurait aucun effet à cause que la
méthode resize() connectée au signal taille_max() serait appelé avant la méthode resize()
de la présente fonction de rappel —, nous émettons le signal perso.
Enfin, nous avons notre accesseur.
Après deux chapitres très riche en nouveautés, je pense qu'un exemple qui regroupe la plupart de
celles-ci vous ferait le plus grand bien.
Exemple
Nous allons faire un programme qui utilise une barre de progression.
Lorsque cette barre de progression atteindra 50%, nous émettrons un signal qui ne transmet pas
d'argument.
Lorsque cette barre atteindra 100%, nous émettrons un signal qui transmet un argument.
Avec cet exemple, nous pratiquerons tout ce que nous avons vu lors de ce chapitre et du précédent.
Voici le résultat :
Le fichier main.cpp
Il est toujours identique :
#include <gtkmm/main.h>
#include "Fenetre.hpp"
Fenetre fenetre;
Gtk::Main::run(fenetre);
return 0;
Le fichier Fenetre.hpp
Nous allons voir ce fichier petit à petit.
Commençons par le début :
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <string>
#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/label.h>
#include <gtkmm/progressbar.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>
L'habituelle protection est présente et nous ajoutons ensuite les entêtes nécessaires au programme.
Dans cet exemple, nous utiliserons un conteneur.
Nous verrons comment utiliser les conteneurs dans le prochain chapitre.
Nous vous attardez donc pas à cette partie du code.
Ensuite, nous créons la classe :
class Fenetre : public Gtk::Window {
public :
Fenetre();
Rien de nouveau...
Puis, nous créons nos deux signaux et nous créons une fonction de rappel :
//
type_signal_pourcentage_max signal_pourcentage_max();
Nous créons, comme à l'accoutumée, un typedef pour le type de chacun des signaux.
En dessous de ces typedefs, nous créons les prototypes des accesseurs pour les signaux.
protected :
type_signal_pourcentage_max signalPourcentageMax;
private :
Gtk::VButtonBox boiteV;
Gtk::Button bouton;
Gtk::Label etiquette;
};
#endif
#define DEF_FENETRE
#include <string>
#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/label.h>
#include <gtkmm/progressbar.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>
public :
Fenetre();
type_signal_pourcentage_max signal_pourcentage_max();
type_signal_pourcentage_max signalPourcentageMax;
private :
Gtk::VButtonBox boiteV;
Gtk::Button bouton;
Gtk::Label etiquette;
};
#endif
Le fichier Fenetre.cpp
Voici le début de ce fichier :
#include "Fenetre.hpp"
add(boiteV);
boiteV.pack_start(bouton);
bouton.set_can_focus(false);
boiteV.pack_start(barreProgression);
boiteV.pack_start(etiquette);
//...
//...
show_all();
J'ai volontairement passé la partie de connexion des signaux pour vous la montrer plus tard.
Ne vous attardez pas au code ci-dessus, car il utilise des notions que vous n'avez pas encore vues
(les conteneurs).
Passons à notre fonction de rappel :
void Fenetre::ajouterPourcentage() {
barreProgression.set_fraction(nouveauPourcentage);
else {
barreProgression.set_fraction(0);
}
Cette fonction sera appelée lorsque l'utilisateur (donc vous ) cliquera sur le bouton Ajouter.
Premièrement, nous récupérons le pourcentage actuel avec get_fraction() et nous lui
ajoutons 0.1 (10%).
Ensuite, nous émettons nos signaux aux pourcentages voulus.
N'oubliez pas que le signal pourcentage_max() prend un paramètre !
Puis, si le pourcentage actuel est inférieur à 100%, nous pouvons mettre à jour la barre de
progression avec set_fraction().
Si le pourcentage est égal à 100%, nous remettons la barre de progression à 0% (pour tester
plusieurs fois ce merveilleux programme ).
Voyons les accesseurs :
Fenetre::type_signal_pourcentage_moitie Fenetre::signal_pourcentage_moitie()
{ //Cette méthode retourne le signal.
return signalPourcentageMoitie;
Fenetre::type_signal_pourcentage_max Fenetre::signal_pourcentage_max() {
return signalPourcentageMax;
bouton.signal_clicked().connect(sigc::mem_fun(*this,
&Fenetre::ajouterPourcentage));
signal_pourcentage_moitie().connect(sigc::bind<std::string>(sigc::mem_fun(etique
tte, &Gtk::Label::set_text), "Le pourcentage est 50%."));
signal_pourcentage_max().connect(sigc::mem_fun(etiquette,
&Gtk::Label::set_text));
Nous connectons le signal clicked() du bouton à notre fonction de rappel
ajouterPourcentage() (à chaque fois que l'utilisateur clique sur le bouton, le pourcentage de
la barre de progression augmente de 10%).
Ensuite, nous connectons notre signal pourcentage_moitie() à la fonction de rappel
set_text().
Comme notre signal pourcentage_moitie() ne transmet aucun paramètre à la fonction de
rappel, nous devons en ajouter un avec sigc::bind.
signal_pourcentage_moitie().connect([&etiquette]() { etiquette.set_text("Le
pourcentage est 50%."); });
signal_pourcentage_max().connect([&etiquette](std::string texte)
{ etiquette.set_text(texte); });
add(boiteV);
boiteV.pack_start(bouton);
bouton.set_can_focus(false);
boiteV.pack_start(barreProgression);
boiteV.pack_start(etiquette);
signal_pourcentage_moitie().connect(sigc::bind<std::string>(sigc::mem_fun(etique
tte, &Gtk::Label::set_text), "Le pourcentage est 50%."));
signal_pourcentage_max().connect(sigc::mem_fun(etiquette,
&Gtk::Label::set_text));
show_all();
void Fenetre::ajouterPourcentage() {
barreProgression.set_fraction(nouveauPourcentage);
else {
barreProgression.set_fraction(0);
}
}
Fenetre::type_signal_pourcentage_moitie Fenetre::signal_pourcentage_moitie()
{ //Cette méthode retourne le signal.
return signalPourcentageMoitie;
Fenetre::type_signal_pourcentage_max Fenetre::signal_pourcentage_max() {
return signalPourcentageMax;
Voilà, vous savez maintenant beaucoup de choses sur la gestion des événements.
Si vous avez de la difficulté à comprendre, relisez ce chapitre et faites des essais par vous-même ;
c'est comme cela que vous progresserez.
Vous vous êtes rendu compte, il y a deux chapitres, qu'il était impossible d'ajouter plus d'un widget
dans une fenêtre.
Si c'était vraiment le cas, ce serait vraiment difficile de faire de jolies fenêtres. C'est pourquoi les
conteneurs ont été inventés.
Dans ceux-ci, nous pouvons mettre une infinité de widgets (en théorie ).
Voyons, à l'instant, comment ils fonctionnent.
Principaux conteneurs
Le conteneur d'alignement
Il permet d'aligner un widget à un endroit précis (en bas à droite, au centre, etc.) :
La barre d'onglets
Vous l'avez vue lors du premier chapitre :
Les barres de défilement
Les boîtes
Elles permettent d'aligner plusieurs widgets.
En voici une (spécialisée pour les boutons) :
Les tableaux
Autres conteneurs
Le cadre
Le cadre d'apparence
Même chose que le conteneur précédent, mais il garde une proportion entre la largeur et la hauteur :
L'icône d'extension
Ce conteneur cache son widget enfant ; il faut cliquer sur une petite flèche (ou un plus, dans le cas
présent) pour qu'il s'affiche.
Lorsque le widget est caché :
Lorsqu'il est affiché :
Les panneaux
Nous pouvons y mettre deux widgets que nous pouvons redimensionner à l'aide d'une "poignée"
situé entre les deux widget:
Le widget détachable
Ce conteneur peut se séparer de la fenêtre.
Le widget fixé
Ce conteneur contient des widgets dont la position est fixe :
Le conteneur d'alignement
Le premier conteneur que nous allons apprendre à utiliser ne peut contenir qu'un seul widget.
Par contre, après avoir vu les autres conteneurs de ce chapitre (en particulier les boîtes), vous saurez
en mettre plusieurs.
Mais, nous verrons que, parfois, il sera inutile d'utiliser le conteneur d'alignement.
Le conteneur d'alignement permet d'aligner un widget à un endroit précis, par exemple en bas à
droite de la fenêtre..
Création d'un conteneur d'alignement
Avant toute utilisation, ajoutez cette ligne à votre projet :
#include <gtkmm/alignment.h>
xalign
Ce paramètre indique l'alignement horizontal du widget.
Il faut donner une des constantes suivantes :
• Gtk::ALIGN_START, pour l'aligner à gauche ou en haut ;
• Gtk::ALIGN_CENTER, pour l'aligner au centre ;
• Gtk::ALIGN_END, pour l'aligner à droite ou en bas.
Est-il possible d'aligner un widget entre la gauche et le centre ?
Oui.
Pour cela, il faudra donner une valeur de 0.0 (gauche) à 1.0 (droite).
Par exemple, si nous indiquons 0.25, le widget sera exactement centré entre la gauche (0.0) et le
centre (0.5).
yalign
C'est comme xalign, mais c'est pour l'alignement vertical.
Les constantes sont les mêmes.
Il est également possible de donner une valeur de 0.0 (haut) à 1.0 (bas).
xscale
Ce paramètre indique l'espace horizontal que prend le widget dans son espacement alloué (de 0.0, le
minimum, à 1.0, le maximum).
Donc, si nous indiquons 0.5, le widget prendra la moitié de l'espace qui lui est alloué.
Si nous voulons que le widget soit aligné, il faut que cette valeur soit inférieure à 1, sinon, comme il
prendra tout l'espace disponible, il ne semblera pas aligné à un endroit précis.
yscale
Identique au précédent, sauf que cela concerne l'espace vertical.
Exemple
Je pense qu'un exemple vous serait très utile .
Pour que j'aie moins de code à vous montrer, je ne vais qu'utiliser un fichier main.cpp.
Mais, vous, dans vos projets, vous créerez une classe pour votre fenêtre.
#include <gtkmm/alignment.h>
#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
fenetre.set_border_width(10);
//Il faut mettre 0, 0 comme derniers paramètres pour que le widget prenne le
moins d'espace possible.
fenetre.add(alignement);
bouton.set_can_focus(false);
Gtk::Main::run(fenetre);
return 0;
C'est tout ; passons maintenant à des conteneurs qui permettent d'ajouter plusieurs widgets.
Les boîtes
Les boîtes permettent d'aligner des widgets soit horizontalement, soit verticalement.
Pour utiliser les boîtes, vous devez inclure le fichier approprié :
#include <gtkmm/box.h>
Cela créera une boîte verticale dont les widgets ne prennent pas le même espace, mais qui ont au
moins 10px entre eux.
pack_start() et pack_end()
Ces méthodes prennent au moins un paramètre : le widget à ajouter à la boîte.
L'une d'elle en ajoute un à partir du début (pack_start()) et l'autre en ajoute un à partir de la fin
(pack_end()).
La plupart du temps, nous utiliserons pack_start(), mais nous pouvons utiliser pack_end()
pour être sûr qu'un widget se retrouvera à la fin.
Étant donné que le principe est difficile à comprendre, je vais vous l'expliquer avec un schéma :
Exemple
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
Gtk::Label etiquette1("Bonjour");
Gtk::Label etiquette2("Hello");
Gtk::Label etiquette3("Hola");
Gtk::Main::run(fenetre);
return 0;
À la ligne 11, nous créons une boîte verticale dont les widgets n'ont pas nécessairement la même
taille et dont il y a un espace d'un minimum de 10px entre chaque widget.
À la ligne 14, nous ajoutons un widget à cette boîte à partir de la fin.
À l'instruction suivante, nous créons une boîte horizontale dont tous les widgets ont la même taille.
Ça ne paraît pas vraiment, mais mettez false (par défaut, c'est false) et vous verrez une petite
différence.
Pour vraiment la voir, essayez avec des boutons au lieu d'étiquettes.
Exercice
Je vais vous faire travailler un petit peu.
Essayez de reproduire cette fenêtre en utilisant des boîtes imbriquées :
• Gtk::VButtonBox.
Ce sont ces deux dernières que nous allons utiliser pour créer des boîtes contenant des boutons.
Il faut inclure la classe :
#include <gtkmm/buttonbox.h>
Il est possible d'appeler le constructeur différemment pour ne pas avoir besoin d'appeler ensuite
set_layout() :
Gtk::HButtonBox boiteBoutonsH(Gtk::BUTTONBOX_END, 10);
Le premier paramètre indique l'arrangement des boutons et le deuxième (lui aussi facultatif),
l'espacement entre ces derniers.
Cela donne (si nous ajoutons des boutons avec pack_start() ou pack_end()) :
set_child_secondary()
Cette méthode permet de rendre un widget secondaire.
Par exemple, si nous utilisons Gtk::BUTTONBOX_END, tous les boutons seront à la fin, sauf ceux
secondaires (ceux-ci seront au début).
Cette méthode s'utilise comme ceci :
boiteBoutonsH.set_child_secondary(boutonOptions);
ATTENTION !
Vous devez ajouter le widgetavant de le rendre secondaire, sinon cela ne fonctionnera pas !
Exemple
Voyons un exemple complexe complet :
#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
//Création d'une boîte pour les boutons (situés à la fin de la boîte) dont
l'espacement entre ceux-ci est de 10px.
Gtk::Button boutonOptions("Options");
boutonOptions.set_can_focus(false);
Gtk::Button boutonAide("Aide");
boutonAide.set_can_focus(false);
Gtk::Button boutonFermer("Fermer");
boutonFermer.set_can_focus(false);
boiteBoutonsH.pack_start(boutonOptions);
boiteBoutonsH.pack_start(boutonAide);
boiteBoutonsH.pack_start(boutonFermer);
boiteBoutonsH.set_child_secondary(boutonOptions);
fenetre.show_all();
Gtk::Main::run(fenetre);
return 0;
Les tableaux
Pour créer un tableau, nous devons lui dire combien de colonnes et de rangées il aura :
Gtk::Table tableau(1, 2);
Redimensionnement
Il est possible de redimensionner un tableau après sa création avec la méthode resize() :
tableau.resize(2, 2);
Le tableau possède maintenant deux rangées et deux colonnes.
Aïe Caramba !
Je vais vous détailler tout ceci :
• child est le widget que nous voulons ajouter au tableau ;
• left_attach est la coordonnée x1 (le côté gauche du widget) ;
• right_attach est la coordonnée x2 (le côté droit du widget) ;
• top_attach est la coordonnée y1 (le côté haut du widget) ;
• bottom_attach est la coordonnée y2 (le côté bas du widget) ;
• xoptions décrit le comportement du widget lorsqu'il est redimensionné horizontalement ;
• yoptions décrit le comportement du widget lorsqu'il est redimensionné verticalement ;
• xpadding est la marge (en px) à gauche et à droite du widget ;
• ypadding est la marge (en px) en haut et en bas du widget.
Si nous voulons qu'il soit à la troisième rangée et à la deuxième colonne et qu'il prenne deux
rangées de large, nous ferons :
tableau.attach(bouton1, 2, 4, 1, 2);
Exemple
Voyons un exemple qui mélange tout ce que nous avons vu pour les tableaux :
#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/table.h>
#include <gtkmm/window.h>
int main(int argc, char* argv[]) {
Gtk::Window fenetre;
fenetre.add(tableau);
bouton1.set_can_focus(false);
bouton2.set_can_focus(false);
bouton3.set_can_focus(false);
Gtk::Main::run(fenetre);
return 0;
}
Nous redimensionnons le tableau tout de suite après l'avoir créé, ce qui est tout à fait inutile (c'était
pour l'exemple ).
Vous devriez bien comprendre avec les commentaires et ce screenshot :
Faites des tests pour bien comprendre comment les tableaux fonctionnent.
Je vous assure que cela pourrait vous aider.
La barre d'onglets
La barre d'onglets ne permet d'afficher qu'un seul widget à la fois.
Cependant, en cliquant sur un autre onglet, nous pouvons voir un autre widget.
Vous les utilisez probablement avec votre navigateur Web (en tout cas, moi, je les utilise ).
C'est très pratique pour pouvoir visualiser ou éditer plusieurs documents "en même temps".
set_scrollable()
Lorsqu'il y a trop d'onglets, la fenêtre s'agrandit.
Cela peut devenir problématique.
En appelant cette méthode, des flèches se rajouteront pour naviguer dans les onglets, donc la fenêtre
peut être plus petite.
Exemple d'une barre d'onglets utilisant set_scrollable() :
popup_enable()
Cette méthode ajoute un menu contextuel.
Lors d'un clic-droit sur la barre d'onglets, un menu contextuel apparaîtra et permettra à l'utilisateur
de se rendre immédiatement à un onglet de la barre.
Cette méthode s'appelle également très simplement :
barreOnglets.popup_enable();
Le résultat :
set_group_name()
Cette méthode permet de définir le groupe d'une barre d'onglets.
Deux barres d'un même groupe peuvent s'échanger des onglets.
Cela peut être pratique.
Cette méthode prend un paramètre :
une chaîne de caractères représentant le nom du groupe.
barreOnglets.set_group("onglets");
barreOnglets2.set_group("onglets");
Les deux barre d'onglets pourront s'échanger des onglets si ceux-ci sont détachables (nous verrons
plus bas comment faire).
append_page()
Le plus important.
Cela permet d'ajouter une page à la barre d'onglets.
Cette méthode prend au minimum un paramètre : le widget à ajouter.
Exemple :
barreOnglets.append_page(texte1);
Le texte écrit sur l'onglet sera "Page 1" si c'est le premier widget à être ajouté, "Page 2" si c'est le
deuxième, etc.
Il existe aussi d'autres méthodes pour ajouter un widget, dont prepend_page() et
insert_page(), respectivement pour ajouter un widget au début et en ajouter un à un position
précise.
Dans le prochain exemple, je vous détaillerai l'utilisation de append_page() avec d'autre
paramètres.
remove_page()
Cette méthode enlève un onglet et son widget de la barre d'onglets.
Elle prend en paramètre soit le numéro de la page de l'onglet, soit le widget lui-même.
ATTENTION !
Comme pour les tableaux, les numéros de page débutent à 0.
set_tab_reorderable()
Cette méthode permet à l'utilisateur de réordonner un widget (modifier sa position) en utilisant le
glisser-déposer.
Elle prend un paramètre : le widget.
set_tab_detachable()
Cette méthode permet à l'utilisateur de détacher un widget, c'est-à-dire de l'enlever de la barre
d'onglets pour le mettre dans une autre.
Elle prend elle aussi un paramètre : le widget.
Ne pas oublier que les deux barres d'onglets doivent avoir le même groupe pour pouvoir s'échanger
des pages !
next_page() et prev_page()
next_page() avance à la prochaine page de l'onglet et prev_page() va à la page précédente.
set_action_widget()
Voici la dernière méthode que nous verrons dans cette sous-partie.
Elle permet d'ajouter un widget à gauche ou à droite des onglets.
Rien ne nous empêche d'appeler deux fois cette méthode pour en avoir un à gauche et un à droite.
Elle prend un pointeur sur le widget et l'une des deux constantes suivantes :
• Gtk::PACK_START : à gauche des onglets ;
• Gtk::PACK_END : à droite des onglets.
Il faut afficher manuellement ce widget avec show() sinon il n'apparaîtra pas !
Exemple
Cette fois, nous allons utiliser trois fichiers (main.cpp, Fenetre.cpp et Fenetre.hpp).
main.cpp
Il est comme à l'accoutumée :
#include "Fenetre.hpp"
Fenetre fenetre;
Gtk::Main::run(fenetre);
return 0;
Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE
#include <gtkmm/box.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/button.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/notebook.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>
public :
Fenetre();
private :
Gtk::Notebook barreOnglets;
Gtk::Notebook barreOnglets2;
Gtk::HButtonBox boiteBoutonsH;
Gtk::VBox boiteV;
Gtk::Button boutonNouvelOnglet;
Gtk::Button boutonPrecedent;
Gtk::Button boutonSuivant;
Gtk::Button boutonSupprimerOnglet;
Gtk::Image imageNouveau;
Gtk::Image imageSupprimer;
Gtk::Label texte1;
Gtk::Label texte2;
Gtk::Label texte3;
Gtk::Label texte4;
Gtk::Label texte5;
Gtk::Label texte6;
Gtk::Label texte7;
};
#endif
Cette image sera créée à partir d'un Stock Item (une image prédéfinie).
Fenetre.cpp
C'est un assez gros fichier (n'ayez pas peur, je vais vous l'expliquer ):
#include "Fenetre.hpp"
Fenetre::Fenetre() : boutonPrecedent(Gtk::Stock::GO_BACK),
boutonSuivant(Gtk::Stock::GO_FORWARD), texte1("Ceci est la première page de
l'onglet."), texte2("Ceci est la deuxième page de l'onglet."), texte3("Ceci est
la troisième page de l'onglet."), texte4("Ceci est la quatrième page de
l'onglet."), texte5("Ceci est la cinquième page de l'onglet."), texte6("Ceci est
la sixième page de l'onglet."), texte7("Ceci est la septième page de
l'onglet."), imageNouveau(Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON),
imageSupprimer(Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON) {
add(boiteV);
boutonPrecedent.set_can_focus(false);
boiteBoutonsH.pack_start(boutonPrecedent);
boutonSuivant.set_can_focus(false);
boiteBoutonsH.pack_start(boutonSuivant);
boiteV.pack_start(boiteBoutonsH);
/*
* */
barreOnglets.set_scrollable();
barreOnglets.popup_enable();
barreOnglets.set_can_focus(false);
barreOnglets.set_group_name("onglets");
barreOnglets.append_page(texte1);
barreOnglets.append_page(texte2);
barreOnglets.set_tab_reorderable(texte2);
barreOnglets.set_tab_detachable(texte2);
//Ajout de texte4 avec "Page 4" écrit sur l'onglet. Un clic sur Alt-4 nous
amène à cet onglet.
barreOnglets.set_tab_reorderable(texte4);
barreOnglets.set_tab_detachable(texte4);
barreOnglets.set_tab_reorderable(texte5);
barreOnglets.set_tab_detachable(texte5);
barreOnglets.set_tab_reorderable(texte6);
barreOnglets.set_tab_detachable(texte6);
barreOnglets.remove_page(5);
/*
* */
boutonNouvelOnglet.set_can_focus(false);
boutonSupprimerOnglet.set_can_focus(false);
barreOnglets.set_action_widget(&boutonNouvelOnglet, Gtk::PACK_END);
barreOnglets.set_action_widget(&boutonSupprimerOnglet, Gtk::PACK_START);
boutonSupprimerOnglet.show();
/*
* */
boiteV.pack_start(barreOnglets);
/*
* */
/*
* */
barreOnglets2.set_scrollable();
barreOnglets2.popup_enable();
barreOnglets2.set_can_focus(false);
barreOnglets2.set_group_name("onglets");
barreOnglets2.append_page(texte7, "Page 7");
barreOnglets2.set_tab_reorderable(texte7);
barreOnglets2.set_tab_detachable(texte7);
boiteV.pack_start(barreOnglets2);
/*
* */
//Connexions
boutonPrecedent.signal_clicked().connect(sigc::mem_fun(barreOnglets,
&Gtk::Notebook::prev_page));
boutonSuivant.signal_clicked().connect(sigc::mem_fun(barreOnglets,
&Gtk::Notebook::next_page));
barreOnglets.signal_switch_page().connect(sigc::mem_fun(*this,
&Fenetre::modifierTitre));
show_all();
set_title(barreOnglets.get_tab_label_text(*barreOnglets.get_nth_page(numPage)));
Voici le résultat :
Vous commencez à faire des fenêtre de plus en plus complexes.
Que d'explications s'imposent.
La liste d'initialisation
À la ligne 3, nous commençons la liste d'initialisation en créant deux boutons à partir de Stock Item.
Ces boutons permettront à l'utilisateur de naviguer dans les onglets.
Toujours dans la liste d’initialisation, nous créons les widgets qui seront dans les onglets.
À la fin de cette liste, nous créons des images... à partir de Stock Item.
Le premier paramètre est le Stock Item et le deuxième, la taille de l'image.
Ajout de widgets
Ensuite, nous ajoutons une boîte verticale à la fenêtre et des boutons à une autre boîte.
Nous ajoutons la boîte pour les boutons dans la boîte verticale.
Notez que le troisième onglet ne peut pas être déplacé, ni détaché (essayez de le faire ).
Le quatrième onglet est ajouté d'une façon différente ; nous envoyons un troisième paramètre qui
vaut true.
Cela signifie que nous voulons lui ajouter une mnémonique.
Cette mnémonique permettra d’accéder à l’onglet avec le combinaison de touches Alt + le
caractère précédé d'un trait de soulignement (_).
Pour "Page _4", le raccourci est donc Alt-4.
À la ligne 110, nous voyons un nouveau signal (nous n'avons pas vraiment eu l'occasion d'en voir
d'autres dans ce chapitre ):
switch_page()
Ce signal est émis lorsqu'il y a un changement de page.
Nous le connectons à notre fonction de rappel personnalisé (modifierTitre()).
set_title(barreOnglets.get_tab_label_text(*barreOnglets.get_nth_page(numPage)));
La méthode get_tab_label_text() nous donne le texte présent sur l'onglet d'un widget
particulier.
Et get_nth_page() nous donne un pointeur vers le widget de la page numPage.
numPage est un paramètre transmis par le signal pour nous dire à quelle page la barre d'onglets est.
Exercice
Modifiez le code ci-dessus pour que les boutons à gauche et à droite de la barre d'onglet enlèvent et
ajoutent des onglets.
Pensez à utiliser Gtk::manage(), car vous devez gérer un nombre indéterminé de widgets.
Ça ne devrait pas vous causer des problèmes.
Utilisation
Avant toute utilisation, il faut inclure la bonne classe :
#include <gtkmm/scrolledwindow.h>
Cela permet de dire au widget si vous voulez toujours voir les barres de défilement, seulement
lorsque nécessaire ou jamais.
Personnellement, j'aime bien les afficher seulement lorsque nécessaire.
Afficher les barres de défilement seulement lorsque c'est nécessaire signifie qu'elles seront absentes
(invisibles) lorsque nous pouvons voir au complet le widget.
Il faut donc appeler la méthode comme ceci pour ce faire :
barresDeDefilement.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
Exemple
Voyons un exemple :
#include <gtkmm/main.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/textview.h>
#include <gtkmm/window.h>
Gtk::Window fenetre;
fenetre.set_border_width(10);
Gtk::ScrolledWindow barresDeDefilement;
barresDeDefilement.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
fenetre.add(barresDeDefilement);
fenetre.show_all();
Gtk::Main::run(fenetre);
return 0;
Si nous n'avions pas utilisé les barres de défilement pour afficher la zone de texte, la fenêtre se
serait vu agrandir au fur et à mesure que nous ajoutons du texte.
Cela devient gênant lorsque celle-ci devient plus grande que l'écran .
Avec les conteneurs, vous pouvez vraiment organiser vos fenêtres comme vous le voulez.
Après avoir vu deux notions importantes (les signaux et les conteneurs), nous allons relaxer un peu
en voyant de nouveaux widgets (donc, pas de nouvelles notions ).
Maintenant, de nombreuses possibilités de conception de fenêtres s'offrent à vous.
Je vous lance un petit défi : créez-moi un jeu de morpion (Tic-tac-toe).
Cela mettra en pratique vos notions de signaux et de conteneurs.
Si vous n'êtes pas capable d'implémenter une petite intelligence artificielle pour pouvoir jouer
contre, faites un morpion à deux joueurs humains.
Si vous êtes capables d'implémenter une IA, faites les deux modes de jeux.
Le jeu final peut ressembler à ceci :
Comment vais-je faire pour savoir quel bouton a été cliqué par l'utilisateur ?
Dois-je créer une fonction de rappel par bouton ?
Non, ce serait beaucoup trop lourd.
Vous vous rappelez le chapitre sur les signaux (moi, oui )?
Eh bien, vous devrez créer une fonction de rappel qui prend un paramètre (un Gtk::Button) que
vous lierez avec sigc::bind, comme vous l'avez appris.
De cette façon, vous pourrez savoir quel bouton a été cliqué sans avoir à faire 9 fonctions de rappel.