Version en ligne

Tutoriel : Créer des interfaces graphiques en C++ avec gtkmm !

Table des matières

Créer des interfaces graphiques en C++ avec gtkmm !
Introduction
Qu'est-ce que GTK+ ? gtkmm ?
Les widgets
Installation de gtkmm
Compiler votre première application !
Votre première fenêtre
Quelques mots sur les #include...
Explication du code de base
Modifier un widget
Ajout d'un widget à la fenêtre
Utiliser l'héritage !
Les signaux et les fonctions de rappel (Partie 1/2)
Fonctionnement des signaux
Connexion d'un signal à une fonction de rappel
Exemple
Les signaux et les fonctions de rappel (Partie 2/2)
Créez vos propres fonctions de rappel
Créez vos propres signaux
Exemple
Organiser vos fenêtres avec les conteneurs
Les différents conteneurs
Le conteneur d'alignement
Les boîtes
Les boîtes pour les boutons
Les tableaux
La barre d'onglets
Les barres de défilement
Les boîtes de dialogue
Les boîtes de dialogue de message
Boîte de dialogue de sélection de couleur
Boîte de dialogue de sélection de police
Boîte de dialogue de sélection de fichier(s)
La boîte de dialogue À propos
Boîte de dialogue personnalisée
Les principaux widgets
Les différents boutons
Widgets d'affichage
Widgets de saisie
Menus, barres d'outils, barre d'état
Les menus
Les barres d'outils
Le gestionnaire d'interface utilisateur
La barre d'état

Créer des interfaces graphiques en C++ avec gtkmm !

Vous voulez créer des interfaces graphiques en C++ avec GTK+ ?
Eh bien, c'est votre jour de chance !
Vous voici sur un tutoriel expliquant comment créer de A à Z une interface graphique avec gtkmm !

Gtkmm vous permet de créer des interfaces graphiques avec des boutons, des menus, des barres d'outils et beaucoup d'autres widgets facilement.

Vous ne connaissez rien à la conception d'interfaces graphiques ?
Eh bien, ce n'est pas grave du tout, car, ici, nous apprenons tout à partir de zéro !

Introduction

Qu'est-ce que GTK+ ? gtkmm ?

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 !

Ça ne donne pas faim, cela ? :p

Qu'est-ce que GTK+ ? gtkmm ?

Introduction Les widgets

Qu'est-ce que GTK+ ? 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 :

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.

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 :

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 QVector au lieu std::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>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Court, n'est-ce pas :soleil: ?


Introduction Les widgets

Les widgets

Qu'est-ce que GTK+ ? gtkmm ? Installation de gtkmm

Les widgets

Que sont les widgets ?

Un widget est un composant dans une interface graphique.

Sans widget, il n'y aurait rien dans les fenêtres de vos applications...
il n'y aurait même pas de fenêtre... :D

Il y a des widgets avec lesquels l'utilisateur peut interagir comme les boutons et les zones de texte.

D'autres widgets servent à afficher des informations ou à arranger des widgets selon un ordre précis.

Widgets fournis par gtkmm

Il y a une pléthore de widgets utilisable avec gtkmm.
Eh oui, tant que ça :lol: .

Je vais vous présenter les principaux.

Le bouton

Je pense que vous savez ce que c'est, je me trompe :euh: ?

Voici tout de même une image d'un bouton :

Image utilisateur
La case à cocher

Une case à cocher est un bouton à deux états : activé (coché) et désactivé (décoché) :

Image utilisateur

Cette case est cochée.

La fenêtre

Je pense que vous savez très bien ce que c'est :

Image utilisateur

Les fenêtres que vous programmerez prendront l'apparence du système.

Le menu

Un menu est un widget présentant plusieurs éléments que l'utilisateur peut choisir :

Image utilisateur
La barre d'outils

La barre d'outils sert à rendre plus accessibles certains des éléments les plus utilisés du menu :

Image utilisateur
La barre d'état

La barre d'état affiche des informations que nous savons déjà :lol: :

Image utilisateur
Les zones de texte uniligne et multiligne

Les zones de texte permettent à l'utilisateur d'entrer du texte.

Voici une zone de texte uniligne :

Image utilisateur

Et voici une zone de texte multiligne :

Image utilisateur
La barre de progression

La barre de progression indique à l'utilisateur l'avancement d'une opération plus ou moins longue :

Image utilisateur
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 :

Image utilisateur
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 :

Image utilisateur

Cela fait le tour de tous les widgetsquelques widgets de gtkmm.

Je vous ai montré cela pour que vous sachiez à quoi vous attendre.

Passons dès maintenant à l'installation de gtkmm !


Qu'est-ce que GTK+ ? gtkmm ? Installation de gtkmm

Installation de gtkmm

Les widgets Compiler votre première application !

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++

Ceci installera le compilateur g++.

Ensuite, vous devez installer gtkmm avec la commande :

sudo apt-get install libgtkmm-3.0-dev

L'installation prend quelques minutes.

Ce devrait être bon.
Rendez-vous à la prochaine sous-partie pour la compilation de votre premier programme.

Sous Windows...
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 :

Image utilisateur

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 :

Image utilisateur

Sauf si vous savez ce que vous faites, gardez toutes les cases cochés.

Cliquez sur Suivant et enfin sur Installer.
L'installation devrait durer quelques minutes :

Image utilisateur

À la fin de l'installation, cliquez sur Fermer :

Image utilisateur

Voilà, tout est installé, rendez-vous à la prochaine sous-partie pour tester votre installation toute fraîche, toute belle. :p


Les widgets Compiler votre première application !

Compiler votre première application !

Installation de gtkmm Votre première fenêtre

Compiler votre première application !

Le premier programme que vous allez compiler affichera une fenêtre de 200px par 200px.

Super :D !

Commençons tout de suite.

Veuillez copier-coller le code source suivant (il sera détaillé au prochain chapitre) :

#include <gtkmm.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    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.

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`

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`

Cela permet de dire au compilateur que nous allons utiliser gtkmm.

g++ main.cpp -o Fenetre -std=c++0x `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 :

Image utilisateur

Votre première fenêtre :D !

Utilisation de gtkmm avec CMake

Si vous utilisez CMake, vous pouvez utilisez un fichier semblable à celui-ci :

cmake_minimum_required(VERSION 2.6)

project(Fenetre)

find_package(PkgConfig)
pkg_check_modules(GTKMM gtkmm-3.0)

if(NOT GTKMM_FOUND)
    message(FATAL_ERROR "gtkmm-3.0 not 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}
)

Vos fichiers sources devront se trouver dans le répertoire src/.
Vous pouvez par exemple utiliser ce fichier comme suit :

cd vers/le/dossier/contenant/CMakeLists.txt
mkdir build && cd build
cmake ..
make
./Fenetre
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`

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`

Validez et vous pouvez enfin compiler votre application et la lancer :

Image utilisateur
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 :

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\) :

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.


Installation de gtkmm Votre première fenêtre

Votre première fenêtre

Compiler votre première application ! Quelques mots sur les #include...

Après une brève introduction, c'est maintenant le temps de commencer.
Vous n'avez encore rien fait et vous commencerez lentement, mais sûrement, en modifiant la fenêtre que vous avez créée lors du chapitre précédent.

Au programme, modification du titre de la fenêtre, de son icône et autres et ajout d'un widget à la fenêtre.

Cela vous donnera des bases pour pouvoir modifier tous les widgets, car je ne peux vous dire tout ce qu'il est possible de faire (la documentation est là pour cela).

Quelques mots sur les #include...

Votre première fenêtre Explication du code de base

Quelques mots sur les #include...

Dans le chapitre précédent, je vous ai fait compiler un programme qui avait l'include suivant :

#include <gtkmm.h>

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>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

nous devrions écrire celui-ci :

#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    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.

Vous n'aimez pas attendre pendant la compilation, n'est-ce pas :lol: ?
Alors, faites ce que je dit. ;)

En outre, un exécutable moins gros dit un téléchargement moins long pour les utilisateurs de vos logiciels.
C'est merveilleux. ^^

D'accord, je comprends maintenant.
Mais, comment vais-je faire pour savoir quelles classes inclure ?

Dans ce tutoriel, je vous dirai quelles classes inclure.

Mais, je vais également vous donner un petit truc.

Dans le code donné plus haut, il y a cette ligne :

Gtk::Main app(argc, argv);

Cela signifie que nous créons un objet Main de l'espace de noms Gtk.

Pour utiliser cette classe, il faut l'inclure :

#include <gtkmm/main.h>

C'est la même chose pour #include <gtkmm/window.h> qui inclut le nécessaire pour cette ligne :

Gtk::Window fenetre;

La classe Window de l'espace de noms Gtk.

Pour conclure, il est préférable d'inclure seulement les classes qui sont nécessaires au projet.


Votre première fenêtre Explication du code de base

Explication du code de base

Quelques mots sur les #include... Modifier un widget

Explication du code de base

Je vais vous expliquer ce que fait le code que vous avez vu dans la sous-partie précédente, à savoir, ce code :

#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Premièrement, nous incluons ce qui est nécessaire au bon fonctionnement de ce programme (explications de la dernière sous-partie).

Ensuite, nous devons prendre les arguments donnés au programme, c'est pourquoi la fonction main est déclarée comme suit :

int main(int argc, char* argv[]) {

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);

De surcroît, on lui passe les arguments par cette ligne.

La ligne suivante crée une fenêtre :

Gtk::Window fenetre;

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.

C'est tout pour les explications du code de base.

Passons maintenant à la modification de votre fenêtre.


Quelques mots sur les #include... Modifier un widget

Modifier un widget

Explication du code de base Ajout d'un widget à la 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 :D ).

Pour modifier notre fenêtre, il faudra appeler des méthodes de la classe Gtk::Window.

Modifier le titre de votre fenêtre

Pour modifier le titre de votre fenêtre, celui dans la barre de titre située en haut de votre fenêtre, il faut appeler la méthode set_title().

Après avoir créer un objet fenetre de type Gtk::Window, nous pouvons donc l'appeler :

fenetre.set_title("Ma belle fenêtre !");

Cela modifiera le titre pour "Ma belle fenêtre !".

Pour obtenir le titre de votre fenêtre, il faut appeler la méthode get_title() (je suis sûr que vous l'aviez deviné :p ).

Exemple :

std::string titre = fenetre.get_title();

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");

C'est simple, non :) ?

L'icône apparaîtra, la plupart du temps, en haut à gauche de la fenêtre et dans la barre des tâches :

Image utilisateur
Minimiser, maximiser, etc.
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();
Mode plein écran

Pour que la fenêtre soit en mode plein écran, vous devez appeler la méthode fullscreen() et pour qu'elle redevienne comme avant, unfullscreen().

fenetre.fullscreen();
fenetre.unfullscreen();

Ce code ne fait rien tellement les actions sont rapides.

Redimensionner la fenêtre et la déplacer

Pour redimensionner la fenêtre, appelez la méthode resize(int largeur, int hauteur).

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.
fenetre.move(100, 50); //Déplacer la fenêtre à la position (100, 50).

Comment faire pour déplacer la fenêtre au centre de l'écran ?

Il faudra utiliser une autre méthode. ^^
Cette méthode s'appelle set_position() et elle prend une constante en paramètre.

Pour centrer la fenêtre, appelez-la comme ceci :

fenetre.set_position(Gtk::WIN_POS_CENTER);

Pour connaître les autres constantes, cliquez ici.

Il existe beaucoup, beaucoup d'autres méthodes, mais je ne peux pas vous les apprendre, car ce serait long et... inutile o_O .


Explication du code de base Ajout d'un widget à la fenêtre

Ajout d'un widget à la fenêtre

Modifier un widget Utiliser l'héritage !

Ajout d'un widget à la fenêtre

Pour ajouter un widget à notre fenêtre, nous devrons... appeler une méthode (je pense que vous commencez à comprendre le principe :lol: ).

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 :D 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);

Tu me fais marcher, non ?
Je ne vois toujours rien dans cette fenêtre...

Il reste une dernière étape pour voir le widget :
l'afficher :-° !

Pour afficher chacun des widgets de votre fenêtre, il faut, pour chacun, appeler leur méthode show().

Donc, pour résumer, voici un code complet :

#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main kit(argc, argv); //Initialise gtkmm et regarde dans les arguments du programme.
    
    Gtk::Window fenetre; //Création de la fenêtre.
    fenetre.set_title("Ma belle fenêtre !"); //Modifier le titre de la fenêtre.
    fenetre.set_border_width(10); //Ajouter une bordure (invisible) de 10px à la fenêtre.
    
    Gtk::Button bouton("Clique-moi !"); //Création d'un bouton.
    fenetre.add(bouton); //Ajout du bouton à la fenêtre.
    
    bouton.show(); //Afficher le bouton.
    
    Gtk::Main::run(fenetre); //Affiche la fenêtre et démarre la boucle, qui se terminera lorsqu'on fermera le programme.
    
    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 :

Image utilisateur

Mais, c'est quoi ce truc affreux sur le bouton ?

Cela indique que le bouton a le focus.

Lorsqu'un widget a le focus, cela veut dire que l'utilisateur peut l'utiliser.
Par exemple, appuyez sur la touche Entrée lorsqu'un bouton a le focus et cela fera la même chose que si vous aviez cliqué dessus avec la souris.

Si une zone de texte a le focus, cela signifie que vous pouvez écrire dedans.

Pour qu'un widget ne puisse pas avoir le focus, appelez la méthode suivante :

bouton.set_can_focus(false);

Voilà le résultat :

Image utilisateur

C'est mieux ainsi, non ?

J'ai essayé d'ajouter un deuxième bouton à la fenêtre et j'ai obtenu une erreur à l'exécution me disant que je ne peux mettre qu'un seul widget à la fois dans une fenêtre.
C'est nul, si c'est vrai !
Comment faire pour en mettre plusieurs ?

En effet, nous ne pouvons mettre qu'un seul widget dans une fenêtre ; un deuxième appel à la méthode add() provoquera une erreur.

Pour afficher plusieurs widgets, il faudra utiliser des conteneurs ; je vous présenterai ceci dans trois chapitres.

Les Stock Items

Les Stock Items sont très utiles pour développer des applications sans aller chercher des icônes un peu partout à travers le Net.

En effet, grâce à eux, nous pouvons créer des boutons, mais aussi des éléments dans un menu et dans une barre d'outils, avec une étiquette (son texte) et une icôneprédéfinies.

Cela signifie que vous codez d'une certaine façon et vous n'avez pas besoin de fournir les icônes avec l'exécutable.
Super, non :) ?

Pour créer un bouton avec une étiquette et une icône prédéfinie, il faudra utiliser une surcharge de son constructeur.
À la place de lui donner un objet de type std::string, nous allons lui donner une constante de type Gtk::Stock.

Par exemple, pour créer un bouton ayant pour texte "Annuler" et l'icône qui va avec, faites comme ceci :

#include <gtkmm/button.h>
#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>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    Gtk::Window fenetre; //Création de la fenêtre.
    
    Gtk::Button bouton(Gtk::Stock::CANCEL); //Création d'un bouton à partir d'un Stock Item.
    fenetre.add(bouton); //Ajout du bouton à la fenêtre.
    bouton.set_can_focus(false); //Empêcher le bouton d'avoir le focus.
    bouton.show(); //Afficher le bouton.
    
    Gtk::Main::run(fenetre);
    
    return 0;
}

Voici le résultat :

Image utilisateur

Comment faire pour savoir quelles sont les constantes prédéfinies ?

Regardez la documentation !
C'est ici, plus précisément.
Elles se trouvent sous le titre Variables.

Cliquez sur l'une d'elle, par exemple CANCEL, pour voir à quoi l'icône ressemble.
Elle peut être différente selon les systèmes d'exploitation et les thèmes.

Il ne se passe rien lorsque je clique sur le bouton.
Comment ajouter un événement lors du clic ?

Pour cela, il faudra utiliser les signaux et les fonctions de rappel.
Je vous présente cela au prochain chapitre (je sens que vous avez hâte :D ).


Modifier un widget Utiliser l'héritage !

Utiliser l'héritage !

Ajout d'un widget à la fenêtre Les signaux et les fonctions de rappel (Partie 1/2)

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.

Une classe pour la fenêtre principale

Pour un vrai projet, nous allons créer une classe pour la fenêtre principale.
C'est dans celle-ci que nous modifierons son titre, son icône et que nous y ajouterons des widgets.

Cela permet de mieux organiser le code.

Voyons à quoi cela ressemble.

Le fichier main.cpp

Ce fichier restera toujours identique :

#include <gtkmm/main.h>

#include "Fenetre.hpp"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    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>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
};

#endif

Je crois que quelques explications s'imposent.

Premièrement, nous écrivons toujours la protection avec #ifndef, #define et #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 ;) ).

Ensuite, il y a le constructeur ; vous devriez en avoir l'habitude.

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.
    set_title("Ma fenêtre héritée !");
    //Autres modifications 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.

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 :D ).

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.

Ajout d'un widget à la fenêtre
Déclarer les widgets en tant que attribut

La méthode la plus simple est de déclarer les widgets en tant que attribut.

Et lorsque nous déclarons un objet en tant que attribut, il faut l'initialiser dans la liste d'initialisation du constructeur.

De cette façon, les widgets seront détruits en même temps que la fenêtre.

Voyons le nouveau fichier Fenetre.hpp :

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/button.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
    
    private :
        Gtk::Button bouton; //Déclaration du bouton.
};

#endif

Et le nouveau fichier Fenetre.cpp :

#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton("Mon bouton !") { //Initialisation du bouton dans la liste d'initialisation.
    //Modification de la fenêtre.
    set_title("Ma fenêtre héritée !");
    //Autres modifications de la fenêtre.
    
    bouton.show(); //Ne pas oublier l'affichage du bouton.
    bouton.set_can_focus(false); //Nous pouvons aussi modifier le bouton.
    add(bouton); //Ajouter le bouton à la fenêtre.
}

Le fichier main.cpp reste toujours le même.

Pourquoi ne pas créer une classe pour le bouton ?

Il aurait été possible de le faire.
Mais comme nous ne l'utilisons qu'une seule fois, cela n'est pas vraiment nécessaire et n'améliore pas vraiment la lisibilité du code.

Créez seulement une classe lorsque cela améliore la lisibilité de votre code ou que vous voulez créer un widget différent de ceux qui existent.

Par exemple, si vous créez une boîte de dialogue personnalisée (nous verrons plus tard ce que c'est et comment en créer une), il est beaucoup plus lisible de faire une classe pour ce widget.

Nous voyons là toute la puissance de l'héritage.

Créer des pointeurs sur des widgets

La deuxième méthode consiste à créer des pointeurs sur des widgets.

De cette façon, il faudra détruire nous-même les widgets.

Voici le nouveau fichier Fenetre.hpp :

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/button.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        ~Fenetre(); //Le destructeur pour pouvoir détruire le bouton.
    
    private :
        Gtk::Button* bouton; //Création du pointeur sur bouton.
};

#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 = new Gtk::Button("Hello World!");
    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).

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>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
};

#endif

Je n'ai rien à dire sur ce code.

Et le fichier Fenetre.cpp est également plus court :

#include "Fenetre.hpp"

Fenetre::Fenetre() {
    Gtk::Button* bouton = Gtk::manage(new Gtk::Button("Hello World!"));
    add(*bouton);
    bouton->show();
}

Nous créons le pointeur en utilisant la fonction Gtk::manage().
Nous ajoutons le bouton, encore une fois, en ajoutant une étoile (*) pour obtenir l'objet et non le pointeur.
Puis, nous l'affichons.

Nous n'avons plus besoin de détruire le widget ; gtkmm le détruira en même temps que son conteneur, à savoir la fenêtre.

De plus, cela nous permet de créer facilement un nombre indéterminé (à la compilation) de widgets.

Dans quel cas ne connaissons-nous pas le nombre de widgets à la compilation ?

Présentement, vous utilisez un navigateur Web.
Ceux qui ont conçu ce logiciel ne savaient pas combien d’onglets vous allez ouvrir. Par conséquent, ils ne pouvaient pas utiliser un attribut par onglet.
Donc, quand vous utiliserez des widgets dont le nombre peut varier, vous devrez bien souvent vous servir de Gtk::manage().

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 :D ). 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 :euh: ).
Pour les autres, je vous apprendrai, dans un chapitre ultérieur, à lire la documentation.


Ajout d'un widget à la fenêtre Les signaux et les fonctions de rappel (Partie 1/2)

Les signaux et les fonctions de rappel (Partie 1/2)

Utiliser l'héritage ! Fonctionnement des signaux

Dans le chapitre précédent, vous avez ajouté un bouton dans votre fenêtre.
Cependant, il ne se passait rien lorsqu'on cliquait dessus.

Il va falloir modifier un peu cela pour que, par exemple, la fenêtre se ferme lorsque l'utilisateur clique sur le bouton.

Avec l'utilisation des signaux, vous verrez que c'est facile de rendre votre application interactive.

Les signaux sont le cœur d'une interface graphique. Vous ne pouvez pas vous en passer.

Mettons un peu d'interactivité à votre fenêtre !

Dans le but de l'alléger, notre étude des signaux et des fonctions de rappel se fera en deux chapitres.

Fonctionnement des signaux

Les signaux et les fonctions de rappel (Partie 1/2) Connexion d'un signal à une fonction de rappel

Fonctionnement des signaux

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.

Pour gérer les événements, nous utiliserons la bibliothèque sigc++ que vous avez installée (sans vous en rendre compte :lol: ) 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.


Les signaux et les fonctions de rappel (Partie 1/2) Connexion d'un signal à une fonction de rappel

Connexion d'un signal à une fonction de rappel

Fonctionnement des signaux Exemple

Connexion d'un signal à une fonction de rappel

Connexion à une fonction de rappel sans paramètre

Notre premier exemple sera de connecter le clic sur un bouton à la fermeture du programme.

Créez-moi donc une fenêtre qui ressemble à ceci :

Image utilisateur

Vous devriez être capable. :-°
Cependant, lorsque nous cliquons dessus, le programme ne se ferme pas.
C'est pourtant ce que nous voulions faire en utilisant ce Stock Item :euh: .

Avant de vous dire comment connecter un signal à une fonction de rappel, je vais vous donner la méthode qui fait arrêter le programme (cette fonction termine la boucle du programme) :

Gtk::Main::quit();

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));

Je vous l'explique à l'instant.

Après boutonQuitter.signal_, il faut mettre le signal que nous souhaitons connecter à une fonction de rappel.
Ici, c'est clicked() (ce qui signifie que ce signal est émis lorsque l'utilisateur clique sur le bouton).

Ensuite, nous connectons (connect()) le signal à la fonction de rappel.

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 (&).

Voici un code complet :

#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    
    Gtk::Button bouton(Gtk::Stock::QUIT); //Création d'un bouton.
    fenetre.add(bouton); //Ajout du bouton à la fenêtre.
    
    bouton.show(); //Ne pas oublier d'afficher le bouton.
    bouton.set_can_focus(false); //Empêcher le bouton d'avoir le focus.
    
    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;
}

Dans ce code, nous construisons la fenêtre en y ajoutant un bouton Quitter.
Ensuite, nous connectons le signal clicked() du bouton à la méthode Gtk::Main::quit(), ce qui a pour effet de fermer le programme.
Votre bouton fait enfin ce pour quoi il a été conçu. ^^

bouton.signal_clicked().connect([]() { Gtk::Main::quit(); });
Connexion à une méthode sans paramètre

Mettez votre fenêtre en plein écran en appelant fullscreen() dans le constructeur de la classe Fenetre.

Changez le Stock Item de votre bouton pour Gtk::Stock::LEAVE_FULLSCREEN.

Voici ce que nous voulons faire maintenant :
quitter le mode plein écran en cliquant sur le bouton.

Cependant, la méthode unfullscreen() appartient à la classe Gtk::Window.

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));

Bien que ce code ne fasse rien d'exceptionnel, ça fonctionne. ^^

Connexion à une méthode avec paramètre(s)

Maintenant, nous voulons que le clic sur le bouton modifie le titre de la fenêtre.
Pour modifier ceci, il faut appeler set_title(), mais avec un argument.

Comment allons nous faire ?

Le problème c'est que le signal clicked() n'envoie aucun paramètre à la fonction de rappel.
Il faut donc en ajouter un.

Pour cela, il faudra lier le paramètre avec sigc::bind().

Donc, nous allons faire ceci :

bouton.signal_clicked().connect(sigc::bind<std::string>(sigc::mem_fun(*this, &Fenetre::set_title), "Titre de la fenêtre"));

Entre les chevrons se trouve le type du paramètre que nous envoyons.
"Titre de la fenêtre" est bien de type std::string.

Dans un code complet, ça donne ceci :

#include <string>

#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    Gtk::Window fenetre;
    fenetre.set_title(""); //Cacher le titre de la fenêtre.
    
    Gtk::Button bouton("Afficher le titre !");
    fenetre.add(bouton); //Ajout du bouton à la fenêtre.
    
    bouton.show(); //Ne pas oublier d'afficher le bouton.
    bouton.set_can_focus(false); //Empêcher le bouton d'avoir le focus.
    
    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, nous créons une fenêtre avec un bouton.
Dans la connexion du signal à la méthode set_title(), notez que nous indiquons &Gtk::Window::set_title au lieu de &Fenetre::set_title, car nous n'avons pas créer de classe Fenetre dans ce code.

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"); });

Il ne faut pas oublier de capturer la fenêtre par référence.

Essayez !
Maintenant, si nous cliquons sur le bouton, le titre de la fenêtre s'affiche !

Transmettre un paramètre est également utile lorsque nous utilisons un même signal pour plusieurs widgets.
Nous pouvons envoyer le widget en paramètre pour savoir que c'est lui qui a émis le signal.


Fonctionnement des signaux Exemple

Exemple

Connexion d'un signal à une fonction de rappel Les signaux et les fonctions de rappel (Partie 2/2)

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 :

Image utilisateur

Et lorsque l'utilisateur cliquera dessus, nous allons modifier le texte sur le bouton et le titre sur la fenêtre :

Image utilisateur

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"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Fenetre fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Ce fichier est toujours le même.

Le fichier Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <string>

#include <gtkmm/button.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
    
    private :
        Gtk::Button bouton;
};

#endif

Nous créons la fenêtre et le bouton ; il n'y a rien de bien compliqué.

Le fichier Fenetre.cpp
#include "Fenetre.hpp"

using namespace std;

Fenetre::Fenetre() : bouton("Hello World!") {
    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.
}

Lui aussi est très simple.

Nous commençons par modifier un peu la fenêtre (titre, bordure).
Puis, nous ajoutons le bouton à la fenêtre.

Enfin, nous connectons le signal clicked() du bouton à deux fonctions de rappel.

La première connexion se charge de modifier le titre de la fenêtre avec set_title().
La deuxième modifie le texte sur le bouton avec set_label().

Dans les deux cas, nous ajoutons un paramètre avec sigc::bind.

Voilà, c'est très simple, n'est-ce pas ?

Et la version C++ 2011 est encore plus simple :

bouton.signal_clicked().connect([this, &bouton]() {
    set_title("Bonjour le monde !");
    bouton.set_label("Bonjour le monde !");
});

Que de nouveautés dans ce chapitre.
C'est un gros morceau que vous venez de voir, là.

Vous savez maintenant comment connecter un signal à une fonction de telle sorte que celle-ci soit appelée lorsque l'utilisateur effectue une action pré-déterminée.

Il existe d'autres signaux que clicked() ; il en existe beaucoup d'autres.
Vous n'avez donc pas vu tous les signaux existants (vous n'en avez vu qu'un seul, en fait :lol: ), mais, au fur et à mesure que vous avancerez dans ce tutoriel, vous en apprendrez d'autres.

Si vous voulez en connaître qui ne sont pas expliqués dans ce tutoriel, il faudra aller dans la doc'.

Dans le prochain chapitre, nous terminerons notre étude des signaux et des fonctions de rappel en voyant comment en créer.


Connexion d'un signal à une fonction de rappel Les signaux et les fonctions de rappel (Partie 2/2)

Les signaux et les fonctions de rappel (Partie 2/2)

Exemple Créez vos propres fonctions de rappel

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.

Créez vos propres fonctions de rappel

Les signaux et les fonctions de rappel (Partie 2/2) Créez vos propres signaux

Créez vos propres fonctions de rappel

Maintenant que nous sommes capables de connecter des signaux à des fonctions de rappel, nous allons apprendre à en créer.

Commençons par les fonctions de rappel et nous finirons avec la création de signaux (gardons le meilleur pour la fin :D ).

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();

Créez également un bouton dans ce fichier.

Nous allons maintenant écrire son contenu dans le fichier Fenetre.cpp :

void Fenetre::augmenterTaille() {
    int largeur(0);
    int hauteur(0);
    get_size(largeur, hauteur);
    resize(largeur + 10, hauteur + 10);
}

Je vous explique ce qui se passe.

Premièrement, nous créons des variables pour conteneur la largeur et la hauteur actuelles de la fenêtre.

Nous obtenons ces données en envoyant ces variables en paramètre à la méthode get_size().

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));

À chaque clic sur un bouton, la fenêtre s'agrandit de 10px.

La fonction de rappel avec des paramètres

Modifions le prototype de notre fonction de rappel pour celui-ci :

void augmenterTaille(int augmentationLargeur, int augmentationHauteur);

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 :p ) :

void Fenetre::augmenterTaille(int augmentationLargeur, int augmentationHauteur) {
    int largeur(0);
    int hauteur(0);
    get_size(largeur, hauteur);
    resize(largeur + augmentationLargeur, hauteur + augmentationHauteur);
}

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));

À 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"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Fenetre fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Ce code est toujours le même.

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/button.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public:
        Fenetre();
        
    private:
        Gtk::Button bouton;
        
        void augmenterTaille(int augmentationLargeur, int augmentationHauteur);
};

#endif

C'est la classe de la fenêtre.
Nous créons le prototype de notre fonction de rappel (qui prend des paramètres) et notre bouton.

Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton("Agrandir") {
    set_default_size(200, 200); //Déterminer la taille par défaut de la fenêtre.
    set_border_width(10); //Ajouter une bordure de 10px autour du bouton.
    add(bouton);
    
    //Connexion du signal clicked() du bouton à la méthode augmenterTaille() de la présente fenêtre.
    //Nous lui passons deux paramètres.
    bouton.signal_clicked().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &Fenetre::augmenterTaille), 130, 140));
    show_all();
}

void Fenetre::augmenterTaille(int augmentationLargeur, int augmentationHauteur) {
    int largeur(0);
    int hauteur(0);
    get_size(largeur, hauteur);
    resize(largeur + augmentationLargeur, hauteur + augmentationHauteur);
}

À 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.


Les signaux et les fonctions de rappel (Partie 2/2) Créez vos propres signaux

Créez vos propres signaux

Créez vos propres fonctions de rappel Exemple

Créez vos propres 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;

Cependant, cela ne crée pas le signal comme ceux dans gtkmm.

Pour l'utiliser, nous ne devrons pas ajouter de parenthèses.

Par exemple, pour le signal clicked() d'un bouton, nous faisons comme ceci :

bouton.signal_clicked().connect()

Pour le signal que nous venons de créer nous devons faire cela :

objet.signal_nom.connect()
Un signal avec des parenthèses

Pour ajouter des parenthèses, nous allons devoir créer un attribut (variable membre) qui sera retournée par une méthode.

Pourquoi devons-nous faire cela ?
C'est un peu une perte de temps, non ?

C'est ce que je me disais aussi. :-°

En utilisant un accesseur, ne saurons que c'est un signal lorsque nous l'émettrons et lorsque nous le connecterons à une fonction de rappel.
En outre, si d'autres personnes lisent votre code, ils sauront immédiatement que c'est un signal.

Voyons comment faire cela.

Ce qui suit se situe dans la partie public de votre classe Fenetre.

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

est maintenant l'équivalent de sigc::signal<void>.

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 :o ).

Enfin, nous devons créer l'accesseur dans le fichier Fenetre.cpp :

Fenetre::type_signal_nom Fenetre::signal_nom() { //Cette méthode retourne le signal.
	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().

Émettre un signal

Pour émettre un signal, il suffit d'appeler la méthode emit() de ce signal :

signal_nom().emit();
Un signal qui transmet des paramètres

Si nous voulons que notre signal transmette des paramètres à la fonction de rappel, nous devons modifier deux choses :

La création du typedef

Pour la création, il faut ajouter le type de l'argument qui sera transmis.
Si c'est un std::string qui est transmis, nous devons faire ceci :

typedef sigc::signal<void, std::string> type_signal_nom;
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 :p ).

Voici les fichiers :

main.cpp
#include <gtkmm/main.h>

#include "Fenetre.hpp"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Fenetre fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Rien de spécial, comme à l'accoutumée.

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/button.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public:
        Fenetre();
        void augmenterTaille();
        
        typedef sigc::signal<void> type_signal_taille_max;
        type_signal_taille_max signal_taille_max();
        
    protected:
        type_signal_taille_max signalTailleMax;
        
    private:
        Gtk::Button bouton;
};

#endif

Dans ce fichier, nous créons notre signal (typedef, attribut et accesseur).

Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton("Agrandir la fenêtre") {
    set_default_size(200, 200);
    set_border_width(10);
    
    bouton.set_can_focus(false);
    add(bouton);
    
    //Connexion du signal clicked() du bouton à la fonction de rappel augmenterTaille().
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::augmenterTaille));
    //Connexion de notre signal perso à la fonction resize().
    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);
    int nouvelleLargeur(largeur + 10);
    int nouvelleHauteur(hauteur + 10);
    resize(nouvelleLargeur, nouvelleHauteur);
    
    if(nouvelleLargeur >= 200 or nouvelleHauteur >= 200) {
        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. :-°


Créez vos propres fonctions de rappel Exemple

Exemple

Créez vos propres signaux Organiser vos fenêtres avec les conteneurs

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 :

Image utilisateur
Le fichier main.cpp

Il est toujours identique :

#include <gtkmm/main.h>

#include "Fenetre.hpp"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    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.

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 :

//
        typedef sigc::signal<void> type_signal_pourcentage_moitie; //Création d'un typedef pour le type du signal.
        type_signal_pourcentage_moitie signal_pourcentage_moitie(); //Création de la méthode qui retourne le signal.
        
        typedef sigc::signal<void, std::string> type_signal_pourcentage_max;
        type_signal_pourcentage_max signal_pourcentage_max();
        
        void ajouterPourcentage(); //Création d'une fonction de rappel.

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.

Plus bas, dans protected, nous créons les-dits signaux :

//
    protected :
        type_signal_pourcentage_moitie signalPourcentageMoitie; //Création du signal.
        type_signal_pourcentage_max signalPourcentageMax;

Le signal pourcentage_moitie() sera émis lorsque la barre de progression atteint 50% et pourcentage_max(), lorsqu'elle atteindra 100%.

Finalement, nous créons les widgets nécessaires au projet et terminons la classe :

//
    private :
        Gtk::ProgressBar barreProgression; //Création d'une barre de progression.
        Gtk::VButtonBox boiteV;
        Gtk::Button bouton;
        Gtk::Label etiquette;
};

#endif

Voici le fichier Fenetre.hpp au complet :

#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>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        
        typedef sigc::signal<void> type_signal_pourcentage_moitie; //Création d'un typedef pour le type du signal.
        type_signal_pourcentage_moitie signal_pourcentage_moitie(); //Création de la méthode qui retourne le signal.
        
        typedef sigc::signal<void, std::string> type_signal_pourcentage_max;
        type_signal_pourcentage_max signal_pourcentage_max();
        
        void ajouterPourcentage(); //Création d'une fonction de rappel.
        
    protected :
        type_signal_pourcentage_moitie signalPourcentageMoitie; //Création du signal.
        type_signal_pourcentage_max signalPourcentageMax;
        
    private :
        Gtk::ProgressBar barreProgression; //Création d'une barre de progression.
        Gtk::VButtonBox boiteV;
        Gtk::Button bouton;
        Gtk::Label etiquette;
};

#endif
Le fichier Fenetre.cpp

Voici le début de ce fichier :

#include "Fenetre.hpp"

Fenetre::Fenetre() : boiteV(Gtk::BUTTONBOX_SPREAD), bouton(Gtk::Stock::ADD) {
    add(boiteV);
    
    boiteV.pack_start(bouton);
    bouton.set_can_focus(false);
    boiteV.pack_start(barreProgression);
    boiteV.pack_start(etiquette);
    
    //Connexions des signaux aux fonction de rappel.
    //...
    //...

    //Affichage des widgets.
    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() {
    double nouveauPourcentage(barreProgression.get_fraction() + 0.1);
    
    if(std::abs(nouveauPourcentage - 0.5) < 0.00001) {
        signal_pourcentage_moitie().emit(); //Émettre notre signal perso.
    }
    else if(std::abs(nouveauPourcentage - 1) < 0.00001) {
        signal_pourcentage_max().emit("Le pourcentage est 100%."); //Émettre notre signal perso avec un paramètre.
    }
    
    if(nouveauPourcentage <= 1.00001) {
        barreProgression.set_fraction(nouveauPourcentage);
    }
    else {
        barreProgression.set_fraction(0);
    }
}

Cette fonction sera appelée lorsque l'utilisateur (donc vous :p ) 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 :lol: ).

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;
}

Je n'ai rien à vous dire sur cette partie du code.
Ces accesseurs retournent les signaux appropriés.

Voyons maintenant les connexions des signaux aux fonctions de rappel (dans le constructeur) :

//
    //Connexion du signal clicked() à une fonction de rappel.
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::ajouterPourcentage));
    //Connexion du signal pourcentage_moitie() à une fonction de rappel en ajoutant un paramètre.
    signal_pourcentage_moitie().connect(sigc::bind<std::string>(sigc::mem_fun(etiquette, &Gtk::Label::set_text), "Le pourcentage est 50%."));
    //Cette fois, pas besoin d'ajouter un paramètre, car le signal le transmet automatiquement.
    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.

Finalement, nous connectons notre autre signal (pourcentage_max()) à la fonction de rappel set_text().
Cette fois, pas besoin d'ajouter un paramètre, car le signal le transmet (au moment de l'émission).

Lorsque la barre de progression atteint 50% et 100%, un message s'affiche dans le bas de la fenêtre.

La version C++ 2011 se fait comme ceci :

bouton.signal_clicked().connect([this]() { this->ajouterPourcentage(); });
signal_pourcentage_moitie().connect([&etiquette]() { etiquette.set_text("Le pourcentage est 50%."); });
signal_pourcentage_max().connect([&etiquette](std::string texte) { etiquette.set_text(texte); });

Seule la dernière ligne pouvait vous poser des problèmes.
En effet, étant donné que le signal pourcentage_max() transmet un paramètre, il fallait que notre fermeture en reçoive un pour pouvoir l’utiliser.

Voici le fichier Fenetre.cpp au complet :

#include "Fenetre.hpp"

Fenetre::Fenetre() : boiteV(Gtk::BUTTONBOX_SPREAD), bouton(Gtk::Stock::ADD) {
    add(boiteV);
    
    boiteV.pack_start(bouton);
    bouton.set_can_focus(false);
    boiteV.pack_start(barreProgression);
    boiteV.pack_start(etiquette);
    
    //Connexion du signal clicked() à une fonction de rappel.
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::ajouterPourcentage));
    //Connexion du signal pourcentage_moitie() à une fonction de rappel en ajoutant un paramètre.
    signal_pourcentage_moitie().connect(sigc::bind<std::string>(sigc::mem_fun(etiquette, &Gtk::Label::set_text), "Le pourcentage est 50%."));
    //Cette fois, pas besoin d'ajouter un paramètre, car le signal le transmet automatiquement.
    signal_pourcentage_max().connect(sigc::mem_fun(etiquette, &Gtk::Label::set_text));
    
    //Affichage des widgets.
    show_all();
}

void Fenetre::ajouterPourcentage() {
    double nouveauPourcentage(barreProgression.get_fraction() + 0.1);
    
    if(std::abs(nouveauPourcentage - 0.5) < 0.00001) {
        signal_pourcentage_moitie().emit(); //Émettre notre signal perso.
    }
    else if(std::abs(nouveauPourcentage - 1) < 0.00001) {
        signal_pourcentage_max().emit("Le pourcentage est 100%."); //Émettre notre signal perso avec un paramètre.
    }
    
    if(nouveauPourcentage <= 1.00001) {
        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.

En parlant de faire des essais :p , je vais vous faire travailler :
Comme vous pouvez le constater, les messages indiquant que la barre de progression est rendue à 50% et 100% demeurent présents lorsque la barre de progression est rendue à 60% et 0%.
Je vous demande d'effacer ces messages lorsqu'elle est rendue à ces pourcentages.
C'est un bon exercice pour vous pratiquer. :)

Passons aux Q.C.M. :)

Dans ce chapitre, vous avez appris à créer vos propres signaux pour gérer tous les évènements possibles !

Maintenant, vous avez une bonne base pour pouvoir gérer la plupart les événements des différents widgets.

Ne vous inquiétez pas, nous verrons d'autres signaux au cours de ce tutoriel pour gérer d'autres événements.


Créez vos propres signaux Organiser vos fenêtres avec les conteneurs

Organiser vos fenêtres avec les conteneurs

Exemple Les différents conteneurs

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. :euh: C'est pourquoi les conteneurs ont été inventés.

Dans ceux-ci, nous pouvons mettre une infinité de widgets (en théorie :D ).

Voyons, à l'instant, comment ils fonctionnent.

Les différents conteneurs

Organiser vos fenêtres avec les conteneurs Le conteneur d'alignement

Les différents conteneurs

Dans un premier temps, voyons ce que sont les conteneurs.

Les conteneurs permettent d'ajouter un ou plusieurs widgets dans une fenêtre. Donc, c’est une façon d’organiser des widgets.

Certains conteneurs ne peuvent contenir qu'un seul widget.
D'autres, ceux que nous utiliserons la plupart du temps, permettent d'en contenir plusieurs.

Cela nous permet d'organiser la façon dont les widgets sont disposés dans la fenêtre.

Voyons les conteneurs que nous étudierons lors de ce chapitre.
Ensuite, nous verrons d'autres conteneurs qui ne feront pas l'objet de ce chapitre (vous pourrez toutefois savoir comment vous en servir grâce à la documentation).

Principaux conteneurs
Le conteneur d'alignement

Il permet d'aligner un widget à un endroit précis (en bas à droite, au centre, etc.) :

Image utilisateur
La barre d'onglets

Vous l'avez vue lors du premier chapitre :

Image utilisateur
Les barres de défilement
Image utilisateur
Les boîtes

Elles permettent d'aligner plusieurs widgets.

En voici une (spécialisée pour les boutons) :

Image utilisateur
Les tableaux
Image utilisateur
Autres conteneurs
Le cadre
Image utilisateur
Le cadre d'apparence

Même chose que le conteneur précédent, mais il garde une proportion entre la largeur et la hauteur :

Image utilisateur
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é :

Image utilisateur

Lorsqu'il est affiché :

Image utilisateur
Les panneaux

Nous pouvons y mettre deux widgets que nous pouvons redimensionner à l'aide d'une "poignée" situé entre les deux widget:

Image utilisateur
Le widget détachable

Ce conteneur peut se séparer de la fenêtre.

Image utilisateur
Le widget fixé

Ce conteneur contient des widgets dont la position est fixe :

Image utilisateur

Cela fait le tour de beaucoup de conteneurs.

Voyons comment les principaux fonctionnent.


Organiser vos fenêtres avec les conteneurs Le conteneur d'alignement

Le conteneur d'alignement

Les différents conteneurs Les boîtes

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>

Voici le prototype de son constructeur :

Gtk::Alignment::Alignment(Align xalign, Align yalign = Gtk::ALIGN_CENTER, float xscale = 1.0, float yscale = 1.0);

Détaillons les paramètres.

xalign

Ce paramètre indique l'alignement horizontal du widget.
Il faut donner une des constantes suivantes :

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 :) .

#include <gtkmm/alignment.h>
#include <gtkmm/button.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    Gtk::Window fenetre;
    
    fenetre.set_border_width(10);
    fenetre.set_default_size(200, 150); //Définir la taille par défaut de la fenêtre.
    
    Gtk::Alignment alignement(Gtk::ALIGN_END, Gtk::ALIGN_END, 0, 0); //Création d'un conteneur d'alignement.
    //L'alignement se fera en bas à droite.
    //Il faut mettre 0, 0 comme derniers paramètres pour que le widget prenne le moins d'espace possible.
    
    fenetre.add(alignement);
    
    Gtk::Button bouton("Clique-moi !");
    bouton.set_can_focus(false);
    
    alignement.add(bouton); //Ajouter un bouton au conteneur d'alignement.
    
    fenetre.show_all(); //Afficher tous les widgets de la fenêtre. Cela évite d'avoir à appeler show() pour chaque widget à afficher.
    
    Gtk::Main::run(fenetre);
    
    return 0;
}

Donc, nous créons le conteneur d'alignement à la ligne 14.
Nous l'ajoutons à la fenêtre, comme n'importe quel widget, avec la méthode add() (ligne 18).
Le widget contenu dans ce dernier sera aligné en bas à droite et prendra le moins d'espace possible.

Nous ajoutons ce widget (un bouton) à la ligne 23 le plus normalement possible : avec un add().

Dernier point important à remarquer :
j'utilise la méthode show_all() pour afficher tous les widgets de la fenêtre.
Cela m'évite d'avoir à appeler la méthode show() pour chaque widget.

Nous utiliserons toujours cette méthode lorsque c'est possible.

Voici le résultat :

Image utilisateur
Modification des paramètres du constructeur

Si vous voulez, après avoir appelé le constructeur, modifié les valeurs de xalign, yalign et compagnie, vous pouvez appeler la méthode set() avec les mêmes paramètres que le constructeur :

void Gtk::Alignment::set(Align xalign, Align yalign = Gtk::ALIGN_CENTER, float xscale = 1.0, float yscale = 1.0);

C'est tout ; passons maintenant à des conteneurs qui permettent d'ajouter plusieurs widgets.


Les différents conteneurs Les boîtes

Les boîtes

Le conteneur d'alignement Les boîtes pour les boutons

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>
Les boîtes verticales

Ce sont les Gtk::VBox.

Il est possible d'en créer une comme ceci :

Gtk::VBox boiteV;

Il est également possible d'en créer une avec des paramètres.
Le premier est un booléen qui indique si oui ou non les widgets prendront le même espace.

Donc, si le premier paramètre vaut true, chaque widget occupera un espace identique aux autres de la même boîte.

Le deuxième paramètre indique la taille minimale (une marge) entre chaque widget (en pixel).

Exemple :

Gtk::VBox boiteV(false, 10);

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.

Les boîtes horizontales

Ces boîtes se créent et s'utilisent de la même manière que les boîte verticales, donc je ne vais pas m'étendre sur leur fonctionnement.
Ce sont les Gtk::HBox (ah oui :p ?).

Ajout de widgets dans une boîte

Il y a deux méthodes pour ajouter un widget dans une boîte.

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 :

Image utilisateur

À gauche se trouve une partie du code et à droite, le résultat.

Pour commencer (1), nous ajoutons une étiquette à partir de la fin. C'est pourquoi elle se trouve à la fin dans le résultat final.

Ensuite (2), nous en ajoutons une autre à partir du début ; elle se trouve donc au début.

Jusque là, c'est facile à comprendre.

Puis (3), nous ajoutons une autre étiquette à partir de la fin.
Alors, elle est au-dessus de la première étiquette, car nous l'ajoutons à partir de la fin.
Cela signifie que la première étiquette ajoutée avec pack_end() se trouve à la fin, la deuxième, au-dessus de cette dernière, la troisième au-dessus de la deuxième et ainsi de suite.

Après (4), nous ajoutons une autre étiquette, mais, cette fois, à partir du début.
Elle se trouve donc après l'étiquette 2, car nous l'ajoutons à partir du début, mais après l'étiquette 2.

Et ainsi de suite.

Avec d'autres paramètres

Il est possible d'utiliser pack_start() et pack_end() avec d'autres paramètres.

Le deuxième paramètre est une constante définissant le comportement du widget lorsque le conteneur (donc la fenêtre) est redimensionné.
Nous pouvons utiliser l'une des trois constantes suivantes :

Le troisième paramètre est un espace supplémentaire (marge) autour de ce widget (en plus de l'espace défini à la création de la boîte).

Exemple
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    Gtk::Window fenetre;
    
    Gtk::VBox boiteV(false, 10); //Création d'une boîte verticale. Il y aura un minimum de 10px entre chaque widget.
    
    Gtk::Label etiquette4("Mot écrit en trois langues différentes."); //Création d'une étiquette.
    boiteV.pack_end(etiquette4); //Ajout d'un widget à la fin de la boîte.
    
    Gtk::HBox boiteH(true); //Création d'une boîte horizontale. Les widgets prendront le même espace.
    boiteV.pack_start(boiteH); //Ajout d'une boîte horizontale au début d'une boîte verticale (donc avant etiquette4).
    
    Gtk::Label etiquette1("Bonjour");
    Gtk::Label etiquette2("Hello");
    Gtk::Label etiquette3("Hola");
    
    boiteH.pack_start(etiquette1); //Ajout d'un widget au début de la boîte.
    boiteH.pack_start(etiquette2); //Ajout d'un widget après le premier.
    boiteH.pack_start(etiquette3, Gtk::PACK_SHRINK); //L'argument Gtk::PACK_SHRINK est ignoré puisque chaque widget a le même espace.
    
    fenetre.add(boiteV); //Ajout de la boîte à la fenêtre.
    
    fenetre.show_all(); //Afficher tous les widgets.
    
    Gtk::Main::run(fenetre);
    
    return 0;
}

Voilà ce que ça donne :

Image utilisateur

À 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.

Le reste est facile à comprendre (si vous avez bien suivi :p ).

Sauf peut-être à la ligne 25.
À cette ligne, nous demandons à ce que le widget prenne le moins d'espace possible (Gtk::PACK_SHRINK), mais, lorsque nous avons créé la boîte, nous avions dit que nous voulions que tous les widgets aient la même taille.
Donc, ce paramètre (Gtk::PACK_SHRINK) est ignoré.

En effet, le widget ne prend pas l'espace minimum, car il a une bordure invisible autour de lui.
S'il n'y avait pas cette bordure, le mot "Hola" serait collé au bord droit de la fenêtre (essayez pour mieux comprendre).

Pour bien comprendre le principe de boîtes imbriquées, j'ai coloré l'image précédente :

Image utilisateur

En rouge, nous voyons la boîte verticale et en bleu, la boîte horizontale.

Exercice

Je vais vous faire travailler un petit peu.

Essayez de reproduire cette fenêtre en utilisant des boîtes imbriquées :

Image utilisateur

Le conteneur d'alignement Les boîtes pour les boutons

Les boîtes pour les boutons

Les boîtes Les tableaux

Les boîtes pour les boutons

Le problème des boutons

Si vous utilisez les boîtes que nous avons vues dans la sous-partie précédente pour y mettre des boutons, cela n'est pas très... esthétique :euh: .

Le problème est surtout lorsque nous redimensionnons la boîte.

Voyons ce que cela peut donner :

Image utilisateur

Les boutons s'agrandissent.

Même en utilisant Gtk::PACK_SHRINK, ce n'est pas super :

Image utilisateur

Les boutons s'agrandissent horizontalement et laissent un espace vide en bas.

Utilisons les boîtes faites pour les boutons !

Il y a des boîtes qui sont faites exprès pour les boutons : les Gtk::ButtonBox.

Il en existe deux sortes :

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>

Vous pouvez utiliser ces boîtes comme celles de la sous-partie précédente.
Voici un résultat possible :

Image utilisateur

C'est beaucoup mieux, n'est-ce pas ?

Pour créer une boîte horizontale pour les boutons, faites comme ceci :

Gtk::HButtonBox boiteBoutonsH;
Modification de l'arrangement des boutons
set_layout()

Ceci permet de modifier l'arrangement des boutons dans la boîte.
Nous pouvons décider, par exemple, des les centrer ou de les mettre à la fin.

Pour que les boutons soient à la fin de la boîte (à droite ou en bas), appelez-la comme ceci :

boiteBoutonsH.set_layout(Gtk::BUTTONBOX_END);

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()) :

Image utilisateur

Voici les autres constantes :

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);
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>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    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::HButtonBox boiteBoutonsH(Gtk::BUTTONBOX_END, 10);
    
    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);
    
    //Ajout de boutons à la boîte.
    boiteBoutonsH.pack_start(boutonOptions);
    boiteBoutonsH.pack_start(boutonAide);
    boiteBoutonsH.pack_start(boutonFermer);
    
    //Rendre un bouton secondaire, donc à part des autres. ATTENTION, ce widget doit déjà être ajouté à la boîte pour que cela fonctionne.
    boiteBoutonsH.set_child_secondary(boutonOptions);
    
    fenetre.add(boiteBoutonsH); //Ajout de la boîte à la fenêtre.
    
    fenetre.show_all();
    
    Gtk::Main::run(fenetre);
    
    return 0;
}

Il n'y a rien de bien compliqué ; je vous ai tout expliqué ce qui se passe.

Avec les commentaires, vous devriez comprendre. ;)

Voici ce que ça donne :

Image utilisateur

Le bouton Options (à gauche) est secondaire.


Les boîtes Les tableaux

Les tableaux

Les boîtes pour les boutons La barre d'onglets

Les tableaux

Les tableaux permettent d'organiser les widgets à la manière d'un... tableau.

Création d'un tableau

N'oubliez pas d'inclure la classe appropriée :

#include <gtkmm/table.h>

Pour créer un tableau, nous devons lui dire combien de colonnes et de rangées il aura :

Gtk::Table tableau(1, 2);

Ceci crée un tableau d'une rangée et de deux colonnes.

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.

Ajout de widgets au tableau

Pour bien comprendre comment ajouter des widgets à un tableau, vous devrez comprendre ceci :
À l'instar d'un graphique mathématique, un tableau a des coordonnées.
Son point de départ est (0, 0) et est situé en haut à gauche (comme le quadrant 4 d'un graphique en valeurs absolues).
Si vous comprenez ceci, vous n'aurez aucun problème ;) .

Voici une image pour illustrer le tout :

Image utilisateur

Le point (0, 0) se trouve en haut à gauche du premier bouton.
Le point (1, 1) se trouve en bas à droite de ce même bouton.

Les coordonnées d'un widget sont de (x1, y1) à (x2, y2).
Dans le cas du premier bouton, c'est de (0, 0) à (1, 1).

Voyons le prototype de la méthode pour ajouter un bouton :

void Gtk::Table::attach(Widget& child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, AttachOptions xoptions = FILL|EXPAND, AttachOptions yoptions = FILL|EXPAND, guint xpadding = 0, guint ypadding = 0);

Aïe Caramba :p !

Je vais vous détailler tout ceci :

Un exemple ne vous ferait pas de mal, je crois. :lol:

Mais avant, je vais vous dire ce que nous pouvons donner comme xoptions et yoptions :

Nous pouvons combiner les deux premiers avec un |.

Si nous voulons que notre widget ne prenne qu'une seule rangée de large et une seule colonne de haut et qu'il se trouve en haut à gauche, nous devrons faire comme ceci :

tableau.attach(bouton1, 0, 1, 0, 1);

Le widget est de (0, 0) à (1, 1).

Et si nous voulons qu'il prenne le moins d'espace possible, ce serait comme ceci :

tableau.attach(bouton1, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK);

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);

Le widget est de (2, 1) à (4, 2).

Et si nous voulons qu'il ait un espace horizontal de 5px et vertical de 10px, ce serait :

tableau.attach(bouton1, 2, 4, 1, 2, Gtk::FILL | Gtk::EXPAND, Gtk::FILL | Gtk::EXPAND, 5, 10);
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::Main app(argc, argv);
    
    Gtk::Window fenetre;
    
    Gtk::Table tableau(1, 2); //Création d'un tableau de 1 case par 2 cases, dont les cases ne sont pas nécessairement de même grandeur.
    tableau.resize(2, 2); //Redimensionner le tableau (2 par 2).
    fenetre.add(tableau);
    
    Gtk::Button bouton1("Bouton 1");
    Gtk::Button bouton2("Bouton 2");
    Gtk::Button bouton3("Bouton 3");
    bouton1.set_can_focus(false);
    bouton2.set_can_focus(false);
    bouton3.set_can_focus(false);
    
    tableau.attach(bouton1, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK); //Ajout d'un widget dans le tableau : ce widget sera de (0, 0) à (1, 1). Le widget prend le moins d'espace possible.
    tableau.attach(bouton2, 1, 2, 0, 1); //Le widget prend l'espace qui lui est alloué et tout autre espace additionnel.
    tableau.attach(bouton3, 0, 2, 1, 2, Gtk::FILL, Gtk::FILL, 25, 10); //Le widget prend l'espace qui lui est alloué. Ajout d'espace (25px horizontal et 10px vertical) autour du widget.
    
    fenetre.show_all(); //L'habituel show_all() pour afficher tous les widgets.
    
    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 :p ).
Vous devriez bien comprendre avec les commentaires et ce screenshot :

Image utilisateur

Faites des tests pour bien comprendre comment les tableaux fonctionnent.
Je vous assure que cela pourrait vous aider. :-°


Les boîtes pour les boutons La barre d'onglets

La barre d'onglets

Les tableaux Les barres de défilement

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 :D ).

C'est très pratique pour pouvoir visualiser ou éditer plusieurs documents "en même temps".

Création d'une barre d'onglets

Avant tout, vous devez inclure la classe Gtk::Notebook :

#include <gtkmm/notebook.h>

C'est très simple ; il vous suffit de créer un objet de type Gtk::Notebook :

Gtk::Notebook barreOnglets;
Quelques méthodes utiles
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() :

Image utilisateur

Vous voyez, il y a des flèches à gauche et à droite pour naviguer.

Cette méthode s'appelle le plus simplement du monde :

barreOnglets.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 :

Image utilisateur
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.

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.

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 :

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"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    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>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void modifierTitre(Gtk::Widget* page, int numPage);
        
    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

Voici quelques explications :
modifierTitre() est une fonction de rappel.
Elle modifiera le titre lors du changement d'onglets de la première barre d'onglets.

Ceci crée une image :

Gtk::Image imageNouveau;

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);

    /*
    * Création de la barre d'onglets
    * */

    //S'il y a trop d'onglets, nous pouvons naviguer à l'aide de flèche.
    barreOnglets.set_scrollable();
    //Ajouter un menu contextuel lors du clic-droit pour la navigation.
    barreOnglets.popup_enable();
    //Empêcher la barre d'avoir le focus.
    barreOnglets.set_can_focus(false);

    //Définir le groupe de la barre d'onglets (pour pouvoir échanger des onglets avec d'autres barre d'onglets).
    barreOnglets.set_group_name("onglets");

    //Ajout d'une page
    //Ajout de texte1 avec "Page 1" écrit sur l'onglet.
    barreOnglets.append_page(texte1);
    barreOnglets.set_tab_reorderable(texte1); //Cet onglet peut être réordonné.
    barreOnglets.set_tab_detachable(texte1); //Cet onglet peut être détaché.

    //Ajout de texte2 avec "Page 2" écrit sur l'onglet.
    barreOnglets.append_page(texte2);
    barreOnglets.set_tab_reorderable(texte2);
    barreOnglets.set_tab_detachable(texte2);

    //Ajout de texte3 avec "Onglet 3" écrit sur l'onglet.
    barreOnglets.append_page(texte3, "Onglet 3");
    //Cet onglet ne peut pas être réordonné, ni détaché.

    //Ajout de texte4 avec "Page 4" écrit sur l'onglet. Un clic sur Alt-4 nous amène à cet onglet.
    barreOnglets.append_page(texte4, "Page _4", true);
    //Le trait de soulignement (_) est situé devant le raccourci.
    barreOnglets.set_tab_reorderable(texte4);
    barreOnglets.set_tab_detachable(texte4);

    barreOnglets.append_page(texte5, "Page 5");
    barreOnglets.set_tab_reorderable(texte5);
    barreOnglets.set_tab_detachable(texte5);

    barreOnglets.append_page(texte6, "Page 6");
    barreOnglets.set_tab_reorderable(texte6);
    barreOnglets.set_tab_detachable(texte6);

    //Enlever le 6e onglet. Il est aussi possible de donner le widget en paramètre.
    barreOnglets.remove_page(5);

    /*
    * Création d'un bouton d'action.
    * */
    boutonNouvelOnglet.set_can_focus(false);
    boutonNouvelOnglet.set_image(imageNouveau); //Modifier l'image du bouton.

    boutonSupprimerOnglet.set_can_focus(false);
    boutonSupprimerOnglet.set_image(imageSupprimer); //Modifier l'image du bouton.

    //Ajout d'un widget d'action à la fin de la barre d'onglets.
    barreOnglets.set_action_widget(&boutonNouvelOnglet, Gtk::PACK_END);

    //Ajout d'un widget d'action au début de la barre d'onglets.
    barreOnglets.set_action_widget(&boutonSupprimerOnglet, Gtk::PACK_START);

    boutonNouvelOnglet.show(); //Il ne faut pas oublier de l'afficher manuellement.
    boutonSupprimerOnglet.show();
    /*
    * Création d'un bouton d'action.
    * */

    boiteV.pack_start(barreOnglets);

    /*
    * Création de la barre d'onglets
    * */

    /*
    * Création d'une deuxième barre d'onglets
    * */


    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);

    /*
    * Création d'une deuxième barre d'onglets
    * */

    //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();
}

void Fenetre::modifierTitre(Gtk::Widget* page, int numPage) {
    //Modifier le titre de la fenêtre selon l'onglet choisi.
    set_title(barreOnglets.get_tab_label_text(*barreOnglets.get_nth_page(numPage)));
}

Voici le résultat :

Image utilisateur

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.

La première barre d'onglets

Par la suite, nous créons une barre d'onglet, puis, nous la modifions quelque peu.

Notez que le troisième onglet ne peut pas être déplacé, ni détaché (essayez de le faire :p ).

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 58, nous enlevons le 6e onglet ainsi que son widget.

Plus loin, nous ajoutons une image différente pour chaque bouton avec set_image() et nous ajoutons les boutons à la barre d'onglets avec set_action_widget().
Il ne faut pas oublier des afficher avec show() !

Nous faisons de même avec un second bouton.

La deuxième barre d'onglets

Nous la créons et la modifions ; vous devriez comprendre facilement.

Connexions des signaux aux fonctions de rappel

Aux lignes 108 et 109, nous connectons le clic sur chacun des boutons à une méthode de Gtk::Notebook qui permet d'avancer et de reculer dans la barre d'onglets.

À 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()).

Enfin, nous affichons tous les widgets avec show_all().

Voyons notre fonction de rappel qui comporte quelques nouveautés :

void Fenetre::modifierTitre(Gtk::Widget* page, int numPage) {
    //Modifier le titre de la fenêtre selon l'onglet choisi.
    set_title(barreOnglets.get_tab_label_text(*barreOnglets.get_nth_page(numPage)));
}

Elle appelle set_title() afin de modifier le titre de la fenêtre.

En paramètre, nous avons... un gros morceau :

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.

Veuillez noter que nous aurions pu également utiliser cette ligne :

set_title(barreOnglets.get_tab_label_text(*page));

Elle est plus courte, mais elle vous montre moins de méthodes. :-°

C'est tout, mais je vous demande un petit exercice. :diable:

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.


Les tableaux Les barres de défilement

Les barres de défilement

La barre d'onglets Les boîtes de dialogue

Les barres de défilement

Si un widget prend trop de place pour être affiché au complet, nous pouvons utiliser les barres de défilement pour n'afficher qu'une partie de ce widget, mais avec des barres de défilement pour se déplacer sur ce widget.

Vous avez un exemple sous les yeux :
Eh oui ! Votre navigateur web utilise des barres de défilement, car la plupart des pages Web sont trop grandes pour être affichées dans la fenêtre sans ces barres.
Vous pouvez, grâces à elles, vous déplacer dans la page Web.

Utilisation

Avant toute utilisation, il faut inclure la bonne classe :

#include <gtkmm/scrolledwindow.h>

Les barres de défilement s'utilise le plus simplement du monde.

Il suffit de créer un objet de type Gtk::ScrolledWindow et d'y ajouter un widget.

C'est un autre conteneur à enfant unique (il ne peut contenir qu'un seul widget).

Exemple :

Gtk::ScrolledWindow barresDeDefilement;

Une de ses méthodes mérite votre attention :

void Gtk::ScrolledWindow::set_policy(PolicyType hscrollbar_policy, PolicyType vscrollbar_policy);

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>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    
    Gtk::Window fenetre;
    fenetre.set_border_width(10);
    
    //Création d'un conteneur ayant des barres de défilement.
    Gtk::ScrolledWindow barresDeDefilement;
    //Afficher les barres de défilement seulement lorsque c'est nécessaire.
    barresDeDefilement.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    
    fenetre.add(barresDeDefilement);
    
    Gtk::TextView zoneDeTexte; //Création d'une zone de texte.
    
    barresDeDefilement.add(zoneDeTexte); //Ajout de la zone de texte au conteneur.
    
    fenetre.show_all();
    
    Gtk::Main::run(fenetre);
    
    return 0;
}

À la ligne 13, nous créons le widget de type Gtk::ScrolledWindow.
À l’instruction suivante, nous appelons la méthode set_policy() pour que les barres de défilement ne s'affichent que lorsque le widget n'est pas visible au complet dans la fenêtre.

Dans cet exemple, nous affichons une zone de texte multiligne.
Pour voir les barres de défilement, il faut entrer du texte jusqu'au bord de la fenêtre.

Cela donne ceci :

Image utilisateur

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 :soleil: ).

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. :diable:

Le jeu final peut ressembler à ceci :

Image utilisateur

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 :p ) ?
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.


La barre d'onglets Les boîtes de dialogue

Les boîtes de dialogue

Les barres de défilement Les boîtes de dialogue de message

Un des moyens pour interagir avec l'utilisateur est d'utiliser les boîtes de dialogue.

Les boîtes de dialogue peuvent afficher des informations, des erreurs, demander une question, une police d'écriture, etc.

Voyons comment cela fonctionne.

Les boîtes de dialogue de message

Les boîtes de dialogue Boîte de dialogue de sélection de couleur

Les boîtes de dialogue de message

Les boîtes de dialogue de message servent à afficher des informations ou à demander des questions.

Il en existe de quatre types :

Information

Avant d'utiliser une de ces quatre boîtes de dialogue, vous devez inclure l'entête suivant :

#include <gtkmm/messagedialog.h>

Voici une boîte de dialogue d'information :

Image utilisateur
Création

Pour créer une boîte de dialogue d'information, vous devez créer un objet de type Gtk::MessageDialog :

Gtk::MessageDialog dialogue(*this, "<big><b>Ceci</b> est un message d'information.</big>", true);

Le premier paramètre est le parent (en l'occurrence, la fenêtre de l'application).
Le deuxième paramètre est le texte écrit sur la boîte de dialogue et le troisième est un booléen qui indique si nous voulons utiliser le Pango Markup Language.

Qu'est-ce que ce langage ?

C'est un langage de balisage qui permet de mettre en forme le texte.
Par exemple, le texte se trouvant entre <b> et </b> sera en gras.

Pour connaître toutes les balises de ce langage, cliquez ici (en anglais).
Dans le prochain chapitre, je vous présenterai la plupart de ces balises.

Si nous mettons false au troisième paramètre (par défaut), nous ne pouvons pas utiliser ce langage.

Méthodes

Comme ces boîtes de dialogue héritent de Gtk::Window, nous pouvons utiliser des méthodes de cette classe.

Par exemple, pour modifier le titre de la boîte de dialogue, nous pouvons faire ceci :

dialogue.set_title("Information");

Une autre méthode mérite votre attention :

dialogue.set_secondary_text("Et <i>ceci</i> est le texte secondaire qui explique quelque chose.", true);

Cette méthode permet d'ajouter un texte secondaire.

Sans mise en forme, il est plus petit que le texte passé au constructeur et il se situe en bas de ce dernier.
Nous devons envoyer true au deuxième paramètre pour pouvoir utiliser le Pango Markup Language.

Enfin, nous devons lancer la boîte de dialogue pour qu'elle s'affiche :

dialogue.run();

Voici une méthode qui affiche une boîte de dialogue :

void Fenetre::afficherInfo() {
    //Création d'une boîte de dialogue d'information.
    Gtk::MessageDialog dialogue(*this, "<big><b>Ceci</b> est un message d'information.</big>", true);
    //Comme cette classe hérite de Gtk::Window, il est possible d'appeler des méthodes de cette dernière classe.
    dialogue.set_title("Information");
    //Ajouter un texte secondaire.
    dialogue.set_secondary_text("Et <i>ceci</i> est le texte secondaire qui explique quelque chose.", true);
    dialogue.run(); //Lancer la boîte de dialogue.
}
Avertissement

Les autres boîtes de dialogue s'utilisent de la même manière, sauf que nous devons ajouter un paramètre au constructeur pour indiquer de quelle boîte de dialogue il s'agit.

Cette boîte ressemble à ceci :

Image utilisateur

Voici comment créer une boîte de dialogue d'avertissement :

Gtk::MessageDialog dialogue(*this, "<big><b><i>Ceci</i> est un message d'alerte.</b></big>", true, Gtk::MESSAGE_WARNING);

Nous ajoutons une constante à la fin pour indiquer de quelle boîte de dialogue il s'agit.

Nous pouvons, bien sûr, appeler les méthodes set_title() et set_secondary_text().
Et nous devons toujours appeler la méthode run() pour l'afficher.

Erreur

Cette fois, la constante est Gtk::MESSAGE_ERROR.

Cette boîte ressemble à ceci :

Image utilisateur

Pour toutes ces boîtes de dialogue, nous pouvons choisir quel(s) bouton(s) nous voulons voir afficher.
Pour cela, nous devons ajouter un paramètre dans le constructeur.

Par exemple, pour créer une boîte de dialogue d'erreur avec un bouton Fermer, nous devons la créer comme ceci :

Gtk::MessageDialog dialogue(*this, "Erreur fatale !", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);

Ici, j'ai mis le troisième paramètre à false, car nous n'utilisons pas le Pango Markup Language.

Voici une méthode qui affiche cette boîte de dialogue, la personnalise et ferme l'application lorsque l'utilisateur ferme cette boîte :

void Fenetre::afficherErreur() {
    //Utilisation d'un bouton Fermer à la place de Valider.
    Gtk::MessageDialog dialogue(*this, "Erreur fatale !", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
    dialogue.set_title("Erreur fatale");
    dialogue.set_secondary_text("Veuillez redémarrer !");
    dialogue.run();
    Gtk::Main::quit(); //Fermeture du programme
}
Question

Pour terminer cette sous-partie, voyons la boîte de dialogue de question.

Elle ressemble à ceci :

Image utilisateur

D'habitude, lorsque quelqu'un nous pose une question simple, nous répondons par "Oui" ou par "Non".
Eh bien, nous pouvons faire pareil avec une boîte de dialogue. :lol:

Au lieu d'utiliser la constante Gtk::BUTTONS_CLOSE, nous devrons utiliser Gtk::BUTTONS_YES_NO.

Voici toutes les constantes :

Cette boîte de dialogue s'utilise exactement comme les autres.
Cette fois, la constante est Gtk::MESSAGE_QUESTION.

Mais comment faire pour savoir si l'utilisateur a appuyé sur "Oui" ou sur "Non" ?

Nous devons vérifier la valeur que renvoie la méthode run().
Eh oui, cette méthode renvoie une valeur.

Nous ne l'avons pas récupéré jusqu'à présent, car elle nous était inutile, mais là, nous n'avons plus le choix.

Donc, nous devons appeler la méthode run() en récupérant ce qu'elle retourne :

int resultat = dialogue.run();

Ensuite, nous devons vérifier cette valeur.

Encore une fois, il y a des constantes pour vérifier ce que retourne cette méthode :

Vous pouvez trouver la liste complète ici.

Voici une méthode qui affiche la boîte de dialogue :

void Fenetre::afficherQuestion() {
    Gtk::MessageDialog dialogue(*this, "Voulez-vous fermer le programme ?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
    dialogue.set_title("Quitter ?");
    dialogue.set_secondary_text("Si vous répondez Oui, vous perdrez vos documents non enregistrés.");
    
    int resultat = dialogue.run(); //Cette méthode retourne le bouton sur lequel l'utilisateur a cliqué.
    if(resultat == Gtk::RESPONSE_YES) { //S'il a cliqué sur Oui.
        Gtk::Main::quit();
    }
    else if(resultat == Gtk::RESPONSE_NO) { //S'il a cliqué sur Non.
        dialogue.hide(); //Cacher l'autre boîte de dialogue pour faire plus professionnel.
        //Afficher une autre boîte de dialogue pour rire de l'utilisateur.
        Gtk::MessageDialog dialog(*this, "Ha Ha Ha !");
        dialog.set_title("Mdr");
        dialog.set_secondary_text("Je ne t'écoute pas !");
        dialog.run();
        Gtk::Main::quit();
    }
}

Au début, nous créons la boîte de dialogue de question et nous la personnalisons.

Ensuite, nous récupérons la valeur que retourne la méthode run().

Puis, nous vérifions ce qu'elle vaut.

Si elle vaut Gtk::RESPONSE_YES, cela signifie que l'utilisateur a cliqué sur le bouton Oui.
Dans ce cas, nous fermons le programme avec Gtk::Main::quit();.

S'il a cliqué sur Non, nous cachons la boîte de dialogue avec la méthode hide(), nous nous moquons de l'utilisateur et fermons tout de même le programme. :lol:

Voilà, c'est tout pour les boîtes de dialogue de message.

Passons à la suite.


Les boîtes de dialogue Boîte de dialogue de sélection de couleur

Boîte de dialogue de sélection de couleur

Les boîtes de dialogue de message Boîte de dialogue de sélection de police

Boîte de dialogue de sélection de couleur

Dans gtkmm, il existe d'autres boîtes de dialogue déjà prêtes à utiliser par le programmeur.

Dans cette sous-partie, nous verrons une boîte de dialogue qui permet à l'utilisateur de choisir une couleur.

Le constructeur

Le constructeur ne prend qu'un seul paramètre, une chaîne de caractères indiquant le titre de la boîte de dialogue :

Gtk::ColorSelectionDialog dialogue("Sélection d'une couleur");

Pour utiliser cette classe, vous devez l'inclure :

#include <gtkmm/colorselection.h>
Méthodes
get_color_selection()

Cette méthode nous permet d'obtenir le widget de sélection de couleur.
Nous devons l'utiliser comme ceci :

Gtk::ColorSelection* selectionCouleur = dialogue.get_color_selection();

À quoi ça sert d'obtenir ce widget ?

Ça nous permet d'obtenir la couleur que l'utilisateur a choisie, son opacité ainsi que d'autres informations comme la couleur précédente.

Ensuite, nous pouvons obtenir la couleur sélectionnée avec get_current_rgba() :

selectionCouleur->get_current_rgba();

Cette méthode nous donne un objet de type Gdk::RGBA.

Que pouvons-nous faire de cet objet ?

Nous pouvons, par exemple, modifier la couleur du texte sur une étiquette :

etiquette.override_color(selectionCouleur->get_current_rgba(), Gtk::STATE_FLAG_NORMAL);

Voyons un exemple complet :

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/colorselection.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void dialogueCouleur();
        
    private :
        Gtk::VBox boiteV;
        Gtk::Button bouton;
        Gdk::RGBA couleur;
        Gtk::Label etiquette;
};

#endif

Nous créons une fonction de rappel (dialogueCouleur()) qui affichera la boîte de sélection de couleur.

De plus, nous créons une boîte verticale, un bouton et une étiquette.

Enfin, nous créons un objet de type Gdk::RGBA pour retenir la couleur choisie par l'utilisateur.

Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton(Gtk::Stock::SELECT_COLOR), etiquette("Texte coloré") {
    set_position(Gtk::WIN_POS_CENTER);
    
    add(boiteV);
    
    bouton.set_can_focus(false);
    
    boiteV.pack_start(bouton, Gtk::PACK_SHRINK);
    boiteV.pack_start(etiquette);
    
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueCouleur));
    
    show_all();
}

void Fenetre::dialogueCouleur() {
    Gtk::ColorSelectionDialog dialogue("Sélection d'une couleur");
    
    //Récupérer le widget de sélection de couleur.
    Gtk::ColorSelection* selectionCouleur = dialogue.get_color_selection();
    //Modifier la couleur du widget se sélection de couleur.
    selectionCouleur->set_current_rgba(couleur);
    
    int resultat = dialogue.run();
    //Si l'utilisateur clique sur Valider.
    if(resultat == Gtk::RESPONSE_OK) {
        //Obtenir la couleur sélectionnée.
        couleur = selectionCouleur->get_current_rgba();
        //Modifier la couleur de l'étiquette.
        etiquette.override_color(couleur, Gtk::STATE_FLAG_NORMAL);
    }
}

Dans le constructeur, nous modifions les widgets à afficher, les ajoutons dans la boîte verticale et connectons le bouton à notre fonction de rappel personnalisée.

Dans cette fonction, nous commençons par créer une boîte de dialogue de sélection de couleur.

Ensuite, nous récupérons le widget de sélection de couleur et modifions la couleur actuellement choisie avec la méthode set_current_rgba().
Nous lui transmettons en paramètre un objet de type Gdk::RGBA, une couleur.

Pourquoi modifions-nous la couleur choisie dans ce widget ?

Nous faisons cela, car nous voulons que la couleur choisie lorsque nous ouvrons la fenêtre soit celle que nous avons choisie la fois précédente.

En d'autres termes, si nous choisissons une couleur rouge la première fois que nous ouvrons cette boîte de dialogue, nous voulons que la deuxième fois, la couleur rouge soit déjà sélectionnée.

Puis, si l'utilisateur clique sur Valider, nous enregistrons la couleur qu'il a choisie avec la méthode get_current_rgba() dans notre attribut.
En enregistrant cette couleur, nous pouvons, comme dit précédemment, modifier la couleur choisie sur le widget de sélection de couleur (ça en fait, des couleurs, dans cette phrase :lol: ).

Puis, nous modifions la couleur du texte de l'étiquette avec :

etiquette.override_color(couleur, Gtk::STATE_FLAG_NORMAL);

Voilà ce que ça donne :

Image utilisateur

Les boîtes de dialogue de message Boîte de dialogue de sélection de police

Boîte de dialogue de sélection de police

Boîte de dialogue de sélection de couleur Boîte de dialogue de sélection de fichier(s)

Boîte de dialogue de sélection de police

Il existe une boîte de dialogue qui permet à l'utilisateur de choisir une police.

Voyons comment elle fonctionne.

Constructeur

Il prend le même paramètre que la boîte de dialogue de sélection de couleur :

Gtk::FontChooserDialog dialogue("Sélection d'une police");

Vous devez inclure l'entête :

#include <gtkmm/fontchooserdialog.h>
Méthodes

La méthode get_font_desc() permet d'obtenir la police choisie par l'utilisateur.
Cette méthode retourne un objet Pango::FontDescription :

Pango::FontDescription police = dialogue.get_font_desc();

Ensuite, nous pouvons modifier la police de l'étiquette avec la méthode override_font().

Cette méthode prend un objet de type Pango::FontDescription (le même que celui retourné par get_font_desc()).
Pour utiliser cet objet, vous devez inclure l'entête :

#include <pangomm/fontdescription.h>
Exemple

Je vous explique comment utiliser cet objet avec un exemple.

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/fontchooserdialog.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

#include <pangomm/fontdescription.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void dialoguePolice();
        
    private :
        Gtk::VBox boiteV;
        Gtk::Button bouton;
        Gtk::Label etiquette;
        Pango::FontDescription police;
};

#endif

C'est très semblable à l'exemple précédent, sauf que, cette fois, ce n'est pas un objet Gdk::RGBA, mais Pango::FontDescription.

Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton(Gtk::Stock::SELECT_FONT), etiquette("Texte formatté") {
    add(boiteV);
    
    bouton.set_can_focus(false);
    
    boiteV.pack_start(bouton, Gtk::PACK_SHRINK);
    boiteV.pack_start(etiquette);
    
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialoguePolice));
    
    show_all();
}

void Fenetre::dialoguePolice() {
    //Création d'une boîte de dialogue de sélection de police.
    Gtk::FontChooserDialog dialogue("Sélection d'une police");
    //Modifier la police.
    dialogue.set_font_desc(police);
    
    int resultat = dialogue.run();
    //Si l'utilisateur clique sur Valider.
    if(resultat == Gtk::RESPONSE_OK) {
        //Obtenir la police de caractères.
        police = dialogue.get_font_desc();
        //Modifier la police de l'étiquette.
        etiquette.override_font(police);
    }
}

Encore une fois, nous allons nous intéresser à la fonction de rappel.

Après avoir créer la boîte de dialogue, nous modifions la police choisie avec :

dialogue.set_font_desc(police);

Comme pour l'exemple précédent, nous voulons que la police du texte soit la police affichée par la boîte de dialogue.

Ensuite, si l'utilisateur clique sur Valider, nous obtenons la police :

police = dialogue.get_font_desc();

Nous enregistrons cette police dans notre attribut, puis nous modifions la police de l'étiquette.

Voici ce que ça donne :

Image utilisateur

C'est tout pour cette boîte de dialogue.
Comme pour la boîte de sélection de couleur, il existe aussi un bouton qui affiche une boîte de sélection de police : Gtk::FontButton.


Boîte de dialogue de sélection de couleur Boîte de dialogue de sélection de fichier(s)

Boîte de dialogue de sélection de fichier(s)

Boîte de dialogue de sélection de police La boîte de dialogue À propos

Boîte de dialogue de sélection de fichier(s)

Les boîtes de dialogue de sélection de fichier(s) permettent de choisir un fichier ou des fichiers pour l'ouverture et un pour la sauvegarde.
Elles permettent également l'ouverture et la création de dossier(s).

Constructeur

Avant tout, il faut inclure le bon entête :

#include <gtkmm/filechooserdialog.h>

Ceci crée une boîte de dialogue d'ouverture de fichier(s) :

Gtk::FileChooserDialog dialogue(*this, "Ouverture d'un fichier");

Si nous voulons créer une boîte de dialogue pour la sauvegarde, la création ou l'ouverture de dossier(s), nous devons ajouter un troisième paramètre :

Gtk::FileChooserDialog dialogue(*this, "Sauvegarde d'un fichier", Gtk::FILE_CHOOSER_ACTION_SAVE);

Ceci crée une boîte de dialogue de sauvegarde de fichier.

Voici toutes les constantes possibles :

Méthodes
Pour l'ouverture de fichier(s)

La méthode set_select_multiple() permet la sélection de plusieurs fichiers.

Pour la sauvegarde de fichier

La méthode set_do_overwrite_confirmation() demande la confirmation de l'utilisateur lorsqu'il est sur le point d'écraser un fichier existant.

La méthode set_create_folders() permet la création de dossier(s).

Pour tous les types de dialogue de sélection de fichier(s)/dossier

La méthode dialogue.get_filename() renvoie le chemin abosolu du fichier sélectionné.

Lorsque nous pouvons sélectionner plusieurs fichiers, nous devons récupérer la liste des fichiers sélectionnés de cette manière :

std::vector<std::string> liste(dialogue.get_filenames());

La méthode set_show_hidden() affiche les fichiers cachés.

La méthode set_current_folder() permet de modifier le dossier actuel.

L'utilisation la plus commune est probablement celle-ci :

dialogue.set_current_folder(Glib::get_home_dir());

La boîte de dialogue de sélection de fichier(s) sera au répertoire personnel de l'utilisateur.

Par défaut, une boîte de dialogue de sélection de fichier(s)/dossier n'a aucun bouton.
Nous devons donc les ajouter nous-mêmes.

Il y a une méthode faite exprès pour cela : add_button().
Le premier paramètre peut être soit un objet std::string, soit un Stock Item.
Le deuxième doit être une des constantes de réponse que j'ai listées plus haut.

Pour rappel, les revoici :

Par exemple, ceci ajoute un bouton Annuler et un Ouvrir :

dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
dialogue.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);

Pourquoi mets-tu Gtk::RESPONSE_OK au lieu de Gtk::RESPONSE_OPEN ?

Parce que cette dernière n'existe pas. :p
Nous ne sommes pas obligés de respecter les noms, nous pouvons mettre n'importe quelle constante ; nous pourrions même mettre un int.

Comme pour les autre boîtes de dialogue, nous devons récupérer ce que retourne la méthode run(), puis l'évaluer :

int resultat = dialogue.run();
if(resultat == Gtk::RESPONSE_OK) {
}

La dernière méthode que je vais vous montrer permet d'ajouter un filtre sur les fichiers.

Qu'est-ce qu'un filtre

En utilisant un filtre, nous pouvons, par exemple, demander à ce que la boîte de dialogue n'affiche que les fichiers ayant l'extension .c, .cpp et .h.

Ça nous permet d'afficher seulement les fichiers que nous voulons.

Pouvons-nous utiliser plusieurs filtres ?

Mais bien sûr. :D
Premièrement, ajoutez cet entête dans votre fichier Fenetre.hpp :

#include <gtkmm/filefilter.h>

Ceci crée un filtre :

Glib::RefPtr<Gtk::FileFilter> filtreFichierCCPP = Gtk::FileFilter::create();

Après avoir créé un filtre, nous devons lui donner un nom :

filtreFichierCCPP->set_name("Fichiers C/C++");

Ce nom sera affiché dans la liste déroulante.

Ensuite, nous devons ajouter des types MIME.
Par exemple, pour les fichiers C/C++ :

filtreFichierCCPP->add_mime_type("text/x-c");
filtreFichierCCPP->add_mime_type("text/x-c++");
filtreFichierCCPP->add_mime_type("text/x-c-header");

Finalement, nous devons ajouter le filtre à la boîte de dialogue :

dialogue.add_filter(filtreFichierCCPP);

Nous pouvons maintenant voir notre filtre dans une liste déroulante près des boutons :

Image utilisateur

Et si nous voulons créer un filtre pour tous les fichiers, comment faire ?

Glib::RefPtr<Gtk::FileFilter> filtreTout = Gtk::FileFilter::create();
filtreTout->set_name("Tous les fichiers");
filtreTout->add_pattern("*");
dialogue.add_filter(filtreTout);

Cette fois, nous utilisons add_pattern().
Il faut lui fournir une expression correspondant aux fichiers que nous voulons afficher.

L’étoile (*) signifie n’importe quelle combinaison de caractères.

Il existe d’autres symboles tels que le point d’interrogation (?) qui signifie n’importe quel caractère.
Il est possible d’utiliser les crochets ([ et ]) à la manière des expressions rationnelles :
Par exemple, "[0-9]" signifie n’importe quel chiffre.

Exemple

Voyons un exemple qui fonctionne.

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <fstream>
#include <vector>

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/filefilter.h>
#include <gtkmm/main.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/stock.h>
#include <gtkmm/textbuffer.h>
#include <gtkmm/textview.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void dialogueOuvrirFichier();
        void dialogueOuvrirFichiers();
        void dialogueEnregistrerFichier();
        void ouvrirFichier(std::string nomFichier);
        
    private :
        Gtk::ScrolledWindow barresDeDefilement;
        Gtk::HButtonBox boiteH;
        Gtk::VBox boiteV;
        Gtk::Button boutonEnregistrer;
        Gtk::Button boutonOuvrir;
        Gtk::Button boutonOuvrirFichiers;
        Gtk::TextView zoneTexte;
};

#endif

Il n'y a pratiquement rien de nouveau pour vous dans ce fichier ; vous n'avez alors pas besoin de mes explications.

Dans cet exemple, je ne vais que vous écrire la méthode pour ouvrir un fichier.

Vous devrez écrire les deux autres dans le prochain TP (celle pour enregistrer un fichier et celle pour ouvrir plusieurs fichiers).

Fenetre.cpp

Comme ce fichier comporte plus de 100 lignes, je vais vous le donner méthode par méthode.

Commençons par le début :

#include "Fenetre.hpp"

Fenetre::Fenetre() : boutonEnregistrer(Gtk::Stock::SAVE), boutonOuvrir(Gtk::Stock::OPEN), boutonOuvrirFichiers("Ouverture de fichiers") {
    //Configurer la fenêtre.
    set_position(Gtk::WIN_POS_CENTER);
    set_default_size(480, 360);
    add(boiteV);
    
    //Empêcher les boutons d'avoir le focus.
    boutonOuvrir.set_can_focus(false);
    boutonOuvrirFichiers.set_can_focus(false);
    boutonEnregistrer.set_can_focus(false);
    
    //Configurer les barres de défilement.
    barresDeDefilement.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    barresDeDefilement.add(zoneTexte);
    
    //Ajout de widgets dans les boîtes.
    boiteV.pack_start(barresDeDefilement);
    boiteV.pack_start(boiteH, Gtk::PACK_SHRINK);
    
    boiteH.pack_start(boutonOuvrir);
    boiteH.pack_start(boutonOuvrirFichiers);
    boiteH.pack_start(boutonEnregistrer);
    
    //Connexion des signaux aux fonctions de rappel.
    boutonOuvrir.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueOuvrirFichier));
    boutonOuvrirFichiers.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueOuvrirFichiers));
    boutonEnregistrer.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueEnregistrerFichier));
    
    show_all();
}

Dans la liste d'initialisation, nous créons les boutons à partir de Stock Item ou de string.

Ensuite, nous ajoutons les widgets dans la fenêtre ; vous devriez bien comprendre.
Enfin, nous connectons le signal clicked() de chaque bouton à une fonction de rappel perso.

Voici la prochaine méthode :

void Fenetre::dialogueOuvrirFichier() {
    //Création d'une boîte de dialogue d'ouverture de fichier.
    Gtk::FileChooserDialog dialogue(*this, "Ouverture d'un fichier");
    
    //Ajout de filtres.
    Glib::RefPtr<Gtk::FileFilter> filtreFichierTexte = Gtk::FileFilter::create(); //Création du filtre.
    filtreFichierTexte->set_name("Fichiers textes"); //Lui donner un nom.
    filtreFichierTexte->add_mime_type("text/plain"); //Ajouter un type MIME.
    dialogue.add_filter(filtreFichierTexte); //Ajouter le filtre au dialogue.
    
    Glib::RefPtr<Gtk::FileFilter> filtreFichierCCPP = Gtk::FileFilter::create();
    filtreFichierCCPP->set_name("Fichiers C/C++");
    filtreFichierCCPP->add_mime_type("text/x-c");
    filtreFichierCCPP->add_mime_type("text/x-c++");
    filtreFichierCCPP->add_mime_type("text/x-c-header");
    dialogue.add_filter(filtreFichierCCPP);
    
    Glib::RefPtr<Gtk::FileFilter> filtreTout = Gtk::FileFilter::create();
    filtreTout->set_name("Tous les fichiers");
    filtreTout->add_pattern("*"); //Ajout d'une expression.
    dialogue.add_filter(filtreTout);
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    if(resultat == Gtk::RESPONSE_OK) {
        //Obtenir le nom du fichier.
        std::string nomFichier = dialogue.get_filename();
        ouvrirFichier(nomFichier);
    }
}

Cette méthode affiche une boîte de dialogue d'ouverture de fichier.

Premièrement, nous créons la boîte de dialogue.
Deuxièmement, nous créons des filtres pour les fichiers texte, les fichiers C/C++ et un pour tous les fichiers.
Puis, nous ajoutons des boutons et, si l'utilisateur clique sur Ouvrir, nous ouvrons le fichier avec une méthode que nous verrons plus bas.

Voici la prochaine méthode :

void Fenetre::dialogueOuvrirFichiers() {
    //Création d'une boîte de dialogue d'ouverture de fichiers.
    Gtk::FileChooserDialog dialogue(*this, "Ouverture de fichiers");
    
    //Permettre à l'utilisateur de sélectionner plusieurs fichiers.
    dialogue.set_select_multiple();
    dialogue.set_show_hidden(); //Afficher les fichiers cachés.
    //Se rendre au dossier personnel de l'utilisateur
    dialogue.set_current_folder(Glib::get_home_dir());
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    //Affichage de la liste des fichiers sélectionnés.
    if(resultat == Gtk::RESPONSE_OK) {
        std::vector<std::string> liste(dialogue.get_filenames());
        std::string texte;
        
        for(std::vector<std::string>::iterator it(liste.begin()) ; it != liste.end() ; ++it) {
            texte += *it + "\n";
        }
        
        dialogue.hide();
        
        Gtk::MessageDialog dialog(*this, "Voici les fichiers sélectionnés :");
        dialog.set_secondary_text(texte);
        dialog.run();
    }
}

Cette méthode n'ouvre pas plusieurs fichiers ; elle ne fait qu'afficher la liste des fichiers sélectionnés par l'utilisateur dans une boîte de dialogue de message.

Au début, nous créons la boîte de dialogue.
Ensuite, nous la configurons un peu.

N'oubliez pas que Glib::get_home_dir() retourne le dossier personnel de l'utilisateur.

Puis, nous ajoutons les mêmes boutons que précédemment.

Si l'utilisateur clique sur Ouvrir, nous créons une liste des fichiers sélectionnés.

À partir de cette liste, nous créons la variable texte et nous l'affichons dans une boîte de message.

Voici la prochaine méthode :

void Fenetre::dialogueEnregistrerFichier() {
    //Création d'une boîte de dialogue de sauvegarde de fichier.
    Gtk::FileChooserDialog dialogue(*this, "Sauvegarde d'un fichier", Gtk::FILE_CHOOSER_ACTION_SAVE);
    
    //Demande de confirmation lors du remplacement d'un fichier existant.
    dialogue.set_do_overwrite_confirmation();
    //Permettre la création de dossiers.
    dialogue.set_create_folders();
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    if(resultat == Gtk::RESPONSE_OK) {
        Gtk::MessageDialog dialog(*this, "Sauvegarde du fichier réussie");
        dialog.run();
    }
}

Encore une fois, cette méthode n'enregistre pas un fichier :-° ; elle affiche simplement un message à l'utilisateur lorsque celui-ci clique sur Enregistrer.

Après avoir créé la boîte de dialogue, nous la modifions un peu.

Ensuite, nous ajoutons les boutons et affichons un message si l'utilisateur clique sur Enregistrer.

Voici la dernière méthode (celle-ci fait vraiment quelque chose :lol: ) :

void Fenetre::ouvrirFichier(std::string nomFichier) {
    std::ifstream fichier(nomFichier.c_str(), std::ios::in);
    
    std::string texte, ligne;
    if(fichier) {
        
        while(getline(fichier, ligne)) {
            texte += ligne + "\n";
        }
        
        fichier.close();
    }
    else {
        Gtk::MessageDialog dialogue(*this, "Erreur lors de l'ouverture du fichier " + nomFichier + ".", false, Gtk::MESSAGE_INFO);
        dialogue.run();
    }
    
    //Obtenir le buffer de la zone de texte.
    Glib::RefPtr<Gtk::TextBuffer> buffer = zoneTexte.get_buffer();
    buffer->set_text(texte); //Modifier le texte du buffer.
}

Nous ouvrons le fichier et mettons son contenu dans la variable texte.
Enfin, nous modifions la zone de texte pour y afficher le texte (cette partie du code sera expliquée dans le prochain chapitre).

Voici le fichier au complet :

#include "Fenetre.hpp"

Fenetre::Fenetre() : boutonEnregistrer(Gtk::Stock::SAVE), boutonOuvrir(Gtk::Stock::OPEN), boutonOuvrirFichiers("Ouverture de fichiers") {
    //Configurer la fenêtre.
    set_position(Gtk::WIN_POS_CENTER);
    set_default_size(480, 360);
    add(boiteV);
    
    //Empêcher les boutons d'avoir le focus.
    boutonOuvrir.set_can_focus(false);
    boutonOuvrirFichiers.set_can_focus(false);
    boutonEnregistrer.set_can_focus(false);
    
    //Configurer les barres de défilement.
    barresDeDefilement.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    barresDeDefilement.add(zoneTexte);
    
    //Ajout de widgets dans les boîtes.
    boiteV.pack_start(barresDeDefilement);
    boiteV.pack_start(boiteH, Gtk::PACK_SHRINK);
    
    boiteH.pack_start(boutonOuvrir);
    boiteH.pack_start(boutonOuvrirFichiers);
    boiteH.pack_start(boutonEnregistrer);
    
    //Connexion des signaux aux fonctions de rappel.
    boutonOuvrir.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueOuvrirFichier));
    boutonOuvrirFichiers.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueOuvrirFichiers));
    boutonEnregistrer.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::dialogueEnregistrerFichier));
    
    show_all();
}

void Fenetre::dialogueOuvrirFichier() {
    //Création d'une boîte de dialogue d'ouverture de fichier.
    Gtk::FileChooserDialog dialogue(*this, "Ouverture d'un fichier");
    
    //Ajout de filtres.
    Glib::RefPtr<Gtk::FileFilter> filtreFichierTexte = Gtk::FileFilter::create(); //Création du filtre.
    filtreFichierTexte->set_name("Fichiers textes"); //Lui donner un nom.
    filtreFichierTexte->add_mime_type("text/plain"); //Ajouter un type MIME.
    dialogue.add_filter(filtreFichierTexte); //Ajouter le filtre au dialogue.
    
    Glib::RefPtr<Gtk::FileFilter> filtreFichierCCPP = Gtk::FileFilter::create();
    filtreFichierCCPP->set_name("Fichiers C/C++");
    filtreFichierCCPP->add_mime_type("text/x-c");
    filtreFichierCCPP->add_mime_type("text/x-c++");
    filtreFichierCCPP->add_mime_type("text/x-c-header");
    dialogue.add_filter(filtreFichierCCPP);
    
    Glib::RefPtr<Gtk::FileFilter> filtreTout = Gtk::FileFilter::create();
    filtreTout->set_name("Tous les fichiers");
    filtreTout->add_pattern("*"); //Ajout d'une expression.
    dialogue.add_filter(filtreTout);
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    if(resultat == Gtk::RESPONSE_OK) {
        //Obtenir le nom du fichier.
        std::string nomFichier = dialogue.get_filename();
        ouvrirFichier(nomFichier);
    }
}

void Fenetre::dialogueOuvrirFichiers() {
    //Création d'une boîte de dialogue d'ouverture de fichiers.
    Gtk::FileChooserDialog dialogue(*this, "Ouverture de fichiers");
    
    //Permettre à l'utilisateur de sélectionner plusieurs fichiers.
    dialogue.set_select_multiple();
    dialogue.set_show_hidden(); //Afficher les fichiers cachés.
    //Se rendre au dossier personnel de l'utilisateur
    dialogue.set_current_folder(Glib::get_home_dir());
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    //Affichage de la liste des fichiers sélectionnés.
    if(resultat == Gtk::RESPONSE_OK) {
        std::vector<std::string> liste(dialogue.get_filenames());
        std::string texte;
        
        for(std::vector<std::string>::iterator it(liste.begin()) ; it != liste.end() ; ++it) {
            texte += *it + "\n";
        }
        
        dialogue.hide();
        
        Gtk::MessageDialog dialog(*this, "Voici les fichiers sélectionnés :");
        dialog.set_secondary_text(texte);
        dialog.run();
    }
}

void Fenetre::dialogueEnregistrerFichier() {
    //Création d'une boîte de dialogue de sauvegarde de fichier.
    Gtk::FileChooserDialog dialogue(*this, "Sauvegarde d'un fichier", Gtk::FILE_CHOOSER_ACTION_SAVE);
    
    //Demande de confirmation lors du remplacement d'un fichier existant.
    dialogue.set_do_overwrite_confirmation();
    //Permettre la création de dossiers.
    dialogue.set_create_folders();
    
    //Ajout de boutons.
    dialogue.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    dialogue.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
    
    int resultat = dialogue.run();
    
    if(resultat == Gtk::RESPONSE_OK) {
        Gtk::MessageDialog dialog(*this, "Sauvegarde du fichier réussie");
        dialog.run();
    }
}

void Fenetre::ouvrirFichier(std::string nomFichier) {
    std::ifstream fichier(nomFichier.c_str(), std::ios::in);
    
    std::string texte, ligne;
    if(fichier) {
        
        while(getline(fichier, ligne)) {
            texte += ligne + "\n";
        }
        
        fichier.close();
    }
    else {
        Gtk::MessageDialog dialogue(*this, "Erreur lors de l'ouverture du fichier " + nomFichier + ".", false, Gtk::MESSAGE_INFO);
        dialogue.run();
    }
    
    //Obtenir le buffer de la zone de texte.
    Glib::RefPtr<Gtk::TextBuffer> buffer = zoneTexte.get_buffer();
    buffer->set_text(texte); //Modifier le texte du buffer.
}

Ça en fait des lignes. o_O

Voici le résultat :

Image utilisateur

Un fichier que nous n'avez jamais vu. :lol:


Boîte de dialogue de sélection de police La boîte de dialogue À propos

La boîte de dialogue À propos

Boîte de dialogue de sélection de fichier(s) Boîte de dialogue personnalisée

La boîte de dialogue À propos

La boîte de dialogue À propos affiche des informations sur le programme tels son nom, son logo, son site web, ...

Création

Le constructeur ne prend aucun paramètre :

Gtk::AboutDialog dialogue;

N'oubliez pas de l'inclure :

#include <gtkmm/aboutdialog.h>
Méthodes

Pour afficher des informations sur cette boîte, nous allons exclusivement appeler des méthodes de la classe Gtk::AboutDialog.

Le nom du programme
dialogue.set_program_name("gtkmm");
La version du programme
dialogue.set_version("0.1");
dialogue.set_copyright("© 2010");
Des commentaires
dialogue.set_comments("Gtkmm est une surcouche de la bibliothèque GTK+ !");

C'est le texte qui sera affiché pour décrire brièvement le programme.

La licence
dialogue.set_license("GNU/GPL");

Bien sûr, il faut mettre la licence, pas son nom. :D

Le site Web
dialogue.set_website("http://www.gtkmm.org/");
Le texte remplaçant l'adresse du site Web
dialogue.set_website_label("gtkmm");

Sur le bouton, il sera affiché "gtkmm" à la place de "http://www.gtkmm.org/".
En passant, c'est un site à mettre dans ses favoris. :D

Le logo
dialogue.set_logo(Gdk::Pixbuf::create_from_file("gtk.png"));

Nous devons donner un Gdk::Pixbuf à la méthode set_logo().

Les auteurs du programme
std::vector<Glib::ustring> listeAuteurs;
listeAuteurs.push_back("antoyo");
dialogue.set_authors(listeAuteurs);

Premièrement, nous créons un std::vector de Glib::ustring.

Puis, nous ajoutons un auteur à la liste avec push_back().
Enfin, nous ajoutons les auteurs à la boîte de dialogue avec set_authors.

Les documentateurs
dialogue.set_documenters(listeDocumentateurs);

Encore une fois, nous devons donner un std::vector de Glib::ustring.

Les graphistes
dialogue.set_artists(listeGraphistes);
listeGraphistes

est également un std::vector de Glib::ustring.

Il ne faut surtout pas oublier d’appeler la méthode suivante pour afficher la fenêtre :

dialogue.run();

Donc, il suffit d'appeler des méthodes pour remplir cette boîte de dialogue :

Image utilisateur

Boîte de dialogue de sélection de fichier(s) Boîte de dialogue personnalisée

Boîte de dialogue personnalisée

La boîte de dialogue À propos Les principaux widgets

Boîte de dialogue personnalisée

Dans cette sous-partie, nous allons créer notre propre boîte de dialogue.

Celle-ci contiendra une étiquette et une zone de texte et une méthode retournera son contenu.

Création

Nous allons nous créer une classe Dialogue (panne d'inspiration :p ) pour notre boîte de dialogue personnalisée.

Voici le fichier Dialogue.hpp :

#ifndef DEF_DIALOGUE
#define DEF_DIALOGUE

#include <string>

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/label.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

class Dialogue : public Gtk::Dialog {
    public :
        Dialogue(std::string titre, Gtk::Window* parent, std::string message);
        std::string get_texte();
        void set_texte(std::string texte);
        
    private :
        Gtk::Box* boiteV;
        Gtk::Label etiquette;
        Gtk::Entry zoneTexte;
};

#endif

Le constructeur demande le titre de la boîte de dialogue, son parent et un message qui sera le texte affiché sur la boîte de dialogue.

Nous avons vraiment besoin d'avoir tous ces paramètres ?

Non.
Les deux premiers sont obligatoires étant donné que nous devons les transmettre au constructeur de Gtk::Dialog.

Nous avons deux méthodes : une pour modifier le texte de la zone de texte et une autre pour le récupérer.

Nous créons un pointeur sur Gtk::Box, car nous allons appeler la méthode get_vbox() de la boîte de dialogue qui retourne un pointeur sur Gtk::Box (bien que cette méthode ne retourne pas une Gtk::VBox*, il s’agit bien d’une boîte verticale).

Voici le fichier Dialogue.cpp :

#include "Dialogue.hpp"

Dialogue::Dialogue(std::string titre, Gtk::Window* parent, std::string message) : Gtk::Dialog(titre, *parent), boiteV(get_vbox()), etiquette(message) { //Récupération de la boîte verticale avec get_vbox().
    boiteV->pack_start(etiquette);
    boiteV->pack_start(zoneTexte);

    //Ajout de boutons à la boîte de dialogue.
    add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);

    show_all();
}

std::string Dialogue::get_texte() {
    return zoneTexte.get_text();
}

void Dialogue::set_texte(std::string texte) {
    zoneTexte.set_text(texte);
}

Dans la liste d'initialisation, nous appelons le constructeur de la classe Gtk::Dialog ; nous lui transmettons le titre et le parent.

Ensuite, toujours dans la liste d'initialisation, nous modifions le texte de l'étiquette en fonction du paramètre transmis par le programmeur et récupérons la boîte verticale de la boîte de dialogue.

Puis, dans le constructeur, nous faisons des actions que vous connaissez bien :
ajout de widgets à la boîte verticale et ajout de boutons à la boîte de dialogue.

Nous aurions pu transformer la boîte verticale en boîte horizontale avec cette instruction :

boiteV->set_orientation(Gtk::ORIENTATION_HORIZONTAL);

La méthode suivante retourne le texte contenu dans la zone de texte.
La dernière modifie le texte sur cette zone de texte.

Pour utiliser cette boîte de dialogue, nous pouvons créer notre classe Fenetre, dont voici les fichiers :

Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/main.h>

#include "Dialogue.hpp"

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void afficherDialogue();
        
    private :
        Gtk::VBox boiteV;
        Gtk::Button bouton;
        Gtk::Label etiquette;
};

#endif

Rien de spécial, à part que nous incluons notre fichier Dialogue.hpp.

Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton("Afficher le dialogue") {
    add(boiteV);
    
    bouton.set_can_focus(false);
    
    boiteV.pack_start(bouton);
    boiteV.pack_start(etiquette);
    
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::afficherDialogue));
    
    show_all();
}

void Fenetre::afficherDialogue() {
    //Création de la boîte de dialogue personnalisée.
    Dialogue dialogue("Boîte de dialogue personnalisée", this, "Veuillez entrer du texte :");
    //Modifier le texte de la zone de texte par celui de l'étiquette.
    dialogue.set_texte(etiquette.get_text());
    
    int reponse = dialogue.run(); //Lancer la boîte de dialogue.
    
    if(reponse == Gtk::RESPONSE_OK) { //Si l'utilisateur clique sur Valider.
        //Modification du texte de l'étiquette par le texte de la zone de texte de la boîte de dialogue.
        etiquette.set_text(dialogue.get_texte());
    }
}

Dans la fenêtre, il y a un bouton et une étiquette.

Le bouton affiche la boîte de dialogue et le texte de l'étiquette se modifie lorsque l'utilisateur appuie sur le bouton Valider de cette boîte de dialogue.

Si vous avez bien suivi ce chapitre et avec les commentaires, vous devriez bien comprendre ce fichier.

main.cpp
#include <gtkmm/main.h>

#include "Fenetre.hpp"

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Fenetre fenetre;
    Gtk::Main::run(fenetre);
    return 0;
}

Nous créons notre fenêtre... :waw:

Voici le résultat :

Image utilisateur

Ça n'a pas l'air de ça, mais la boîte de dialogue est la fenêtre du bas. :-°

Donc, créer une boîte de dialogue personnalisée n'est pas vraiment plus dur que d’utiliser une autre boîte de dialogue.

Bref, vous avez appris à afficher des boîtes pour interagir avec l'utilisateur et vous savez maintenant créer vos propres boîtes de dialogue.

Pour vous entraîner à les utiliser, je vous recommande d'embellir un peu votre morpion que vous avez créé au chapitre précédent (je me trompe :euh: ?).

Je vous demande d'ajouter une boîte de dialogue de type information qui indiquera, à la fin de la partie, qui a gagné.
Après que l'utilisateur l'ait fermé, je souhaite qu'une de type question s'affiche pour lui demander s'il souhaite jouer une nouvelle partie.
S'il répond non, l'application doit se fermer.

Allez, au boulot :) !


La boîte de dialogue À propos Les principaux widgets

Les principaux widgets

Boîte de dialogue personnalisée Les différents boutons

Pour pouvoir faire de jolies fenêtres, il faut connaître un minimum de widgets.

Le but de ce chapitre est de vous apprendre à en utiliser quelques uns, de telle sorte que vous pourrez vous débrouiller dans la plupart des cas.

Les différents boutons

Les principaux widgets Widgets d'affichage

Les différents boutons

Il existe différentes sortes de boutons.

C'est dans cette sous-partie que nous allons les découvrir.

Les boutons-poussoir

Ce sont les seuls boutons que nous avons utilisés jusqu'à présent, donc vous les connaissez (ouf, moins d'explications pour moi :p ).

Rappels

Nous pouvons créer un bouton de plusieurs façons.

Ceci crée un bouton vide :

Gtk::Button bouton;

(C'est parfois utile. :euh: )

Vous devez, bien entendu, inclure l'entête :

#include <gtkmm/button.h>

Nous pouvons créer un bouton avec du texte :

Gtk::Button bouton("Mon bouton");

Nous pouvons ajouter une mnémonique à ce bouton de la façon suivante :

Gtk::Button bouton("Mon _bouton", true);

Dans ce cas, un clic sur Alt-b est équivalent à cliquer sur le bouton.

La dernière façon de créer un bouton est d'utiliser les Stock Item que vous connaissez bien (c'est le plus simple) :

Gtk::Button bouton(Gtk::Stock::OK);

Le seul signal que nous avons vu jusqu'à présent est le signal clicked() qui peut s'utiliser comme ceci :

bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::maFonctionDeRappel));

ceci est une connexion à une fonction de rappel de la classe Fenetre.

Méthodes

Nous allons voir quelques méthodes qui pourront vous être utiles.

La méthode set_label() permet de modifier le texte du bouton :

bouton.set_label("Nouveau texte.");

Nous pouvons obtenir le texte écrit sur le bouton avec get_label() :

std::string texte = bouton.get_label();

Je vais vous montrer deux autres méthodes.

La première est set_relief() qui permet de modifier l'apparence du bouton.

Cette méthode prend l'une des trois constantes suivantes :

Par exemple, voici un bouton sans relief (Gtk::RELIEF_NONE) :

Image utilisateur

Notez que le relief apparaît lorsque la souris est sur le bouton.

Comment se fait-il que l'image du bouton soit en bas du texte au lieu d'à sa gauche ?

C'est la dernière méthode que je souhaite vous montrer. ^^

La méthode set_image_position() prend l'une des constantes suivantes :

Les cases à cocher

Les cases à cocher sont des boutons à deux états : activé (coché) et désactivé (décoché).

Constructeur

Avant tout, vous devez inclure l'entête :

#include <gtkmm/checkbutton.h>

Il suffit de créer une instance de Gtk::CheckButton :

Gtk::CheckButton caseCocher;

Il est possible d'en créer d'une autre façon, par exemple en ajoutant un texte (à ajouter dans la liste d'initialisation) :

caseCocher("Afficher le titre")

Ou encore en utilisant une mnémonique :

caseCocher("Afficher le _titre", true)

Lorsque l'utilisateur appuiera sur Alt-t, la case s'activera ou se désactivera selon les cas.

Méthodes

Comme la case à cocher dérive du bouton-poussoir, il est possible d'utiliser les méthodes de ce dernier, par exemple :

caseCocher.set_can_focus(false);

pour enlever le contour désagréable autour du texte de la case à cocher :

Image utilisateur

devient ceci :

Image utilisateur

Mais, ça, vous le savez déjà, donc je ne vais pas m'attarder dessus.

Voyons de nouvelles méthodes spécifiques aux cases à cocher et au bouton radio (que nous verrons plus loin dans cette sous-partie).

Pour activer (cocher) un case à cocher, appelez la méthode set_active() :

caseCocher.set_active();

Pour la désactiver (décocher), il faut appelez cette même méthode en lui passant comme seul paramètre false :

caseCocher.set_active(false);

De même, il est possible de savoir si une case est coché ou non avec get_active() :

if(caseCocher.get_active()) {
    //La case est cochée.
}
else {
    //La case n'est pas cochée.
}
Signal

Nous allons voir notre deuxième signal.

C'est le signal toggled().
Ce signal est émis lorsque l'état (activé ou désactivé) de la case à cocher est modifié.

Ce signal peut donc être émis de deux façons différentes :

Exemple

Passons à un exemple pour mieux comprendre comme fonctionne la case à cocher.

Fichier Fenetre.hpp

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/checkbutton.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
    
    protected :
        void afficherTitre();
        
        Gtk::CheckButton caseCocher;
};

#endif

Ici, il n'y a rien de bien compliqué.
Nous créons notre case à cocher.

Fichier Fenetre.cpp

#include "Fenetre.hpp"

Fenetre::Fenetre() : caseCocher("Afficher le _titre", true) {
    set_border_width(10);
    set_title("Bouton à cocher");
    
    caseCocher.set_can_focus(false);
    
    //Lorsque l'utilisateur clique sur la case à cocher, appeler la fonction de rappel afficherTitre.
    caseCocher.signal_toggled().connect(sigc::mem_fun(*this, &Fenetre::afficherTitre));
    caseCocher.set_active(); //Activer la case à cocher.
    
    add(caseCocher);
    
    caseCocher.show();
}

void Fenetre::afficherTitre() {
    if(caseCocher.get_active()) { //Si la case est cochée.
        set_title("Bouton à cocher");
    }
    else { //Si elle n'est pas cochée.
        set_title("");
    }
}

Premièrement, dans la liste d'initialisation, nous initialisons notre bouton avec un texte et une mnémonique.
Ensuite, nous modifions un peu la fenêtre et la case à cocher.
Nous connectons le signal toggled() de la case à cocher à notre fonction de rappel afficherTitre() et nous activons le bouton.

Cette fonction affichera le titre si la case est coché et le cachera si elle ne l'est pas.

Les boutons radio

Les boutons radio fonctionnent de la même manière que les cases à cocher, à une exception près :
Les boutons radio fonctionnent par groupe.

C'est-à-dire que si l'utilisateur (ou le programme) active un bouton radio d'un groupe, tous les autres de ce même groupe se désactiveront.

Donc, la seule chose qu'il faut faire de plus pour utiliser ce bouton est de lui donner un groupe (Gtk::RadioButtonGroup).

Utilisation

Dans notre fichier Fenetre.hpp, section private, nous devons créer notre groupe et nos boutons radio :

#include <gtkmm/radiobutton.h>
//...
private :
    Gtk::RadioButtonGroup groupe; //Création d'un groupe.
    Gtk::RadioButton boutonRadio1, boutonRadio2, boutonRadio3; //Création de boutons radio.

La liste d'initialisation pourrait ressembler à ceci :

Fenetre::Fenetre() : boutonRadio1(groupe, "Titre 1"), boutonRadio2(groupe, "Titre 2"), boutonRadio3(groupe, "Titre 3")

À part cela, les boutons radio s'utilisent exactement de la même façon que les cases à cocher, car les premiers dérivent des deuxièmes.

Alors, nous pouvons utiliser set_active(), get_active() ou encore le signal toggled().

Par exemple, si nous voulons que le titre de la fenêtre change lorsque l'utilisateur clique sur un bouton radio, nous pourrions faire une fonction de rappel qui contient ceci :

if(boutonRadio1.get_active()) {
    set_title("Titre 1");
}
else if(boutonRadio2.get_active()) {
    set_title("Titre 2");
}
else {
    set_title("Titre 3");
}

Essayez de reproduire cette fenêtre avec les boutons radio qui modifie le titre lorsque l'utilisateur en choisi un :

Image utilisateur

Le séparateur entre le dernier bouton radio et le bouton Quitter est simplement un Gtk::HSeparator que j'ai ajouté à la boîte verticale.


Les principaux widgets Widgets d'affichage

Widgets d'affichage

Les différents boutons Widgets de saisie

Widgets d'affichage

L'étiquette

Une étiquette permet d'afficher du texte ; ça vous le savez aussi bien que moi. :D

Bien que nous en avons déjà parlé, nous n'avons pas vu tout ce qu'elle permet de faire, loin de là.

Vous devez inclure l'entête :

#include <gtkmm/label.h>
Construction

Il est possible de construire une étiquette sans paramètre, comme ceci :

Gtk::Label etiquette;

Dans ce cas, l'étiquette n'affichera rien, donc vous devrez lui ajouter du texte plus tard.

Alors, pourquoi créer une étiquette vide ?

Ça peut être utile dans deux cas.

Le premier, c'est lorsque vous ne savez le texte à écrire qu'à l'exécution du programme.

Le deuxième est lorsque vous voulez utiliser la mise en forme (le Pango Markup Language, ça vous rappelle de quoi ?).

Il est également possible de créer une étiquette ayant déjà du texte en le passant simplement en paramètre :

Gtk::Label etiquette("Mon texte");

Une autre façon de créer une étiquette est de lui envoyer, en plus du texte, les alignements horizontal et vertical du widget, comme s'il était dans un Gtk::Alignment.
Par exemple :

Gtk::Label etiquette("Mon texte", Gtk::ALIGN_END, Gtk::ALIGN_START);

L'étiquette (non pas le texte) sera en haut à droite.
Les constantes sont les mêmes que pour le conteneur d'alignement.

Pourquoi dis-tu que ce n'est pas le texte qui sera en haut à droite ?
Moi, je trouve qu'il est bien là...

Je dis cela parce qu'il est possible de placer l'étiquette à droite et de centrer le texte.
Ça se voit seulement lorsqu'il y a plusieurs lignes de tailles inégales.

Voici un exemple pour mieux comprendre :

Image utilisateur

Bien que l'étiquette se trouve à droite, le texte est centré.

Méthodes

La méthode set_selectable() permet à l'utilisateur de sélectionner le texte :

etiquette.set_selectable();

Lui permettre de sélectionner le texte lui permet également de copier le texte.

Voici la fameuse méthode pour centrer le texte :

etiquette.set_justify(Gtk::JUSTIFY_CENTER);

Voici les constantes :

Pour le texte justifié, ça ne fonctionne qu'avec les textes de plusieurs lignes :

Image utilisateur

De plus, vous devez utiliser la méthode set_line_wrap() pour que les lignes puissent être coupées :

etiquette.set_line_wrap();
etiquette.set_text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque aliquam nunc eget magna porttitor consequat. In sagittis lorem eget ante molestie vulputate. Suspendisse ut fermentum enim. Nam ac orci id lorem vestibulum cursus vitae et elit. Integer dolor quam, sodales id semper nec, adipiscing quis nisi. Donec purus lorem, aliquam at porttitor eget, feugiat non purus. Fusce nec libero at sem tempus pharetra ut non nulla. In ac magna mi. Integer ullamcorper nibh ac nibh consectetur id lacinia arcu lobortis. Donec egestas lectus ac erat aliquet vitae sagittis nibh vulputate. Phasellus non justo non sem tincidunt congue.");

Si vous n'utilisez pas set_line_wrap(), tout le texte sera sur la même ligne (ce que je vous recommande d'éviter :p ).

La dernière méthode que je vais vous montrer est set_markup().
Cette méthode modifie le texte, mais, cette fois, vous pouvez utiliser le Pango Markup Language :

etiquette.set_markup("<b>Gras</b>\n<i>Italique</i>\n<u>Souligné</u>\n<s>Barré</s>\n<big>Gros</big>\n2<sup>10</sup>\nCO<sub>2</sub>\n<small>Petit</small>\n<tt>Monospace</tt>"); //Ce texte sera mis en forme.

Voici quelques balises que vous pouvez utiliser :

Vous pouvez utiliser pour les retours à la ligne.

Avec la balise <span>, nous devons utiliser des attributs.
Les attributs sont des informations supplémentaires que nous donnons à la balise.
Chaque attribut doit avoir une valeur, entre guillemets.
Voici un schéma :

<balise attribut="valeur"></balise>

Voici quelques attributs de la balise <span> :

Voici un exemple :

etiquette.set_markup("<span face='Comic sans MS'>Comic sans MS</span>\n<span color='green'>Vert</span>\n<span background='yellow'>Fond jaune</span>\n<span color='red' underline='error'>Erreur</span>");

Ce qui donne :

Image utilisateur

Il est possible d'utiliser plusieurs attributs sur une balise, ne vous privez pas. :D

Pour connaître d'autres attributs de la balise <span>, vous trouverez votre bonheur dans la documentation.

Afficher des images

Il est très facile d'afficher une image.

Il vous suffit de créer une instance de Gtk::Image avec comme seul paramètre le chemin vers le fichier.
Ensuite, il ne vous reste qu'à la mettre dans un conteneur est de l'afficher.

Voici un exemple :

#include <gtkmm/image.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    
    Gtk::Image image("gtk.png"); //Création d'une image à partir d'un fichier.
    fenetre.add(image);
    image.show();
    
    Gtk::Main::run(fenetre);
    return 0;
}

Bref, rien de bien compliqué.

Il y a un autre moyen de créer une image qui pourrait vous intéresser :
créer une image à partir d'un Stock Item.

Pour ce faire, vous devez donner deux paramètres au constructeur :
le Stock Item et la taille de l'image.

Voici les constantes à utiliser pour la taille de l'image :

Par exemple, ce code :

Gtk::Image image(Gtk::Stock::QUIT, Gtk::ICON_SIZE_DIALOG);

donne ceci :

Image utilisateur
La barre de progression

Elle aussi, vous l'avez déjà utilisée.

Encore une fois, nous irons un peu plus en détails.

Vous devez inclure ceci :

#include <gtkmm/progressbar.h>
Construction

La construction n'a rien de bien compliqué :-° :

Gtk::ProgressBar barreProgression;
Méthodes

Nous pouvons modifier le texte sur la barre de progression avec set_text() :

barreProgression.set_text("0 %");

Nous pouvons également modifier l'orientation :

barreProgression.set_orientation(Gtk::ORIENTATION_VERTICAL);

Voici toutes les constantes :

Par défaut, les barres de progression sont de gauche à droite ou de haut en bas.
Mais, il est possible de l’inverser avec :

barreProgression.set_inverted();

Il est possible de récupérer la fraction du pourcentage avec get_fraction() ; cette méthode retourne un nombre de 0.0 à 1.0.

De même, pour modifier la fraction, il suffit d'appeler set_fraction() :

barreProgression.set_fraction(0.5);

Ceci met la barre de progression à 50%.

Exemple

Fichier Fenetre.hpp

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <sstream>
#include <string>

#include <gtkmm/button.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/main.h>
#include <gtkmm/progressbar.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void ajouter();
        
    private :
        Gtk::ProgressBar barreProgression; //Barre de progression.
        Gtk::VButtonBox boiteV;
        Gtk::Button boutonAjouter;
};

#endif

Nous créons les widgets nécessaire au projet.

Notez que nous ajoutons ceci :

#include <sstream>
#include <string>

pour pouvoir convertir facilement un nombre en std::string.

Fenetre.cpp

#include "Fenetre.hpp"

Fenetre::Fenetre() : boutonAjouter(Gtk::Stock::ADD) {
    add(boiteV);
    
    boutonAjouter.set_can_focus(false);
    
    //Modifier le texte de la barre de progression.
    barreProgression.set_text("0 %");
    //Inverser la barre de progression.
    barreProgression.set_inverted();
    
    boiteV.pack_start(boutonAjouter);
    boiteV.pack_start(barreProgression);
    
    //Connexion
    boutonAjouter.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::ajouter));
    
    show_all();
}

void Fenetre::ajouter() {
    //Obtention de la fraction du pourcentage de la barre de progression et ajout de 10%.
    double valeur(barreProgression.get_fraction() + 0.1);
    
    //Conversion du nombre en std::string.
    std::ostringstream flux;
    flux << valeur * 100;
    
    if(valeur <= 1) {
        barreProgression.set_fraction(valeur); //Modifier la fraction de la barre.
        barreProgression.set_text(flux.str() + " %");
    }
    else {
        barreProgression.set_fraction(0);
        barreProgression.set_text("0 %");
    }
}

Lorsque l'utilisateur clique sur le bouton, on ajoute 10% à la barre de progression et nous modifions son texte.

Pour le texte, nous voulons afficher le pourcentage de la barre de progression sur cette dernière.
Nous faisons donc une conversion avant d'appeler set_text().


Les différents boutons Widgets de saisie

Widgets de saisie

Widgets d'affichage Menus, barres d'outils, barre d'état

Widgets de saisie

Il existe plusieurs widgets qui permettent à l'utilisateur d'entrer des données ou d'en choisir selon une liste.

Nous en verrons quelques uns tels les zones de texte et la liste déroulante.

La zone de texte uniligne

Cette zone de texte permet à l'utilisateur d'entrer une seule ligne de texte.

Construction

Le constructeur d'une zone de texte est très simple ; il ne prend aucun paramètre :

Gtk::Entry zoneTexte;

N'oubliez pas d'inclure :

#include <gtkmm/entry.h>
Méthodes

Voyons quelques méthodes.

Une méthode importante est set_text().
Cette méthode permet de modifier le texte présent dans la zone de texte :

zoneTexte.set_text("Salut les Zér0 !");

Le texte affiché sur la zone de texte sera donc "Salut les Zér0 !" (sans les guillemets, bien sûr).

De même, il est possible de récupérer le texte avec get_text() :

std::string texte = zoneTexte.get_text();

Une méthode qui peut vous être utile est set_alignment() :

zoneTexte.set_alignment(Gtk::ALIGN_CENTER);

Cette méthode modifie l'alignement horizontal du texte dans la zone de texte.
Elle s'utilise de la même manière que le widget d'alignement, c'est-à-dire avec l'une des constantes suivantes :

Vous pouvez également utiliser un nombre de 0 (gauche) à 1 (droite).

Pouvons-nous limiter le nombre de caractères que l'utilisateur peut entrer ?

Oui et c'est avec la méthode set_max_length() avec comme paramètre la nombre maximum de caractères qu'il peut entrer :

zoneTexte.set_max_length(10);

Ceci limite la zone de texte à 10 caractères.

Et si on veut que l'utilisateur entre un mot de passe, y a-t-il un moyen de faire en sorte qu'il ne s'affiche pas ?

Encore une fois, oui. :D

Vous devez faire comme ceci :

zoneTexte.set_visibility(false);

Vous pouvez faire un tas d'autres choses avec cette zone de texte, comme afficher une image ou une progression, mais je ne peux pas tout vous apprendre.

Voici ce que ça pourrait donner :

Image utilisateur
Signal

La zone de texte propose un signal qui peut vous être utile :
activate()

Ce signal est émis lorsque l'utilisateur appuie sur la touche entrée.
Il peut donc aider à améliorer l'accessibilité à votre application.

Par exemple, ceci appelle la méthode afficher() lorsque l'utilisateur appuie sur la touche entrée :

zoneTexte.signal_activate().connect(sigc::mem_fun(this, &Fenetre::afficher));

Pour vous pratiquer, écrivez la fonction de rappel qui affichera le texte de la zone de texte dans une boîte de dialogue de message.

Le bouton compteur

Le bouton compteur est une zone de texte pour les nombres.
Elle comporte deux petites flèches, une pour augmenter la valeur du nombre et une autre pour la diminuer :

Image utilisateur
Constructeur

Pour construire un bouton compteur, nous aurons besoin de créer un Gtk::Adjustment.

Cet objet possède des informations sur la valeur initiale, maximale et minimale et sur l'incrémentation de cette valeur.

Nous pouvons créer un tel objet comme ceci :

Glib::RefPtr<Gtk::Adjustment> ajustement = Gtk::Adjustment::create(60, 0, 100, 5);

La valeur initiale est 60.
La valeur minimale est 0 et maximale, 100.
L'incrémentation est de 5.

Lorsque l'utilisateur cliquera sur une flèche, le nombre augmentera ou diminuera de 5.

Nous pouvons ensuite créer notre bouton compteur :

Gtk::SpinButton boutonCompteur(ajustement);

Il faut inclure ceci :

#include <gtkmm/adjustment.h>
#include <gtkmm/spinbutton.h>
Méthodes

Voyons quelques méthodes qui pourraient vous être utiles.

La méthode set_numeric() empêche l'utilisateur d'entrer autre chose que des chiffres :

boutonCompteur.set_numeric();

La méthode get_value() retourne la valeur sous forme d'un double et get_value_as_int(), sous forme d'un int.

Si vous voulez avoir tout de suite la valeur sous forme de texte, vous pouvez utiliser get_text(), car le bouton compteur dérive de la zone de texte.

Signal

Un signal qui pourrait vous être utile est value_changed() qui est émis lorsque la valeur est modifiée.

Exemple

Voici un exemple qui modifie le texte d'une étiquette lorsque la valeur du bouton compteur est modifiée.

Fenetre.hpp

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/adjustment.h>
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void modifierTexte();
        
    private :
        Glib::RefPtr<Gtk::Adjustment> ajustement; //Ajustement.
        Gtk::VBox boiteV;
        Gtk::SpinButton boutonCompteur; //Bouton compteur.
        Gtk::Label etiquette;
};

#endif

Ici, nous déclarons nos widgets ; il n'y a rien de vraiment compliqué.

Fenetre.cpp

#include "Fenetre.hpp"

Fenetre::Fenetre() : ajustement(Gtk::Adjustment::create(60, 0, 100, 5)), boutonCompteur(ajustement), etiquette("60") {
    add(boiteV);
    
    boiteV.pack_start(boutonCompteur);
    boiteV.pack_start(etiquette);
    
    boutonCompteur.set_numeric(); //Empêcher l'entrée de lettres ou symboles spéciaux.
    //Lorsque la valeur est modifiée, appeler une fonction de rappel.
    boutonCompteur.signal_value_changed().connect(sigc::mem_fun(*this, &Fenetre::modifierTexte));
    
    show_all();
}

void Fenetre::modifierTexte() {
    //Modifier le texte de l'étiquette à partir de celui du bouton compteur.
    etiquette.set_text(boutonCompteur.get_text());
}

Dans le constructeur, nous initialisons nos widgets et les ajoutons à la fenêtre.
De plus, nous empêchons l'utilisateur d'entrer autre chose que des chiffres et connectons le signal value_changed() à une fonction de rappel.

Cette fonction de rappel modifie le texte de l'étiquette à partir de celui du bouton compteur.

C'est tout, passons à la suite. :)

La zone de texte multiligne

La zone de texte multiligne est plus compliquée à utiliser que celle uniligne.

Pourquoi cela ?
Parce qu'elle permet de faire plus de chose.

Les zones multilignes de texte de gtkmm séparent les données de la présentation. Il s’agit du design pattern modèle-vue.
Le modèle est un objet Gtk::TextBuffer et la vue est un objet Gtk::TextView.

Le buffer

Le buffer est un objet qui détient des informations sur une zone de texte et qui permet de modifier cette dernière.

Chaque zone de texte a son propre buffer.

Un buffer est représenté par la classe Gtk::TextBuffer que vous devez inclure :

#include <gtkmm/textbuffer.h>
Construction de la zone de texte

Construire une zone de texte est très facile :

#include <gtkmm/textview.h>
Gtk::TextView zoneTexte;

Il n'y a rien d'autre à faire.

Méthodes

Nous allons voir quelques méthodes, mais sachez qu'il en existe beaucoup d'autres.

Si nous entrons beaucoup de texte sur la même ligne, la fenêtre s'agrandit en largeur.
C'est assez problématique...
Pour régler ce problème, nous pouvons faire en sorte que la ligne continue sur la ligne suivante avec set_wrap_mode() :

zoneTexte.set_wrap_mode(Gtk::WRAP_WORD);

Ceci coupe les lignes au mot près.

Il est possible de centrer le texte sur la zone de texte avec :

zoneTexte.set_justification(Gtk::JUSTIFY_CENTER);

Les autres constantes sont :

Pour modifier et obtenir le texte de la zone de texte, il faudra utiliser le buffer.

Pour obtenir le buffer de la zone de texte, il faut faire comme ceci :

Glib::RefPtr<Gtk::TextBuffer> buffer = zoneTexte.get_buffer();

Qu'est-ce que c'est, Glib::RefPtr ?

C'est un pointeur intelligent ; il s'utilise pratiquement de la même manière que les pointeurs que vous connaissez.

Vous pouvez obtenir plus d'informations sur ce pointeur dans ce livre sur gtkmm.

Maintenant que nous avons le buffer, nous pouvons obtenir le texte comme ceci :

std::string texte = buffer->get_text()

et modifier le texte comme cela :

buffer->set_text("texte");
Signal

Un signal important est changed() ; il est émis par le buffer lorsque le texte est modifié.

Exemple

Voyons un exemple de programme qui affiche dans une étiquette le texte de la zone de texte.
Le texte de l'étiquette est modifié à chaque fois que le texte de la zone de texte est modifié.
Un bouton effacera le contenu de la zone de texte.

Fenetre.hpp

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/textbuffer.h>
#include <gtkmm/textview.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void texteModifie();
        void vider();
    
    private :
        Gtk::VBox boiteV;
        Gtk::Button bouton;
        Glib::RefPtr<Gtk::TextBuffer> buffer;
        Gtk::Label etiquette;
        Gtk::TextView zoneTexte;
};

#endif

C'est très simple : nous créons les widgets nécessaires à l'application.

La fonction de rappel texteModifie() est appelée lorsque le texte de la zone de texte est modifié.

La fonction de rappel vider() est appelée lorsque l'utilisateur clique sur le bouton.

Fenetre.cpp

#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton(Gtk::Stock::CLEAR) {
    buffer = zoneTexte.get_buffer(); //Obtenir le buffer de la zone de texte.
    set_default_size(480, 320);

    add(boiteV);

    bouton.set_can_focus(false);

    etiquette.set_justify(Gtk::JUSTIFY_CENTER);

    //Couper les lignes au mot pour que la fenêtre ne s'agrandisse pas s'il y a trop de texte sur une même ligne.
    zoneTexte.set_wrap_mode(Gtk::WRAP_WORD);
    zoneTexte.set_justification(Gtk::JUSTIFY_CENTER);

    boiteV.pack_start(zoneTexte);
    boiteV.pack_start(etiquette, Gtk::PACK_SHRINK);
    boiteV.pack_start(bouton, Gtk::PACK_SHRINK);

    //Lorsque le texte de la zone de texte est modifié, appel d'une fonction de rappel.
    buffer->signal_changed().connect(sigc::mem_fun(*this, &Fenetre::texteModifie));
    //Lorsque l'utilisateur appuie sur ce bouton, vider la zone de texte.
    bouton.signal_clicked().connect(sigc::mem_fun(*this, &Fenetre::vider));

    show_all();
}

void Fenetre::texteModifie() {
    //Modifier le texte de l'étiquette à partir de celui de la zone de texte.
    etiquette.set_text(buffer->get_text());
}

void Fenetre::vider() {
    buffer->set_text(""); //Modifier le texte de la zone de texte.
}

Dans le constructeur, nous obtenons le buffer qui nous servira à modifier le texte de la zone de texte ou l'obtenir.

Nous modifions un peu les widgets, les ajoutons dans la fenêtre et connectons des signaux à une fonction de rappel.

Les fonctions de rappel sont très simples.

La première modifie le texte de l'étiquette et la seconde, celui de la zone de texte.

La liste déroulante

La liste déroulante permet à l'utilisateur de choisir un élément d'une liste.
Seul l'élément choisi est affiché :

Image utilisateur

Lorsque l'utilisateur clique sur la liste déroulante, les choix apparaissent :

Image utilisateur
Constructeur

Vous devez inclure :

#include <gtkmm/comboboxtext.h>

Pour construire une liste déroulante, il suffit de créer un objet de type Gtk::ComboBoxText :

Gtk::ComboBoxText listeDeroulante;
Méthodes

La première méthode que je vais vous montrer est append().
Celle-ci permet d'ajouter un élément dans la liste déroulante.

Voici comment elle s'utilise :

listeDeroulante.append("France");

Une autre méthode utile est set_active_text().
Cette méthode permet de modifier le texte affiché (donc l'élément) sur la liste déroulante.
Il faut mettre une chaîne de caractères qui a déjà été ajoutée par append() ou une autre méthode d'ajout.

Par exemple, si nous voulons que "France" soit l'élément affiché, il faudra faire :

listeDeroulante.set_active_text("France");

Que se passe-t-il si le texte "France" n'existe pas ?

Il ne se passe rien du tout.
Donc, ce n'est pas si grave que cela. :D

La dernière méthode que je souhaite vous montrer est get_active_text() qui retourne le texte affiché sur la liste déroulante.

Signal

Le signal changed() est émis lorsque l'élément choisi est modifié.

Il peut l'être de deux façons :

Exemple

Voyons un petit exemple pour mieux comprendre.

Dans cet exemple, des noms de pays seront affichés dans la liste déroulante et lorsque l'élément choisi sera modifié, une boîte de dialogue affichera le nom du pays choisi.

Fenetre.hpp

#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <gtkmm/comboboxtext.h>
#include <gtkmm/main.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public :
        Fenetre();
        void afficher();
    
    private :
        Gtk::ComboBoxText listeDeroulante; //Liste déroulante.
};

#endif

La méthode afficher() affichera la boîte de dialogue.

Fenetre.cpp

#include "Fenetre.hpp"

Fenetre::Fenetre() {
    //Ajout de texte à la liste déroulante.
    listeDeroulante.append("France");
    listeDeroulante.append("Québec");
    listeDeroulante.append("Maroc");
    listeDeroulante.append("Belgique");
    
    //Lorsque la sélection est modifiée.
    listeDeroulante.signal_changed().connect(sigc::mem_fun(*this, &Fenetre::afficher));
    
    listeDeroulante.set_active_text("Québec"); //Modifier le texte présentement vu.
    
    add(listeDeroulante);
    listeDeroulante.show();
}

void Fenetre::afficher() {
    //Affiche le texte sélectionné.
    Gtk::MessageDialog dialogue(*this, listeDeroulante.get_active_text());
    dialogue.run();
}

Dans le constructeur, nous ajoutons des éléments à la liste déroulante.
Ensuite, nous connectons le signal changed() à notre fonction de rappel.
Puis, nous modifions le texte affiché par défaut.
Enfin, nous ajoutons la liste déroulante a la fenêtre.

La fonction de rappel affiche simplement une boîte de dialogue avec le nom du pays choisi.

Pourquoi une boîte de dialogue s'affiche à l'ouverture de l'application ?

N'oubliez pas que le signal changed() n'est pas seulement émis lorsque l'utilisateur choisi un autre élément, mais aussi lorsque c'est fait par le programme lui-même.

Ce fut un très gros chapitre et il est plus que probable que vous n'ayez pas tout retenu.

Mais, comme je ne cesse de le répéter, c'est en forgeant que l'on devient forgeron.

Maintenant, après la lecture de ce chapitre, vous pouvez faire plusieurs applications intéressantes.
Cela me donne plusieurs idées. :diable:

Essayez de faire les programmes suivantes :

C'est très important de pratiquer, j'insiste. :colere2:


Widgets d'affichage Menus, barres d'outils, barre d'état

Menus, barres d'outils, barre d'état

Widgets de saisie Les menus

La plupart des applications contiennent un menu, une ou des barres d'outils et, parfois, une barre d'état (vous savez, l'endroit situé en bas de la fenêtre qui affiche des informations que nous savons déjà :D ).

C'est dans ce chapitre que vous apprendrez à créer tout cela.

Les menus

Menus, barres d'outils, barre d'état Les barres d'outils

Les menus

Les classes

Dans cette partie, nous allons créer une barre de menus.

Pour ce faire, nous allons avoir besoin de plusieurs classes.

La première est, bien sûr, Gtk::MenuBar, qui représente une barre de menu.
Une barre de menu contient des étiquettes alignées horizontalement :

Image utilisateur

Barre de menu contenant les menus Fichier et Édition.

Le menu est représenté par la classe Gtk::Menu.
Un menu est un ensemble d’item de menu, représenté verticalement :

Image utilisateur

Un menu comportant divers item de menu (Nouveau, Récents, Ouvrir, …).

Enfin, les items de menu sont représentés par la classe Gtk::MenuItem.
Les items de menu sont constitués le plus souvent d’une étiquette (texte) et d’une image.
Nous pouvons attribuer une mnémonique et un raccourci aux items de menu.

Plusieurs classes dérivent de Gtk::MenuItem :

Nous ne verrons que les deux premiers.
Vous pourrez facilement comprendre les trois autres grâce à la documentation.

Avant de passer à la suite, voici un schéma représentant les différentes classes sur l’image précédente :

Image utilisateur
Le code
La barre de menu

Créer une barre de menu est très simple :

Gtk::MenuBar barreMenu;
Le menu

Nous ne pouvons pas ajouter un menu directement à la barre de menu ; il faut ajouter un item de menu à cette dernière (retourner voir le schéma si vous l’avez déjà oublié :p ).

Donc, nous allons créer un item de menu et l’ajouter à la barre de menu :

Gtk::MenuItem menuItemFichier("Fichier");
barreMenu.append(menuItemFichier);

Nous pouvons spécifier une mnémonique à l’item en précédent d’un trait de soulignement (_) le caractères souhaité et en passant true au deuxième paramètre :

Gtk::MenuItem menuItemFichier("_Fichier", true);

Ensuite, nous devons créer notre menu et l’ajouter à l’item de menu voulu :

Gtk::Menu menuFichier;
menuItemFichier.set_submenu(menuFichier);

Il faut utiliser set_submenu() pour ajouter un menu à un item.
Cette méthode permet donc de créer des sous-menus (voir l’item de menu Récents de l’exemple).

Les items de menu

Maintenant, nous avons un menu, mais il est un peu… :euh: … vide.
Ajoutons-lui des items.

Vous avez déjà vu comment créer un item de menu lorsque nous avons créé le menu Fichier.
Pour en ajouter un à un menu, c’est exactement la même chose :

Gtk::MenuItem menuItemFichiersRecents("_Récents", true);
menuFichier.append(menuItemFichiersRecents);

Mais il est possible de faire plus beau, grâce à la classe Gtk::ImageMenuItem.
Créons un item de menu Nouveau avec une belle petite image à côté (tout cela grâce aux Stock Items :) ) :

Gtk::ImageMenuItem menuNouveau(Gtk::Stock::NEW);
menuFichier.append(menuNouveau);

Le signal à connaître pour les items de menu est activate(), qui est émit lorsque l’item de menu est cliqué ou lors de l’appel de la méthode activate() de l’item.

Séparateur

L’ajout d’un séparateur est très simple :

Gtk::SeparatorMenuItem separateur;
menuFichier.append(separateur);
Exemple

C’est le temps de voir un exemple :

#include <gtkmm/box.h>
#include <gtkmm/imagemenuitem.h>
#include <gtkmm/main.h>
#include <gtkmm/menu.h>
#include <gtkmm/menubar.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    fenetre.resize(200, 75);
    
    Gtk::VBox boiteV;
    fenetre.add(boiteV);
    
    //Création d’une barre de menu.
    Gtk::MenuBar barreMenu;
    boiteV.pack_start(barreMenu, Gtk::PACK_SHRINK);
    
    //Création de l’item menu fichier et ajout à la barre de menu.
    Gtk::MenuItem menuItemFichier("_Fichier", true);
    barreMenu.append(menuItemFichier);
    
    //Création du menu Fichier et ajout à l’item de menu Fichier.
    Gtk::Menu menuFichier;
    menuItemFichier.set_submenu(menuFichier);
    
    //Création d’un item de menu à partir d’un Stock Item et ajout au menu.
    Gtk::ImageMenuItem menuNouveau(Gtk::Stock::NEW);
    menuFichier.append(menuNouveau);
    
    //Sous-menu : Fichier->Récents
    Gtk::MenuItem menuItemFichiersRecents("_Récents", true);
    menuFichier.append(menuItemFichiersRecents);
    
    //Utilisation de set_submenu() afin de créer un sous-menu.
    Gtk::Menu menuRecents;
    menuItemFichiersRecents.set_submenu(menuRecents);
    
    Gtk::MenuItem fichierRecent1("Fichier récent 1");
    menuRecents.append(fichierRecent1);
    Gtk::MenuItem fichierRecent2("Fichier récent 2");
    menuRecents.append(fichierRecent2);
    
    //Fichier->Ouvrir
    Gtk::ImageMenuItem menuOuvrir(Gtk::Stock::OPEN);
    menuFichier.append(menuOuvrir);
    
    //Fichier->Enregistrer
    Gtk::ImageMenuItem menuEnregistrer(Gtk::Stock::SAVE);
    menuFichier.append(menuEnregistrer);
    
    //Ajout d’un séparateur au menu Fichier.
    Gtk::SeparatorMenuItem separateur;
    menuFichier.append(separateur);
    
    //Fichier->Fermer
    Gtk::ImageMenuItem menuFermer(Gtk::Stock::CLOSE);
    menuFichier.append(menuFermer);
    
    //Fichier->Quitter
    Gtk::ImageMenuItem menuQuitter(Gtk::Stock::QUIT);
    //Connexion du clic sur le menu Quitter à la fonction Gtk::Main::quit().
    menuQuitter.signal_activate().connect(sigc::ptr_fun(&Gtk::Main::quit));
    menuFichier.append(menuQuitter);
    
    //Menu édition
    Gtk::MenuItem menuItemEdition("É_dition", true);
    barreMenu.append(menuItemEdition);
    
    Gtk::Menu menuEdition;
    menuItemEdition.set_submenu(menuEdition);
    
    fenetre.show_all();
    Gtk::Main::run(fenetre);
    return 0;
}

Dans cet exemple, nous créons une barre de menu comportant deux items de menu (donc deux menus).
Le menu fichier comporte un item ayant un sous-menu.
En effet, si l’on pointe la souris sur l’item Récents, nous voyons un autre menu s’afficher.

Passons aux barres d’outils !


Menus, barres d'outils, barre d'état Les barres d'outils

Les barres d'outils

Les menus Le gestionnaire d'interface utilisateur

Les barres d'outils

Classes

Les classes utiles à la création d’une barre d’outils sont Gtk::ToolBar et Gtk::ToolButton.

La barre d’outils

La barre d’outils est représentée par la classe Gtk::ToolBar.
Une barre d’outils contient des boutons alignés horizontalement.

Les items de barre d’outils

Les items sont représentés par la classe Gtk::ToolItem.
Mais, bien souvent, nous utiliserons une de ses classes filles :

Nous verrons les deux premières classes de cette liste.

Les items sont donc des boutons ou des séparateurs.

Le code
La barre d’outils

Encore une fois, créer une barre d’outils est très simple :

Gtk::Toolbar barreOutils;
Les items

Il existe plusieurs façon de créer des boutons pour une barre d’outils.
L’une d’entre elles consiste à fournir une chaîne de caractères au constructeur :

Gtk::ToolButton boutonCompiler("Compiler");

Pour ajouter un bouton à la barre d’outils, il faut, encore, utiliser la méthode append() :

barreOutils.append(boutonCompiler);

Il est également possible de créer un bouton à partir d’un Stock Item :

Gtk::ToolButton boutonNouveau(Gtk::Stock::NEW);
Info-bulle

Nous pouvons ajouter une info-bulle aux boutons de deux façons.
La première façon consiste à appeler la méthode set_tooltip_text() :

boutonNouveau.set_tooltip_text("Crée un nouveau fichier");

Ce texte ne doit pas utiliser le Pango Markup Language.

Si nous voulons utiliser ce langage, nous devons appeler la méthode suivante :

boutonNouveau.set_tooltip_markup("<b>Nouveau</b> fichier");

Le principal signal à connaître pour ce bouton est clicked().

Le séparateur

Il se crée très simplement, d’une façon très semblable aux séparateurs de menu :

Gtk::SeparatorToolItem separateur;
barreOutils.append(separateur);
Exemple
#include <gtkmm/box.h>
#include <gtkmm/main.h>
#include <gtkmm/separatortoolitem.h>
#include <gtkmm/stock.h>
#include <gtkmm/toolbar.h>
#include <gtkmm/toolbutton.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window window;
    window.resize(200, 75);
    
    Gtk::VBox vBox;
    window.add(vBox);
    
    //Création d’une barre d’outils.
    Gtk::Toolbar barreOutils;
    vBox.pack_start(barreOutils, Gtk::PACK_SHRINK);
    
    //Création d’un bouton à partir d’un Stock Item et ajout à la barre d’outils.
    Gtk::ToolButton boutonNouveau(Gtk::Stock::NEW);
    boutonNouveau.set_tooltip_text("Crée un nouveau fichier");
    barreOutils.append(boutonNouveau);
    
    Gtk::ToolButton boutonCompiler("Compiler");
    barreOutils.append(boutonCompiler);
    
    //Création d’un séparateur et ajout à la barre d’outils.
    Gtk::SeparatorToolItem separateur;
    barreOutils.append(separateur);
    
    Gtk::ToolButton boutonQuitter(Gtk::Stock::QUIT);
    boutonQuitter.set_tooltip_markup("<b><span color='red'>Ferme l’application</span></b>");
    //Connexion du signal clicked() du bouton à la fonction Gtk::Main::quit().
    boutonQuitter.signal_clicked().connect(sigc::ptr_fun(&Gtk::Main::quit));
    barreOutils.append(boutonQuitter);
    
    window.show_all();
    Gtk::Main::run(window);
    return 0;
}

Il n’y a rien de compliquer dans ce code ; vous devriez le comprendre facilement avec l’aide des commentaires.

Si l’on utilise les mêmes items pour le menu et la barre d’outils, cette façon de faire duplique beaucoup de code (création d’items pour le menu, connexion des items du menu à des fonctions de rappel, création d’items pour la barre d’outils, connexion des items de la barre d’outils à des fonctions de rappel). Ça devient vite répétitif.
Y a-t-il moyen d’éviter la répétition du code ?

Oui.
Pour cela, il faut utiliser des…

Actions

Les actions permettent de créer des items de menu et des items de barre d’outils rapidement.
En utilisant des actions, nous n’aurons pas à créer deux items et faire deux connexions de signal pour chaque item présent dans le menu et la barre d’outils.

Création

La création de signal peut se faire de plusieurs façons.
Celle permettant de créer un signal à partir d’un Stock Item est :

Glib::RefPtr<Gtk::Action> actionNouveau = Gtk::Action::create("nouveau", Gtk::Stock::NEW);

Pour créer une action à partir d’une chaîne de caractère (représentant l’étiquette), il faut faire ceci :

Glib::RefPtr<Gtk::Action> actionCompiler = Gtk::Action::create("ouvrir", "Compiler");

Nous pouvons également créer une action avec une étiquette différente et une info-bulle de cette façon :

Glib::RefPtr<Gtk::Action> actionQuitter = Gtk::Action::create("quitter", Gtk::Stock::QUIT, "Quitter l’application", "Ferme l’application");
Signal

La connexion du signal d’une action se fait de la même manière qu’avec un item de menu :

actionQuitter->signal_activate().connect(sigc::ptr_fun(&Gtk::Main::quit));
Items

Après avoir créé l’action, nous pouvons obtenir les items de menu et de barre d’outils comme suit :

Gtk::MenuItem* menuItem = actionNouveau->create_menu_item();
Gtk::ToolItem* toolItem = actionNouveau->create_tool_item();

Ensuite, nous pouvons les ajouter au menu et à la barre d’outils.

Exemple

Voici un exemple où les actions sont utilisées pour créer un menu et une barre d’outils :

#include <gtkmm/action.h>
#include <gtkmm/box.h>
#include <gtkmm/main.h>
#include <gtkmm/menu.h>
#include <gtkmm/menubar.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/separatortoolitem.h>
#include <gtkmm/stock.h>
#include <gtkmm/toolbar.h>
#include <gtkmm/toolbutton.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    fenetre.resize(200, 75);
    
    Gtk::VBox boiteV;
    fenetre.add(boiteV);
    
    /*
     * Actions
     */
    //Création d’une action à partir d’un Stock Item.
    Glib::RefPtr<Gtk::Action> actionNouveau = Gtk::Action::create("nouveau", Gtk::Stock::NEW);
    Glib::RefPtr<Gtk::Action> actionOuvrir = Gtk::Action::create("ouvrir", Gtk::Stock::OPEN);
    //Création d’une action à partir d’un texte.
    Glib::RefPtr<Gtk::Action> actionCompiler = Gtk::Action::create("ouvrir", "Compiler");
    //Création d’une action avec une info-bulle.
    Glib::RefPtr<Gtk::Action> actionQuitter = Gtk::Action::create("quitter", Gtk::Stock::QUIT, "Quitter l’application", "Ferme l’application");
    
    //Connexion d’une action à une fonction de rappel.
    actionQuitter->signal_activate().connect(sigc::ptr_fun(&Gtk::Main::quit));
    
    /*
     * Menu
     */
    Gtk::MenuBar barreMenu;
    boiteV.pack_start(barreMenu, Gtk::PACK_SHRINK);
    
    Gtk::MenuItem menuItemFichier("Fichier");
    barreMenu.append(menuItemFichier);
    
    Gtk::Menu menuFichier;
    menuItemFichier.set_submenu(menuFichier);
    
    //Ajout d’actions au menu fichier.
    menuFichier.append(*(actionNouveau->create_menu_item()));
    menuFichier.append(*(actionOuvrir->create_menu_item()));
    menuFichier.append(*(actionCompiler->create_menu_item()));
    
    Gtk::SeparatorMenuItem separateurMenu;
    menuFichier.append(separateurMenu);
    
    menuFichier.append(*(actionQuitter->create_menu_item()));
    
    /*
     * Barre d’outils
     */
    Gtk::Toolbar barreOutils;
    boiteV.pack_start(barreOutils, Gtk::PACK_SHRINK);
    
    //Ajout d’actions à la barre d’outils.
    barreOutils.append(*(actionNouveau->create_tool_item()));
    barreOutils.append(*(actionOuvrir->create_tool_item()));
    barreOutils.append(*(actionCompiler->create_tool_item()));
    
    //Création d’un séparateur et ajout à la barre d’outils.
    Gtk::SeparatorToolItem separateurBarreOutils;
    barreOutils.append(separateurBarreOutils);
    
    barreOutils.append(*(actionQuitter->create_tool_item()));
    
    fenetre.show_all();
    Gtk::Main::run(fenetre);
    return 0;
}

Encore une fois, il n’y a rien de bien compliqué et vous comprendrez facilement avec les commentaires.


Les menus Le gestionnaire d'interface utilisateur

Le gestionnaire d'interface utilisateur

Les barres d'outils La barre d'état

Le gestionnaire d'interface utilisateur

Le gestionnaire d’interface utilisateur est une façon de créer des menus et des barres d’outils à partir d’une description XML.

Syntaxe

Les balises que nous allons utiliser sont les suivantes :

Nous pouvons préciser l’attribut name pour la barre d’outils et la barre de menu afin de faciliter sa récupération depuis le code C++.
En outre, nous devons préciser l’attribut action des <menu>, <menuitem> et <toolitem>.

Voici un exemple de ce fichier XML :

<ui>
    <menubar name="barreMenu">
        <menu action="fichier">
            <menuitem action="nouveau"/>
            <menuitem action="ouvrir"/>
            <separator/>
            <menuitem action="quitter"/>
        </menu>
    </menubar>
    <toolbar name="barreOutils">
        <toolitem action="nouveau"/>
        <toolitem action="ouvrir"/>
        <separator/>
        <toolitem action="quitter"/>
    </toolbar>
</ui>
Gestionnaire d’interface utilisateur

Le gestionnaire d’interface utilisateur est représenté par la classe Gtk::UIManager.
Pour instancier un objet Gtk::UIManager, il suffit de faire :

Glib::RefPtr<Gtk::UIManager> uiManager = Gtk::UIManager::create();

Ensuite, nous pouvons lui faire lire le fichier de description XML avec :

uiManager->add_ui_from_file("menuBarreOutils.ui");

Puis, nous pouvons récupérer la barre d’outils et la barre de menu grâce à la méthode get_widget() :

Gtk::Widget* barreMenu = uiManager->get_widget("/barreMenu");
Gtk::Widget* barreOutils = uiManager->get_widget("/barreOutils");

Le paramètre est le chemin vers le widget voulu.
La racine est facultative. Le code suivant est identique :

Gtk::Widget* barreOutils = uiManager->get_widget("/ui/barreOutils");

Le code suivant récupère l’item de barre d’outils Nouveau :

Gtk::Widget* toolitemNouveau = uiManager->get_widget("/barreOutils/nouveau");

Finalement, nous pouvons ajouter les barres à notre boîte verticale :

boiteV.pack_start(*barreMenu, Gtk::PACK_SHRINK);
boiteV.pack_start(*barreOutils, Gtk::PACK_SHRINK);
Groupe d’actions

Nous avons spécifié des actions dans la description XML.
Par conséquent, il faudra créer les actions ayant pour nom ceux spécifiés dans le XML.
Dans la partie précédente, nous avons créé des actions.
Pour rappel, nous avons fait comme ceci :

Glib::RefPtr<Gtk::Action> actionNouveau = Gtk::Action::create("nouveau", Gtk::Stock::NEW);

Le premier paramètre de la fonction create() est le nom de l’action.
Le même nom que dans la description XML.

Les actions créées devront être liées au gestionnaire d’interface utilisateur.
Pour ce faire, nous allons avoir besoin de créer un groupe d’actions.

Un groupe d’actions ce crée comme ceci :

Glib::RefPtr<Gtk::ActionGroup> groupe = Gtk::ActionGroup::create();

Nous pouvons lier le groupe d’actions avec le gestionnaire de cette façon :

uiManager->insert_action_group(groupe);
(UiManager:2471): Gtk-WARNING **: quitter: missing action quitter

Après avoir créé le groupe, nous devons y ajouter des actions.
Pour cela, nous devons utiliser la méthode add() :

groupe->add(actionNouveau);

Bien souvent, comme nous n’utiliserons plus les actions par la suite, nous allons ajouter les actions comme ceci :

groupe->add(Gtk::Action::create("fichier", "Fichier"));

Si nous voulons, en plus d’ajouter l’action au groupe, indiquer la fonction de rappel, nous pouvons ajouter un deuxième paramètre à cette méthode :

groupe->add(Gtk::Action::create("quitter", Gtk::Stock::QUIT, "Quitter l’application", "Ferme l’application"), sigc::ptr_fun(&Gtk::Main::quit));
Groupe d’accélérations

Un groupe d’accélérations est un groupe de raccourcis clavier.
Bien souvent, un tel groupe sera attaché à une fenêtre.

Le problème que nous avons en utilisant un gestionnaire d’interface utilisateur est que les raccourcis clavier lui sont propres.
Par conséquent, nous devons les transférer à la fenêtre pour que celle-ci puisse réagir à ces raccourcis.
Pour ce faire, nous allons utiliser la méthode suivante :

fenetre.add_accel_group(uiManager->get_accel_group());

Ainsi, nous ajoutons le groupe d’accélérations du gestionnaire à la fenêtre.

Exemple

Voici un exemple mettant en application ce que vous venez de voir :

#include <gtkmm/action.h>
#include <gtkmm/box.h>
#include <gtkmm/main.h>
#include <gtkmm/stock.h>
#include <gtkmm/uimanager.h>
#include <gtkmm/window.h>

int main(int argc, char* argv[]) {
    Gtk::Main app(argc, argv);
    Gtk::Window fenetre;
    
    Gtk::VBox boiteV;
    fenetre.add(boiteV);
    
    //Création d’un groupe d’actions.
    Glib::RefPtr<Gtk::ActionGroup> groupe = Gtk::ActionGroup::create();
    //Création d’actions et ajout au groupe.
    groupe->add(Gtk::Action::create("fichier", "Fichier"));
    groupe->add(Gtk::Action::create("nouveau", Gtk::Stock::NEW));
    groupe->add(Gtk::Action::create("ouvrir", Gtk::Stock::OPEN));
    //Il est possible de spécifier une fonction de rappel au deuxième paramètre de la méthode add().
    groupe->add(Gtk::Action::create("quitter", Gtk::Stock::QUIT, "Quitter l’application", "Ferme l’application"), sigc::ptr_fun(&Gtk::Main::quit));
    
    //Création d’un gestionnaire d’interface utilisateur.
    Glib::RefPtr<Gtk::UIManager> uiManager = Gtk::UIManager::create();
    uiManager->insert_action_group(groupe);
    uiManager->add_ui_from_file("menuBarreOutils.ui");
    //Pour que la fenêtre réagisse aux raccourcis-clavier des actions, il faut lui ajouter un groupe d’accélération (raccourcis).
    fenetre.add_accel_group(uiManager->get_accel_group());
    
    Gtk::Widget* barreMenu = uiManager->get_widget("/barreMenu");
    boiteV.pack_start(*barreMenu, Gtk::PACK_SHRINK);
    
    Gtk::Widget* barreOutils = uiManager->get_widget("/barreOutils");
    boiteV.pack_start(*barreOutils, Gtk::PACK_SHRINK);
    
    fenetre.show_all();
    
    Gtk::Main::run(fenetre);
    return 0;
}

Les barres d'outils La barre d'état

La barre d'état

Le gestionnaire d'interface utilisateur

La barre d'état

La barre d’état permet d’afficher des informations sur l’état de l’application.
Elle est représentée par la classe Gtk::Statusbar.

Création

La création d’une telle barre se fait assez facilement :

Gtk::Statusbar barreEtat;
Utilisation

Nous pouvons ajouter ou enlever des messages dans une barre d’état.

Pour en ajouter un, il faut utiliser la méthode push() :

int messageID = barreEtat.push("Vous pouvez commencer à utiliser l’application");

Comme vous pouvez le constater, cette méthode renvoie un identifiant.
Cela est utile s’il vous prend l’envie de supprimer le message :

barreEtat.remove_message(messageID);

Il est également possible de supprimer tous les messages :

barreEtat.remove_all_messages();

Pourquoi supprimer tous les messages ?
Il ne peut y en avoir qu’un qui est affiché à l’écran, non ?

Il ne peut, en effet, qu’y avoir un message à la fois.
Par contre, la barre d’état contient une pile de message.
Par conséquent, le dernier message ajouté (avec push()) sera affiché.
Les autres n’apparaîtront pas.
Si nous enlevons le message affiché, l’avant-dernier message ajouté s’affichera et ainsi de suite.

Exemple

Voici un exemple d’utilisation de la barre d’état.
Ce programme affiche un message lorsque l’utilisateur pointe la souris sur le bouton.
Ce message disparaît lorsque le souris ne pointe plus dessus :

Image utilisateur
Fenetre.hpp
#ifndef DEF_FENETRE
#define DEF_FENETRE

#include <string>

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/statusbar.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

class Fenetre : public Gtk::Window {
    public:
        Fenetre();
        
        bool afficherMessageEtat(GdkEventCrossing* event, std::string message);
        bool cacherMessageEtat(GdkEventCrossing* event);
        
    private:
        Gtk::Statusbar barreEtat; //Barre d’état.
        Gtk::VBox boiteV;
        Gtk::Button bouton;
};

#endif
Fenetre.cpp
#include "Fenetre.hpp"

Fenetre::Fenetre() : bouton(Gtk::Stock::QUIT) {
    add(boiteV);
    
    //Ajouter un message et récupérer son ID.
    int messageID = barreEtat.push("Vous pouvez commencer à utiliser l’application");
    //Enlever le message à partir de son ID.
    barreEtat.remove_message(messageID);
    
    boiteV.pack_start(bouton);
    
    //Connexions de signaux au bouton afin d’afficher un message dans la barre d’état lorsqu’il est survolé.
    bouton.signal_enter_notify_event().connect(sigc::bind<std::string>(sigc::mem_fun(*this, &Fenetre::afficherMessageEtat), "Quitter l’application"));
    bouton.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Fenetre::cacherMessageEtat));
    
    //Un cas d’utilisation de pack_end() est l’ajout d’une barre d’état.
    boiteV.pack_end(barreEtat, Gtk::PACK_SHRINK);
    
    show_all();
}

bool Fenetre::afficherMessageEtat(GdkEventCrossing* event, std::string message) {
    (void)event;
    barreEtat.push(message);
    return true;
}

bool Fenetre::cacherMessageEtat(GdkEventCrossing* event) {
    (void)event;
    barreEtat.remove_all_messages();
    return true;
}

La seule information que vous ignorez dans ce programme est les signaux utilisés :
Le signal enter_notify_event() est émit lorsque la souris pointe sur le bouton.
Le signal leave_notify_event() est émit, quant à lui, lorsque la souris quitte le bouton.

Maintenant, vous pouvez vraiment dire que vous savez créer des fenêtres. Vous pouvez pratiquement créer toutes les applications que vous voulez.

Mais... (je sais que vous l'attendiez, celui-là :-° ).
Pratiquez, pratiquez, pratiquez !

Modifier votre jeu de morpion (je ne le lâcherai jamais, ce jeu :p ).
Ajoutez-lui un menu qui permet de commencer une nouvelle partie, de quitter le programme (avec une confirmation si la partie n'est pas terminé, bien sûr ^^ ).
Ajoutez-lui une barre d'outils avec les mêmes actions que dans le menu (c'est ce que tout le monde fait, non :lol: ?).
Une barre d’état indiquant l’état du jeu ne serait pas de trop non plus…

Pratiquez, car dans le prochain chapitre, vous ferez un TP. :o

Vous venez d'apprendre beaucoup de choses sur la conception d'interfaces graphiques.
C'est une grande partie que vous venez de voir, là.

Étant donné qu'il y a beaucoup de nouveautés, je vous recommande vraiment de pratiquer. C'est nécessaire.
Ne me dites pas que vous êtes en panne d'idées, car il y a beaucoup de logiciels sur votre ordinateur que vous pouvez imiter. ;)

Maintenant, vous pouvez créer la plupart des fenêtres que vous voulez.
Si vous ne savez pas comment faire quelque chose, allez dans la documentation (je ne vous le dirai jamais assez :D ).

Pratiquez pour bien comprendre les principes de création d'interfaces graphiques avec gtkmm.

Maintenant, vous pouvez vous envoler de vos propres ailes.


Le gestionnaire d'interface utilisateur