Résumé :
Cette page web rassemble quelque lignes de code en c++.
Elle a pour objectif d'introduire à l'utilisation de wxWindows. wxwidgets est un projet open source qui permet de créer rapidement des applications GUI (Graphical User Interface). Ce sont des applications munies d'une interface graphique, c'est à dire contenant des fenêtres, boutons, menu ... La librairie wxWidgets permet de créer des applications cross-platform. En principe, en re-compilant le même code sous différents systèmes d'exploitation (MS Windows, linux, mac OS), vous obtiendrez votre programme. Cette page ne se veut en aucun cas en concurrence avec http://wiki.wxwidgets.org/ que je conseille vivement. J'accepte avec plaisir vos remarques et questions via e-mail : nicolas.delanoue@univ-angers.fr |
Nicolas Delanoue |
Table des matières | |
Nous commencerons par présenter le fameux Hello World (Programme 1). Puis nous verrons comment inclure des boutons et y associer des évenements (programme 2). Nous combinerons wxwidgets et la librairie graphique opengl dans le programme 3. Une introduction de thread sera proposée dans le programme 4. |
Programme 1 - Hello world | |
Comme à l'habitude, quand on veut
introduire, un langage de programmation ou bien l'utilisation d'une librairie,
rien de tel qu'un programme très court. Ce programme affiche une fenêtre qui ne contient qu'un
bouton. Lorsque l'utilisateur clique sur ce bouton, le message "Hello
World" s'affiche. Comme tout programme c++ qui se respecte, il est composé d'au moins deux fichiers. : un fichier .h et un fichier .cpp. Le fichier hworld.h contient les déclarations des classes et les entêtes des méthodes alors que fichier hworld.cpp contient le code des méthodes. |
Capture d'écran - Linux Gnome |
Capture d'écran - Windows XP |
// Fichier hworld.h // Indispensable pour faire des wxwidgets : #include <wx/wx.h> // On doit créer un identifiant pour chaque évenement // Ceci permettra, par exemple, d'associer un même // évemenement à deux boutons #define ID_Bt_Click 1 //Déclaration d'une classe MyApp (Mon application) dérivée de wxApp class MyApp: public wxApp { virtual bool OnInit(); }; //Déclaration d'une classe MyFrame (Ma fenetre principale) dérivée de wxFrame class MyFrame: public wxFrame { public: //Constructeur de la fenetre : MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); //Fonction qui sera appelé lorsque l'utilisateur cliquera sur le MonBouton1 void OnClickButton1(wxCommandEvent& event); // Boutton 1 wxButton *MonBouton1; // C'est la table qui est écrite dans le fichier cpp DECLARE_EVENT_TABLE() }; // Sorte de main ... IMPLEMENT_APP(MyApp) |
// Fichier hworld.cpp #include "hworld.h" // Déclarations de la table des événements // Sorte de relation qui lie des identifiants d'événements aux fonctions BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON(ID_Bt_Click, MyFrame::OnClickButton1) END_EVENT_TABLE(); // Code de l'initialisation de l'application bool MyApp::OnInit() { // On crée une instance de la classe MyFrame // On définit le texte qui apparaît en haut puis son emplacement et sa taille. wxString Nom; Nom = _T("Nom de l'application"); MyFrame *frame = new MyFrame( Nom, wxPoint(50,50), wxSize(450,340) ); // On la rend visible int a = 10; a = 10 +a; a = 10 + a; frame->Show(TRUE); SetTopWindow(frame); return TRUE; } // Construction de la fenêtre. Elle ne contient qu'un bouton. MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame *)NULL, -1, title, pos, size) { // Création d'un bouton. Ce bouton est associé à l'identifiant // événement ID_Bt_Click, en consultant, la table des événements // on en déduit que c'est la fonction OnClickButton qui sera // appelée lors d'un clic sur ce bouton MonBouton1 = new wxButton(this,ID_Bt_Click, _T("Cliquez moi dessus !")); } // Fonction qui est exécuté lors du click sur le bouton. void MyFrame::OnClickButton1(wxCommandEvent& WXUNUSED(event)) { // Affiche une boite de dialogue avec la chaine "C'est un Hello world... wxMessageBox( _T("C'est un Hello world wxWidgets ! "), _T("Titre de la message Box"), wxOK | wxICON_INFORMATION, this); } |
Afin de compiler ce code source, on pourra s'appuyer sur ce fichier makefile : |
all : hworld.cpp g++ -c hworld.cpp `wx-config --cxxflags` -o hworld.o g++ -o hworld.exe hworld.o `wx-config --libs` clear : rm hworld.o rm hworld.exe exe : hworld.exe ./hworld.exe |
Programme 2 - Plusieurs boutons et des événements | |
Une chose vous a peut-être choqué dans le
programme précédent : nous n'avons jamais défini la taille de notre
bouton, ni l'endroit où le placer. Par défaut, il occupe tout l'espace
libre de la fenêtre. Il est possible d'imposer tous ces
paramètres. Cette technique a plusieurs inconvénients. Tout d'abord, il faut explicitement donner les coordonnées et la
taille des boutons sans que ceux-ci ne se chevauchent. Il faut aussi
faire attention à la résolution de l'écran de l'utilisateur pour ne
pas placer un bouton dans une zone non atteignable... Pour toutes ces raisons, nous préfererons la technique de sizer... L'idée est simple : on place des boîtes (wxBoxsizer) qui, à leur tour, peuvent contenir des boîtes ou des boutons. La figure suivante propose un exemple de cette hiérarchie : Exemple d'utilisation de sizers. |
Quelques captures d'écran: | |
Capture d'écran - Linux Gnome |
Capture d'écran - Windows XP |
Le code: |
// Fichier programme2.h // Indispensable pour faire des wxwidgets : #include "wx/wx.h" // On doit créer un identifiant pour chaque évenement #define ID_Bt_Click1 1 #define ID_Bt_Click2 2 #define ID_Bt_Click3 3 //Déclaration d'une classe MyApp (Mon application) dérivée de wxApp class MyApp: public wxApp { virtual bool OnInit(); }; //Déclaration d'une classe MyFrame (Ma fenetre principale) dérivée de wxFrame class MyFrame: public wxFrame { public: //Constructeur de la fenetre : MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); //Fonction appelée lorsque l'utilisateur clique sur le Bouton1 ou Bouton2 void OnClickButton1(wxCommandEvent& event); void OnClickButton2(wxCommandEvent& event); void OnClickButton3(wxCommandEvent& event); // Bouton 1 et bouton2 wxButton *Bouton1; wxButton *Bouton2; wxButton *Bouton3; // C'est la table qui est écrite dans le fichier cpp DECLARE_EVENT_TABLE() }; // Sorte de main ... IMPLEMENT_APP(MyApp) |
// Fichier programme2.cpp #include "programme2.h" // Déclarations de la table des événements // Sorte de relation qui lit des identifiants d'événements aux fonctions BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON(ID_Bt_Click1, MyFrame::OnClickButton1) EVT_BUTTON(ID_Bt_Click2, MyFrame::OnClickButton2) EVT_BUTTON(ID_Bt_Click3, MyFrame::OnClickButton3) END_EVENT_TABLE(); // Code de l'initialisation de l'application bool MyApp::OnInit() { // On crée une instance de la classe MyFrame // On définit le texte qui apparait en haurt puis son emplacement et sa taille. MyFrame *frame = new MyFrame( "Programme 2", wxPoint(50,50), wxSize(450,340) ); // On la rend visible frame->Show(TRUE); SetTopWindow(frame); return TRUE; } // Construction de la fenêtre. Elle ne contient deux boutons, deux menus MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame *)NULL, -1, title, pos, size) { // Création des boutons et des associations boutons-événements Bouton1 = new wxButton(this,ID_Bt_Click1," Bouton 1 !"); Bouton2 = new wxButton(this,ID_Bt_Click2," Bouton 2 !"); Bouton3 = new wxButton(this,ID_Bt_Click3," Bouton 3 !"); // Création du sizer qui va contenir les boutons wxBoxSizer *Mysizer_buttons = new wxBoxSizer( wxHORIZONTAL ); Mysizer_buttons->Add(Bouton1); Mysizer_buttons->Add(Bouton2); // Création du sizer principal qui va contenir le sizer de boutons wxBoxSizer *Principal_sizer = new wxBoxSizer( wxVERTICAL ); Principal_sizer->Add(Mysizer_buttons); Principal_sizer->Add(Bouton3); //Placement du sizer du sizer principal qui va récursivement //placer les sizers et les boutons qu'il contient ... SetSizer(Principal_sizer); } // Fonction qui est exécutée lors du click sur le bouton 1. void MyFrame::OnClickButton1(wxCommandEvent& WXUNUSED(event)) { // Affiche une boite de dialogue avec la chaine "Vous avez cliqué ... wxMessageBox("Vous avez clique sur le bouton 1 ", "Hello World", wxOK | wxICON_INFORMATION, this); } // Fonction qui est exécutée lors du click sur le bouton 2. void MyFrame::OnClickButton2(wxCommandEvent& WXUNUSED(event)) { // Affiche une boite de dialogue avec la chaine "Vous avez cliqué ... wxMessageBox("Vous avez clique sur le bouton 2 ", "Hello World", wxOK | wxICON_INFORMATION, this); } // Fonction qui est exécutée lors du click sur le bouton 3. void MyFrame::OnClickButton3(wxCommandEvent& WXUNUSED(event)) { // Affiche une boite de dialogue avec la chaine "Vous avez cliqué ... wxMessageBox("Vous avez clique sur le bouton 3 ", "Hello World", wxOK | wxICON_INFORMATION, this); } |
Programme 3 - Incorporer du code OpenGL | |
Cette exemple illustre comment introduire du code OpenGl avec wxWidgets. OpenGl est une librairie graphique portable qui permet de
dessiner des objets en trois dimensions. La plupart des cartes graphiques sont faites pour effectuer des calculs rapides en parallèle (calculs matriciels). La force de cette librairie, c'est qu'elle exploite directement votre carte graphique. Par conséquent, c'est rapide et cela laisse votre processeur vaquer à d'autres activités. L'exemple ne vous apprend pas à programmer en OpenGl. Pour cela, je conseille vivement le site NeHe qui contient un excellent tutorial. Peut-être un jour, ferais-je une introduction à OpenGL du point de vue matriciel $SO(R^3)...$ Bref, pour faire de l'openGL, il faut dériver la classe wxGLCanvas (espace de dessin) et dessiner via une méthode appelée Draw(). |
Quelques captures d'écran: | |
Capture d'écran - Linux Gnome |
Capture d'écran - Windows XP |
Le code: |
// Fichier programme3.h #include <wx/wx.h> // Indispensable pour combiner wxwidgets et openGL : #include <wx/glcanvas.h> #define ID_Dessine 1 // Classe dérivée de wxGLCanvas // C'est ce qui va nous permettre de dessiner class GL_Window : public wxGLCanvas { public: // Constructeur GL_Window(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style=0, const wxString& name=_T("GLCanvas"), int* attribList = 0, const wxPalette& palette = wxNullPalette) : wxGLCanvas(parent, id, attribList, pos, size, style, name, palette) { glContext = new wxGLContext(this); }; virtual ~GL_Window() {}; // Fonction que l'on appelera dès que l'on souhaite peindre. void draw(); void init(); // Paramètre qui détermine l'angle de rotation du triangle. float angle_; // Dans le cas de plusieurs wxGLCanvas, on peut définir plusieurs contextes wxGLContext *glContext; }; //Déclaration d'une classe MyFrame (Ma fenêtre principale) dérivée de wxFrame class MyFrame: public wxFrame { public: //Constructeur de ma fenêtre MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); //Fonction applelée lors du clic sur le bouton nommé Dessine void OnDessine(wxCommandEvent& event); GL_Window *MyGLCanvas; wxButton *MonBouton; DECLARE_EVENT_TABLE() }; class MyApp: public wxApp { virtual bool OnInit(); }; |
// Fichier programme3.cpp #include "programme3.h" // Déclarations de la table des événements BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON(ID_Dessine, MyFrame::OnDessine) END_EVENT_TABLE() IMPLEMENT_APP(MyApp); // Constructeur de la fenêtre, placement des sizers, du bouton et // d'une zone de dessin openGL : MyGLCanvas MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame *)NULL, -1, title, pos, size) { wxBoxSizer *Mysizer_buttons = new wxBoxSizer( wxHORIZONTAL ); MonBouton = new wxButton(this, ID_Dessine ,_T(" Dessine !")); Mysizer_buttons->Add(MonBouton); wxBoxSizer *Principal_sizer = new wxBoxSizer( wxVERTICAL ); MyGLCanvas = new GL_Window( this, -1, wxPoint(-1,-1), wxSize(500,500), wxSUNKEN_BORDER, _T("some text")); MyGLCanvas->angle_ = 0; Principal_sizer->Add(MyGLCanvas); Principal_sizer->Add(Mysizer_buttons); SetSizer(Principal_sizer); } // Fonction appelée lors du clic sur le bouton. void MyFrame::OnDessine(wxCommandEvent& WXUNUSED(event)) { MyGLCanvas->draw(); } // Fonction membre de la classe dévivée de wxGLCanvas // C'est cette fonction qui est appelée dès que l'on veut // repeindre la zone de dessin. void GL_Window::draw() { angle_ = angle_ + 10; //indique que le code openGL qui suit est pour ce composant GL_Window via glContext SetCurrent( *glContext ); // Code OpenGL qui dessine un triangle. // Initialisation, couleur de fond . // glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 0.0); glViewport(0, 0, (GLint)500, (GLint)500); glPushMatrix(); glColor3f(1.0, 0, 0); glRotatef(angle_,0,0,1); glBegin(GL_POLYGON); glVertex3f(-0.5, -0.5, 1); glVertex3f(-0.5, 0.5, 1); glVertex3f(0.5, 0.5, 1); glEnd(); glPopMatrix(); // Dessine ! SwapBuffers(); }; bool MyApp::OnInit() { // Création de la fenetre principale MyFrame *frame2 = new MyFrame( _T("Hello World, Nico "), wxPoint(50,50), wxSize(600,600) ); frame2->Show(TRUE); SetTopWindow(frame2); return TRUE; } |
Programme 4 - Introduction d'un Thread et d'un timer | |
L'utilisation de Thread peut s'avérer très utile lorsque l'on souhaite effectuer des calculs lourds, c'est à dire dès lors que
l'exécution d'une méthode prend du temps. Il est inimaginable de bloquer l'application entière sous prétexe que le calcul est en cours. L'idée est de lancer différentes tâches en parallèle : le processeur est périodiquement occupé à effectuer une tâche puis une autre. Le programme que je vous propose contient un seul Thread (Le calcul effectué est simplement une incrémentation d'une variable entière). D'un point de vue interface graphique, il y a 3 boutons qui permettent respectivement de lancer le thread, mettre le thread en pause et quitter l'applicaion. On peut suivre en temps réel l'évolution du calcul du thread grâce à une zone de texte wxTextCtrl qui est mise à jour toutes les 50 ms via un timer. (wxTimer) |
Quelques captures d'écran: | |
Capture d'écran - Linux Gnome |
Capture d'écran - Windows XP |
Le code: |
// Commande pour faire des wxwidgets #include "wx/wx.h" #include <wx/thread.h> #define ID_Open 1 #define ID_Quit 3 #define ID_Lance 4 #define ID_Pause 5 #define Id_Timer 6 //Déclaration d'une classe MyApp (Mon application) dérivée de wxApp class MyApp: public wxApp { virtual bool OnInit(); }; // La classe MyThread est une classe dérivé de wxThread class MyThread: public wxThread { public : int x; // Contructeur de la classe MyThread( int x_): wxThread(wxTHREAD_JOINABLE) { x = x_; } // Méthode virtuelle, c'est dans cette méthode que l'on place // les calculs qui peuvent être long. Attention, cette méthode // ne doit jamais être appelée directement. void* Entry(); }; //Déclaration d'une classe MyFrame (Ma fenetre principale) dérivée de wxFrame class MyFrame: public wxFrame { public: //Constructeur de ma fenetre MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); //Fonction appelée lorsque l'utilisateur veut quitter. void OnQuit(wxCommandEvent& event); //Fonction appelée lorsque l'utilisateur lance le thread void OnLance(wxCommandEvent& event); //Fonction appelée lorsque l'utilisateur veut quitter. void OnPause(wxCommandEvent& event); //Fonction appelée périodiquement par le timer. void OnTimer(wxCommandEvent& event); wxButton *MonBouton1; wxButton *MonBouton2; wxButton *MonBouton3; wxTimer MonTimer; wxTextCtrl *MyTextCtrl; MyThread* thread; DECLARE_EVENT_TABLE() }; |
#include "programme4.h" // Déclarations de la table des événements // C'est une sorte de relation qui lit des événements à des fonctions. BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON(ID_Quit, MyFrame::OnQuit) EVT_BUTTON(ID_Lance, MyFrame::OnLance) EVT_BUTTON(ID_Pause, MyFrame::OnPause) EVT_TIMER(Id_Timer, MyFrame::OnTimer) END_EVENT_TABLE() // Sorte de main ... IMPLEMENT_APP(MyApp) // Constructeur de l'application. bool MyApp::OnInit() { MyFrame *frame = new MyFrame( "Je sais faire des Threads", wxPoint(50,50), wxSize(450,340) ); frame->Show(TRUE); SetTopWindow(frame); return TRUE; } // Fonction demande du temps avant de renvoyer un résultat. void* MyThread::Entry() { for (int i=0; i< 10000000; i++) { x++; // Cette instruction est indispensable si l'on veut // pouvoir arrêter le processus en cours de route. // Grosso-modo, à ce moment, c'est ici que la "tache" // vérifie que personne ne lui a demandé de se stopper. TestDestroy(); } }; // Construction MyFrame, on y créé des boutons des relations événement-bouton MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame *)NULL, -1, title, pos, size) { wxBoxSizer *Mysizer = new wxBoxSizer( wxHORIZONTAL ); MonBouton1 = new wxButton(this,ID_Lance,"Lance"); MonBouton2 = new wxButton(this,ID_Pause,"Pause"); MonBouton3 = new wxButton(this,ID_Quit,"Quitte"); MyTextCtrl = new wxTextCtrl(this,-1,"00000000"); Mysizer->Add(MonBouton1,1,wxALIGN_RIGHT); Mysizer->Add(MonBouton2,1,wxALIGN_RIGHT); Mysizer->Add(MonBouton3,1,wxALIGN_RIGHT); Mysizer->Add(MyTextCtrl,1,wxALIGN_RIGHT); SetSizer(Mysizer); // Création et lancement du Timer ce qui permettra d'appeler périodiquement // la fonction OnTimer via Id_Timer (toute les 50 ms) MonTimer.SetOwner(this,Id_Timer); MonTimer.Start(50); // Création du thread. thread = new MyThread(10); } void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) { // Ferme l'application. Close(); } // Méthode appelée lorsque l'utilisateur clique sur le bouton1 // Elle a pour but de lancer le thread. void MyFrame::OnLance(wxCommandEvent& WXUNUSED(event)) { // Comme indiquer dans l'introduction, // on ne doit pas directement appelé la méthode Entry de MyThread. // On peut lancer le thread via les deux méthodes héritées de wxThread: thread->Create(); thread->Run(); // Après avoir vérifié que le thread est en train de calculer ... if (thread->IsRunning() == TRUE) { //... on affiche ce message. wxMessageBox("Le thread vient de commencer à tourner ... ", "Thread tourne", wxOK | wxICON_INFORMATION, this); } } // Méthode appelée lorsque l'utilisateur clique sur le bouton2 void MyFrame::OnPause(wxCommandEvent& WXUNUSED(event)) { // Si le thread tourne alors on l'arrete if (thread->IsRunning()) { thread->Pause(); //... et on affiche wxMessageBox("Le thread vient d'être arreté ", "Thread stoppé", wxOK | wxICON_INFORMATION, this); } } //Méthode appelée périodiquement par le timer. //Cette méthode permet l'affichage de la variable x à l'écran. void MyFrame::OnTimer(wxCommandEvent& WXUNUSED(event)) { // Déclaration d'une chaine de caractère wxString string; // Affectation de la chaine à la valeur de x via un chgt de type string = wxString::Format("%d", thread->x); // On remplit la zone de texte avec la chaine string. MyTextCtrl->SetValue(string); }; |