Appeler une deuxième fenêtre et en retourner une valeur

| Jean-Paul Vidal | dourouc05 |

Ce tutoriel vous montrera comment appeler une deuxième fenêtre et en récupérer une valeur à l'aide de PyQt4.7 Commentez

- Titre : Appeler une deuxième fenêtre et en retourner une valeur

- Auteurs : Jean-Paul Vidal, dourouc05

- Parution : 29 octobre 2010

- Mise à jour : 7 décembre 2010

- Licence : Copyright ® 2010 Jean-Paul Vidal. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Présentation générale

- III. Lancement d'une deuxième fenêtre à partir d'une première fenêtre

- IV. Lancer une fenêtre 'modale'

- V. Transmission de l'information d'arrêt de la deuxième fenêtre et de la donnée

- VI. Fermeture de la deuxième fenêtre

- VII. Positionnement des Widgets sur la fenêtre

- VII-A. Fenêtre QWidget

- VII-B. Fenêtre QMainWindow

- VIII. Code complet

I - Introduction

Le code en bas de page permet de résoudre plusieurs problèmes :

- Comment une fenêtre déjà ouverte peut-elle en appeler une autre ?

- Comment cette deuxième fenêtre appelée peut-elle être "modale" ?

- Comment cette deuxième fenêtre peut-elle se fermer ?

- Comment cette deuxième fenêtre peut-elle retourner une donnée à la première fenêtre lors de sa fermeture ?

Et, accessoirement :

- comment positionner des widgets dans une fenêtre principale (QMainWindow).

II - Présentation générale

Les deux fenêtres sont déclarées comme des classes :

- la première fenêtre est une instance de la classe 'Principale' ;

- la deuxième fenêtre est une instance de la classe 'Quelclient'.

On lance le programme : la première fenêtre s'affiche. Elle comporte une ligne de saisie et, en-dessous, un bouton.

On clique sur le bouton : la deuxième fenêtre s'affiche. Comme elle est modale, on ne peut plus accéder à la première fenêtre tant que la seconde est ouverte.

La deuxième fenêtre contient aussi une ligne de saisie et un bouton. On tape un nom dans la ligne de saisie et on clique sur le bouton. La deuxième fenêtre se ferme et le nom qu'on a tapé dans la deuxième fenêtre est maintenant affiché dans la première. Cela prouve bien que, à l'arrêt de la deuxième fenêtre, la donnée saisie a bien été transmise à la première fenêtre.

Voilà comment ça marche.

III - Lancement d'une deuxième fenêtre à partir d'une première fenêtre

Le lancement de la deuxième fenêtre est très simple : on crée l'instance de classe et on demande l'affichage :

self.quelclient = Quelclient()
self.quelclient.show()

IV - Lancer une fenêtre 'modale'

Si on veut que la deuxième fenêtre soit modale, il suffit d'ajouter ceci avant le show() :

self.quelclient.setWindowModality(QtCore.Qt.ApplicationModal)

Sans cette ligne, la fenêtre est non modale par défaut au lancement. Si nécessaire, il est cependant possible de revenir au mode non modal avec la ligne suivante :

self.quelclient.setWindowModality(QtCore.Qt.NonModal)

V - Transmission de l'information d'arrêt de la deuxième fenêtre et de la donnée

On a résolu deux problèmes différents mais complémentaires :

- comment renvoyer une valeur de la deuxième fenêtre à la première ?

- comment la première fenêtre sait-elle que cette valeur est à sa disposition ?

On utilise pour cela un échange par signal qui, sur PyQt4, est la manière privilégiée pour faire communiquer deux widgets. Junicodee, la première fenêtre se prépare à recevoir un signal en provenance de la deuxième fenêtre et à lancer dans ce cas la méthode clientchoisi :

self.connect(self.quelclient, SIGNAL("fermeturequelclient(PyQt_PyObject)"), self.clientchoisi)

À la fermeture de la deuxième fenêtre, on émet un signal avec en argument la donnée à transmettre :

self.emit(SIGNAL("fermeturequelclient(PyQt_PyObject)"), unicode(self.lineEdit.text()))

Le nom du signal lui-même est créé dans ce programme (il n'existe pas dans la bibliothèque PyQt4), l'argument PyQt_PyObject permet de passer n'importe quel objet Python, ici un simple texte en Unicode. Cela pourrait être, par exemple, une liste ou un dictionnaire. La donnée transmise est passée dans le x de la méthode clientchoisi(self, x).

VI - Fermeture de la deuxième fenêtre

Attention, tel que programmé, il faut arrêter la deuxième fenêtre avec le bouton. Si on l'arrête d'une autre manière (avec la croix en haut de la fenêtre, par exemple), le signal ne sera pas émis et donc la donnée non transmise. Cela peut être souhaitable mais pas forcément. Dans le cas où on veut que le signal soit émis dans tous les cas, il faut remplacer la méthode ok_m par ceci :

def ok_m(self):
self.close()

def closeEvent(self, event):
self.emit(SIGNAL("fermeturequelclient(PyQt_PyObject)"), unicode(self.lineEdit.text()))

Ici, la méthode closeEvent surcharge la méthode déjà existante de la classe de la fenêtre (ici QWidget) et est lancée dans tous les cas d'arrêt de la fenêtre. On pourrait d'ailleurs lancer une fenêtre-message du genre "Êtes-vous sûr ?" avant de fermer effectivement la fenêtre ou d'annuler la fermeture, comme suit:

def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self,
u"Demande de fermeture",
u"Êtes-vous sûr de vouloir fermer la fenêtre ?",
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()

Avec un tel code, on pourrait aussi retourner un message différent selon le mode de fermeture. Il suffirait d'une variable drapeau initialisée à False qui passerait à True avec la méthode ok_m et qui serait testée dans closeEvent.

VII - Positionnement des Widgets sur la fenêtre

Dernier point, ce code permet de voir que le positionnement des widgets sur la fenêtre avec QGridLayout est légèrement différent entre une fenêtre complète de type QMainWindow (c'est-à-dire avec possibilité de menus, de barre d'outil, de barre d'état, etc.) et une fenêtre courante de type QWidget.

On voit bien la différence entre les deux types de fenêtre.

VII-A - Fenêtre QWidget

posit = QtGui.QGridLayout()
posit.addWidget(self.lineEdit, 0, 0)
posit.addWidget(self.bouton, 1, 0)
self.setLayout(posit)

VII-B - Fenêtre QMainWindow

self.setCentralWidget(QtGui.QFrame())
...
posit = QtGui.QGridLayout()
posit.addWidget(self.lineEdit, 0, 0)
posit.addWidget(self.bouton, 1, 0)
self.centralWidget().setLayout(posit)

En effet, pour la fenêtre de type QMainWindow, il existe déjà un layout pour positionner le menu et les autres éléments, il n'est pas possible de l'utiliser. Il faut donc ajouter un fond QFrame, qui sera appelé centralWidget, ajouter les widgets ayant ce fond comme parent et positionner les widgets sur ce fond.

VIII - Code complet

Voilà le code complet : un simple copier-coller devrait vous permettre d'exécuter chez vous mais il vous faut bien sûr PyQt4 en plus de Python.

Ce code a été écrit sous Python 2.7. Sa version Python 3.x ne devrait pas être très différente. Il est de plus multiplateforme (au moins Windows-Linux).

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import SIGNAL

#############################################################################
class Quelclient(QtGui.QWidget):

def __init__(self, parent=None):
super(Quelclient, self).__init__(parent)
self.setWindowTitle(u"Quel client")

# créer un lineEdit
self.lineEdit = QtGui.QLineEdit(self)
# créer un bouton
self.bouton = QtGui.QPushButton(u"Ok", self)
self.bouton.clicked.connect(self.ok_m)
# positionner les widgets dans la fenêtre
posit = QtGui.QGridLayout()
posit.addWidget(self.lineEdit, 0, 0)
posit.addWidget(self.bouton, 1, 0)
self.setLayout(posit)

def ok_m(self):
# emettra un signal "fermeturequelclient()" avec l'argument cité
self.emit(SIGNAL("fermeturequelclient(PyQt_PyObject)"), unicode(self.lineEdit.text()))
# fermer la fenêtre
self.close()

#############################################################################
class Principal(QtGui.QMainWindow):

def __init__(self, parent=None):
"""Initialise la fenêtre"""
super(Principal, self).__init__(parent)
self.setWindowTitle(u"Code test")

# mettre un fond (nécessaire avec un QMainWindow)
self.setCentralWidget(QtGui.QFrame())
# créer un lineEdit
self.lineEdit = QtGui.QLineEdit(self.centralWidget())
# créer un bouton
self.bouton = QtGui.QPushButton(u"Sélectionnez un client !", self.centralWidget())
self.bouton.clicked.connect(self.quelclient_m)
# positionner les widgets sur le fond de la fenêtre
posit = QtGui.QGridLayout()
posit.addWidget(self.lineEdit, 0, 0)
posit.addWidget(self.bouton, 1, 0)
self.centralWidget().setLayout(posit)

def quelclient_m(self):
"""Lance la deuxième fenêtre"""
self.quelclient = Quelclient()
# en cas de signal "fermeturequelclient()" reçu de self.quelclient => exécutera clienchoisi
self.connect(self.quelclient, SIGNAL("fermeturequelclient(PyQt_PyObject)"), self.clientchoisi)
# la deuxième fenêtre sera 'modale' (la première fenêtre sera inactive)
self.quelclient.setWindowModality(QtCore.Qt.ApplicationModal)
# appel de la deuxième fenêtre
self.quelclient.show()

def clientchoisi(self, x):
"""affiche le résultat x transmis par le signal à l'arrêt de la deuxième fenêtre"""
self.lineEdit.setText(x)

#############################################################################
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('plastique'))
main = Principal()
main.show()
sys.exit(app.exec_())

Merci à dourouc05 et à Mahefasoa pour leur relecture !