I. Introduction

Le but de ce tutoriel est de permettre à une personne familière avec Python et la programmation orientée objet, plus communément appelée OOP (Object-Oriented Programming), de réaliser une application graphique fonctionnelle en Qt et surtout "manuellement". J'entends par là sans passer par QtDesigner. 
Lors de la rédaction de ce tutoriel les versions suivantes de Python et PySide étaient utilisées : 
Python 2.7.1 Windows XP ; 
PySide 1.0.0 beta 1.

Afin d'appréhender le mieux possible ce tutoriel il est impératif d'avoir quelques connaissances en Python. Si cela n'était pas le cas, je vous invite à lire le tutoriel de G. Swinnen qui traite principalement de Tkinter et des notions de base de Python.

Après avoir lu ce tutoriel, vous devriez être en mesure de créer une application graphique fonctionnelle avec PySide. Nous y verrons donc la manière de créer les widgets les plus courants et comment les utiliser. Vous pouvez retrouver toutes les informations complètes sur les widgets vus ici, sur la documentation officielle de PySide. À noter enfin qu'une grande partie de ce tutoriel est aussi valable pour PyQt, l'autre binding Qt de Python. Il faudra principalement faire attention aux manières de connecter un widget à une action. Nous verrons ceci plus bas.

II. Installation de PySide

Vous trouverez dans la FAQ PyQt & PySide les informations nécessaires aux diverses installations de PySide.

III. Présentation des widgets de base

 Je vais présenter ici les widgets indispensables à la réalisation d'une application graphique en PySide. Je vais aussi profiter de ce chapitre pour montrer rapidement ce que nous pouvons faire avec ces widgets. Dans ce chapitre nous allons donc voir comment :

  • créer une fenêtre ;
  • y ajouter des widgets de base (label, bouton, zone de texte) ;
  • apprendre à réaliser des fonctions de base avec ces mêmes widgets.

III-A. Création d'une fenêtre

Afin de créer une fenêtre, nous utilisons la classe QWidget du module QtGui. Voici comment créer une fenêtre vide :

 
Sélectionnez

# -*- coding: iso-8859-1 -*-

## Import minimum obligatoire pour réaliser une application graphique en PySide.
import sys
from PySide import QtGui

#Création de la classe Frame issue de QWidget. 
#Toute application graphique doit contenir au moins une telle classe.
class Frame(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        
#Redimensionnement de la fenêtre principale.
        self.resize(600,500)

#Application de la police d'écriture Verdana à la fenêtre mais aussi à tous les widgets enfants. 
#À noter que nous aurions aussi pu choisir la taille et la mise en forme (gras, italique...)
        self.setFont(QtGui.QFont("Verdana")) 

#Titre de la fenêtre 
        self.setWindowTitle("Présentation PySide... Présentation des widgets de base")

#Utilisation d'une icône pour la fenêtre si celui est présent dans le répertoire courant... 
#sinon on passe.
        try:
            self.setWindowIcon(QtGui.Icon("icon.jpg")) 
        except:pass
        

#Les quatre lignes ci-dessous sont impératives pour lancer l'application.
app = QtGui.QApplication(sys.argv)
frame = Frame()
frame.show()
sys.exit(app.exec_())
			

III-B. Création et utilisation d'un bouton (QPushButton)

Pour créer un bouton, nous utiliserons la classe QPushButton(string,parent) héritée du module QtGui.

 
Sélectionnez

#Création du bouton
self.quit_0 = QtGui.QPushButton("Quitter\nsans message", self)

#Positionnement et dimensionnement du bouton de la forme (x,y,h,l)
self.quit_0.setGeometry(490, 450, 100, 30)

#Création d'une connexion entre le widget QPushButton, le signal clicked et le slot quit.
self.quit_0.clicked.connect(quit)


#En PyQt cette ligne se serait écrite :
#self.connect(self.quit_0, QtCore.SIGNAL("clicked()"),QtGui.qApp, QtCore.SLOT("quit()"))

#À noter que quit est un slot prédéfini et qu'il permet de quitter l'application proprement.
#Slot est un terme propre à Qt. Certains sont prédéfinis, d'autres seront créés directement par vous. 
#Dans ce cas-là, il s'agira ni plus ni moins que des fonctions que vous avez rencontrées dans votre 
#apprentissage de Python.
			


Nous aurions donc pu aussi créer un bouton activant un slot créé par nous-même. Nous aurions procédé ainsi :

 
Sélectionnez

self.btn_0 = QtGui.QPushButton("Action_1", self) 
self.btn_0.clicked.connect(self.action1) 
self.btn_0.move(0, 120)

#Création d'un slot personnalisé
def action1(self) :
    print " Activation slot action1 "
			

III-C. Création et utilisation d'une zone de texte non éditable (QLabel)

Pour créer une zone de texte non éditable, nous utiliserons la classe QLabel(string,parent) héritée du module QtGui.

 
Sélectionnez

#Création d'un QLabel. Nous le laissons se positionner seul pour avoir un rendu.
#Il se place en haut à gauche. 
#Ne permet pas à l'utilisateur de rentrer du texte.
self.label_0 = QtGui.QLabel("Ceci est un label", self) 

#Il est possible de modifier la valeur du QLabel
self.label_0.setText(" Nouveau texte ")

#Et de récupérer celle-ci
print self.label_0.text()

#À noter que setText() et text() fonctionne pour une grande majorité de widgets Qt.

			

III-D. Création et utilisation d'une zone de texte éditable (QLineEdit)

Pour créer une zone de texte éditable, nous utiliserons la classe QLineEdit(string,parent) héritée du module QtGui.

 
Sélectionnez

#Création d'un QLineEdit. Ce widget permet à l'utilisateur de rentrer du texte sur une seule ligne.
self.lineedit = QtGui.QLineEdit("Ceci est un LineEdit", self) 

#Positionnement du widget sans modifier sa taille.
self.lineedit.move(0, 25)
			

III-E. Création et utilisation d'une zone de texte multi-lignes (QTextEdit)

Pour créer une zone de texte multi-lignes, nous utiliserons la classe QTextEdit(string,parent) héritée du module QtGui.

 
Sélectionnez

#Création d'un QTextEdit. Il permet comme pour le LineEdit de rentrer du texte. 
#Cependant il permet de manière automatique d'effectuer des retours à la ligne et 
#de mettre en place un ascenseur si besoin.
self.textedit = QtGui.QTextEdit("""Ceci est un TextEdit.
				Il permet un retour à la ligne dans le widget et
				la création d'un ascenseur si besoin""", self)

#Positionnement et dimensionnement  du widget
self.textedit.setGeometry(0, 50, 100, 50)

			

III-F. Conclusion

Nous avons donc appris à créer une application graphique basique et nous avons maintenant appréhendé quelques notions de base sur les widgets les plus courants. Vous trouverez ici le code regroupant tous les points vus ci-dessus, plus quelques notions supplémentaires concernant l'utilisation des slots.
exemple

Dans le chapitre suivant nous allons montrer comment créer d'autres widgets qui pourront nous servir lors de la création de notre application finale.

IV. Présentation d'autres widgets usuels

 Dans ce chapitre nous allons voir comment créer trois nouveaux widgets qui sont :

  • une barre de menu ;
  • une barre de statut ;
  • une application avec plusieurs pages grâce au QTabWidget.

Afin de créer une barre de menu et une barre de statut, nous n'allons pas faire hériter notre classe Frame d'un QWidget mais d'un QMainWindow. Son utilisation est identique et on peut de la même manière y ajouter différents widgets. Nous verrons aussi comment utiliser le QTabWidget. Ce widget permet d'afficher sur notre application plusieurs onglets indépendant les uns des autres. Il nous permettra donc d'avoir plusieurs "écrans" sur le même, sans avoir à gérer plusieurs fenêtres. Voici le code permettant de créer cette fenêtre avec ces trois nouveaux widgets :

 
Sélectionnez

# -*- coding: iso-8859-1 -*-

import sys
from PySide import QtCore, QtGui

h = 300
l = 400

class Frame(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.resize(l,h)
        self.setFont(QtGui.QFont("Verdana"))
        self.setWindowTitle("Présentation PySide... Présentation d'autres widgets")
        try:
            self.setWindowIcon(QtGui.Icon("icon.jpg"))
        except:pass
        
        fen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((fen.width()-size.width())/2, (fen.height()-size.height())/2)

        self.quit_1 = QtGui.QPushButton("Quitter", self)
        self.quit_1.move(490, 450)
        self.quit_1.clicked.connect(self.close)
        
        #Création de la barre de statut avec les informations voulues.
        self.statusBar().showMessage("Utilisation de QMainWindow")
        
        menubar = self.menuBar() ## Création d'une barre de menu
        file_ = menubar.addMenu("&Fichier") ## Ajout d'un menu.
        
        #Création de l'action Fermer.
	#QAction reçoit cinq paramètres qui sont le titre, le parent, 
	#le raccourci clavier, le message qui apparaîtra dans la barre de statut et enfin le slot qui sera appelé.
        self.menuquit = QtGui.QAction("&Fermer", self, shortcut=QtGui.QKeySequence.Close,
                                    statusTip="Quitter l'application", triggered=self.close)   
        
        #Ajout de l'action créée ci-dessus.
        file_.addAction(self.menuquit)
        
        #Ajout de la fenêtre à onglets
        self.tabWidget = QtGui.QTabWidget(self)
        self.tabWidget.setGeometry(0,20,l,h-40)
        
        #Création de deux QWidget qui permettront ensuite de créer les pages du QTabWidget
        self.pnl_1 = QtGui.QWidget(self.tabWidget)
        
        #À noter que le parent du QWidget peut aussi bien être le QTabWidget 
	#ou le QMainWindows. Personnellement, je préfère la première solution afin 
	#de garder une hiérarchie cohérente dans le code.
        self.pnl_2 = QtGui.QWidget(self)
        
        #Ajout des QWidgets en temps que Tab du QTabWidget.
	#Le premier Tab portera le nom Entrée et le deuxième le nom Lecture.
        self.tabWidget.addTab(self.pnl_1, "Entrée")
        self.tabWidget.addTab(self.pnl_2, "Lecture")
        
        #Modification de la couleur de fond des QWidget.
        self.pnl_1.setPalette(QtGui.QPalette(QtGui.QColor("white")))
        self.pnl_1.setAutoFillBackground(True)
        self.pnl_2.setPalette(QtGui.QPalette(QtGui.QColor("white")))
        self.pnl_2.setAutoFillBackground(True)
        
        self.quit_1 = QtGui.QPushButton("Quitter", self.pnl_1)
        self.quit_1.move(100, 100)
        self.quit_1.clicked.connect(self.close)
        
app = QtGui.QApplication(sys.argv)
frame = Frame()
frame.show()
sys.exit(app.exec_())
			

V. Le point sur les bases de données

Dans la suite de ce tutoriel, nous allons travailler avec des bases de données. Il existe plusieurs DBMS (database management system). Python gère nativement SQlite 3. Pour des raisons de simplicité, nous allons donc baser nos exemples sur ce DBMS.

Il existe avec PySide/PyQt deux manières de se connecter à ce DBMS :

  • directement ;
  • en passant par le module QtSql.

Pour des raisons pratiques, nous allons rapidement présenter ces deux manières de faire. Cela dit, le but de ce tutoriel n'est pas d'approfondir nos connaissances sur les bases de données. Il est donc impératif pour vous d'avoir un minimum de notions acquises si vous souhaitez aller plus loin.

V-A. Utilisation du module SQlite 3

Dans les lignes suivantes, vous verrez comment créer et travailler avec une base de données SQlite 3.

 
Sélectionnez

# -*- coding: iso-8859-1 -*-

import sqlite3
 

#Création d'une nouvelle base de données, ainsi que de l'une des ses tables.
#Attention, dans le cas  cette table existe déjà, une erreur est 
#levée. Il est donc conseillé de prévoir cette éventualité.
def create():
    conn = sqlite3.connect("mybdd_1.db")  
    c = conn.cursor()
    c.execute('''create table mytable (key TEXT, dep TEXT, projet TEXT)''')
    conn.commit()
    c.close()
    
#Pour ma part je procède ainsi afin d'éviter de lever une erreur :
try:
    create()
except:pass
#Ce n'est peut être pas la solution la plus adaptée, mais elle fonctionne très bien.

#Enregistrement dans la table
def Save():
    liste = (str('ess1'), str('ess2'), str('ess3'))
    conn = sqlite3.connect("mybdd_1.db") 
    c = conn.cursor()
    c.execute("""insert into mytable values ('%s','%s','%s')"""%liste)
    conn.commit()
    c.close()
    
#Lecture de la table
def read():
    conn = sqlite3.connect("mybdd_1.db")  
    c = conn.cursor()
	
    c.execute("select * from mytable")
    for row in c:
        print row
    c.close()
			

V-B. Utilisation du module QtSql

Nous avons vu plus haut comment utiliser le module SQlite 3 natif chez Python. Cela dit, Qt permet lui aussi de manipuler des DBMS à sa manière, dont SQlite 3. Le gros avantage avec le module Qt réside dans le fait que la manière d'écrire, d'interroger ou de supprimer des données est identique, quel que soit le DBMS choisi. Seule la manière de s'y connecter diffère. Dans un souci de simplicité et de clarté, nous allons utiliser comme DBMS avec QtSql celui utilisé précédemment. Ce tutoriel n'ayant pas pour but de décrire précisément ce qui est faisable avec QtSql, je vous invite à consulter la FAQ correspondante ou la documentation officielle. Cependant nous rappellerons ici, quelques opérations possibles.

 
Sélectionnez

from PySide import QtGui, QtCore, QtSql

#Définition du pilote de base de données choisi
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
			
#Création de la base de données
self.db.setDatabaseName("mybdd.db")
			
#Ouverture de la base de données
self.db.open()
			
#Création de la table Contact à l'intérieur de la base de données ouverte
query = QtSql.QSqlQuery()
query.exec_('''create table Contact (
			id INTEGER PRIMARY KEY,
			nom TEXT, 
			prenom TEXT)''')
			
#Enregistrement des modifications.
self.db.commit()
			
			
#NOTA : les lignes ci-dessus diffèrent entre les différents pilotes possibles, notamment avec 
#les DBMS plus conventionnels qui se connectent à un serveur extérieur à l'application 
#nécessitant une identification par login et mot de passe par exemple.
#À partir de cette étape, les opérations sur les bases de données restent identiques
#quel que soit le pilote choisi.
			
			
#Écriture dans la table Contact
self.model = QtSql.QSqlTableModel()
self.model.setTable("Contact")
self.model.select()
self.model.insertRows(0, 1)
self.model.setData(self.model.index(0, 1), "Gentil")
self.model.setData(self.model.index(0, 2), "Charles-Élie")
self.model.submitAll()

#Permet de compter le nombre de lignes dans la table sélectionnée.
nb_row = self.model.rowCount()
			
#Lecture de la première ligne de la table Contact
record = self.model.record(0)
contact = record.value('prenom') + ' ' + record.value('nom')
			
#Fermeture de la base de données
self.db.close()
			

D'autres actions, comme la suppression ou la modification d'entrées, seront abordées dans les sources disponibles dans prochain chapitre.

VI. Création de notre première application : le carnet d'adresses

Nous y voici... À ce stade du tutoriel, nous avons assez de connaissances pour créer notre première application. Je vous propose de concevoir ensemble un carnet d'adresses minimaliste qui devra permettre :

  • de créer des contacts avec nom, prénom, n° de tel... ;
  • de consulter ces contacts ;
  • de supprimer ces contacts ;
  • de modifier les informations entrées d'un contact existant.

Nous allons donc commencer par créer une fenêtre à deux onglets. Le premier permettra d'entrer un nouveau contact, le deuxième permettra d'interroger, de modifier et de supprimer le cas échant un contact sélectionné. Dans le premier onglet, pour chaque contact, nous renseignerons :

  • le nom ;
  • le prénom ;
  • le n° de téléphone ;
  • le n° de fax ;
  • l'e-mail ;
  • l'adresse ;
  • le code postal ;
  • la ville.

Il faudra bien penser à la méthode choisie pour enregistrer le contact créé. Ces informations seront stockées dans une table Contact d'une base de données à créer. Dans le deuxième onglet, le contact voulu sera sélectionné via une liste déroulante dans laquelle seront renseignés le nom et le prénom de chaque contact présent dans la table Contact de notre base de données. Par la suite, nous offrirons à l'utilisateur le choix entre Consulter, Modifier ou Supprimer le contact choisi.

Afin de vous guider, voici des vues de ce que pourrait être notre application :
Image non disponible

Image non disponible

exemple

VII. Remerciements

Un grand merci à Thibaut Cuvelier et Jacques Thery pour leur relecture.