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