Nous avons vu jusqu'à présent comment concevoir des applications en mode console, à savoir, n'utilisant que le mode texte comme interface avec l'utilisateur. Or, la plupart des applications utilisées par le grand public offrent une interface graphique : une fenêtre comportant des boutons, des zones de texte, des cases à cocher, … Il est temps pour nous d'aborder comment parer nos applications d'une interface graphique et ainsi rendre leur utilisation beaucoup plus aisée.
Il existe plusieurs bibliothèques graphiques en Python telles que Tkinter qui offre un choix limité d'éléments graphiques et son aspect est assez austère. Nous allons utiliser la librairie PySide2 depuis le module éponyme qui offre la plupart des composants courants et qui est assez simple d'utilisation. De plus, elle s'adapte au thème configuré sur le système d'exploitation.
Nous allons travailler depuis un script permettant de générer des mots de passe aléatoirement pouvant comporter des minuscules, des majuscules, des chiffres et des symboles. La longueur du mot de passe est variable.
from random import choice def genererMotDePasse(tailleMotDePasse=8, minuscules=True, majuscules=True, chiffres=True, symboles=True): caracteres = "" if minuscules: caracteres += "abcdefghijklmnopqrstuvwxyz" if majuscules: caracteres += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if chiffres: caracteres += "0123456789" if symboles: caracteres += "&~#{([-|_\^@)=+$]}*%!/:.;?," motDePasse = "" for i in range(tailleMotDePasse): motDePasse += choice(caracteres) return(motDePasse)
Nous allons réaliser l'interface suivante.
Notre interface permet de choisir quels jeux de caractères utiliser pour notre mot de passe à l'aide des cases à cocher. La glissière permet de faire varier la taille du mot de passe. Enfin, après avoir cliqué sur le bouton Générer, le mot de passe apparaît dans la zone de texte. Le bouton Vers le presse-papier copie le mot de passe généré dans le presse-papier et le bouton Quitter ferme l'application.
Nous allons utiliser divers composants graphiques aussi nommés widgets (pour Window Gadgets). Nous utiliserons donc les classes suivantes :
Nous allons donc écrire une classe correspondant à notre fenêtre en héritant la classe QDialog et y décrire l'ensemble des widgets comme des attributs de la classe dans le constructeur :
import sys from PySide2 import QtCore, QtGui, QtWidgets class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # Les cases à cocher self.__caseMinuscules = QtWidgets.QCheckBox("Minuscules") self.__caseMajuscules = QtWidgets.QCheckBox("Majuscules") self.__caseChiffres = QtWidgets.QCheckBox("Chiffres") self.__caseSymboles = QtWidgets.QCheckBox("Symboles") # Les boutons self.__boutonQuitter = QtWidgets.QPushButton("Quitter") self.__boutonCopier = QtWidgets.QPushButton("Vers le presse-papier") self.__boutonGenerer = QtWidgets.QPushButton("Générer") # Le champ de texte self.__champTexte = QtWidgets.QLineEdit("") # La glissière self.__glissiereTaille = QtWidgets.QSlider(QtCore.Qt.Horizontal) # Le label self.__labelTaille = QtWidgets.QLabel("Taille du mot de passe : " + str(self.__glissiereTaille.value()))
Nous allons à présent aborder le placement des widgets dans la boîte de dialogue. PySide nous propose plusieurs méthodes pour placer les widgets. La solution la plus simple est le placement sur une grille à l'aide de la classe QGridLayout : chaque widget occupe une case dans une grille. Il est cependant possible de faire en sorte qu'un widget occupe plusieurs lignes ou colonnes.
Voici notre maquette d'interface dont les widgets ont été répartis sur une grille.
Pour implémenter cela, nous allons créer un objet de la classe QGridLayout, puis ajouter les widgets créés précédemment avec la méthode addWidget(widget, ligne, colonne) avec ligne et colonne, le numéro de la ligne et de la colonne souhaitées. Enfin, nous définirons le layout comme étant l'élément central de la fenêtre avec self.setLayout(layout).
Nous ajoutons donc à notre constructeur la portion de code suivante :
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # ... layout = QtWidgets.QGridLayout() layout.addWidget(self.__caseMajuscules, 0, 0) layout.addWidget(self.__labelTaille, 0, 1) layout.addWidget(self.__caseMinuscules, 0, 2) layout.addWidget(self.__caseChiffres, 1, 0) layout.addWidget(self.__glissiereTaille, 1, 1) layout.addWidget(self.__caseSymboles, 1, 2) layout.addWidget(self.__champTexte, 2, 1) layout.addWidget(self.__boutonQuitter, 3, 0) layout.addWidget(self.__boutonCopier, 3, 1) layout.addWidget(self.__boutonGenerer, 3, 2) self.setLayout(layout)
Nous allons terminer la préparation de notre fenêtre en modifiant le titre de la boîte de dialogue avec la méthode self.setWindowTitle(titre). Nous allons également définir le minimum et le maximum de la glissière avec respectivement les méthodes setMinimum et setMaximum. Nous allons cocher par défaut la case minuscules et chiffres avec la méthode setChecked. Nous ajoutons les lignes suivantes à notre constructeur :
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # ... self.setWindowTitle("Générateur de mot de passe") self.__caseMinuscules.setChecked(True) self.__caseChiffres.setChecked(True) self.__glissiereTaille.setMinimum(8) self.__glissiereTaille.setMaximum(30)
Nous allons enfin ajouter une icône à notre application pour que celle-ci soit reconnaissable dans la barre des tâches. Nous ajoutons les trois lignes suivantes au constructeur de notre boîte de dialogue :
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): # ... icone = QtGui.QIcon() icone.addPixmap(QtGui.QPixmap("cadenas.svg")) self.setWindowIcon(icone)
Pour exécuter notre fenêtre, on écrit les lignes suivantes dans le programme principal qui permettent de créer une application Qt en fournissant les arguments de la ligne de commande (ligne 1), instancie notre fenêtre (ligne 2) et l'affiche (ligne 3) :
app = QtWidgets.QApplication(sys.argv) dialog = MaFenetre() dialog.exec_()
Voici le code complet :
import sys from PySide2 import QtCore, QtGui, QtWidgets class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # Les cases à cocher self.__caseMinuscules = QtWidgets.QCheckBox("Minuscules") self.__caseMajuscules = QtWidgets.QCheckBox("Majuscules") self.__caseChiffres = QtWidgets.QCheckBox("Chiffres") self.__caseSymboles = QtWidgets.QCheckBox("Symboles") # Les boutons self.__boutonQuitter = QtWidgets.QPushButton("Quitter") self.__boutonCopier = QtWidgets.QPushButton("Vers le presse-papier") self.__boutonGenerer = QtWidgets.QPushButton("Générer") # Le champ de texte self.__champTexte = QtWidgets.QLineEdit("") # La glissière self.__glissiereTaille = QtWidgets.QSlider(QtCore.Qt.Horizontal) self.__glissiereTaille.setMinimum(8) self.__glissiereTaille.setMaximum(30) # Le label self.__labelTaille = QtWidgets.QLabel("Taille du mot de passe : " + str(self.__glissiereTaille.value())) layout = QtWidgets.QGridLayout() layout.addWidget(self.__caseMajuscules, 0, 0) layout.addWidget(self.__labelTaille, 0, 1) layout.addWidget(self.__caseMinuscules, 0, 2) layout.addWidget(self.__caseChiffres, 1, 0) layout.addWidget(self.__glissiereTaille, 1, 1) layout.addWidget(self.__caseSymboles, 1, 2) layout.addWidget(self.__champTexte, 2, 1) layout.addWidget(self.__boutonQuitter, 3, 0) layout.addWidget(self.__boutonCopier, 3, 1) layout.addWidget(self.__boutonGenerer, 3, 2) self.setLayout(layout) self.setWindowTitle("Générateur de mot de passe") icone = QtGui.QIcon() icone.addPixmap(QtGui.QPixmap("cadenas.svg")) self.setWindowIcon(icone) self.__caseMinuscules.setChecked(True) self.__caseChiffres.setChecked(True) app = QtWidgets.QApplication(sys.argv) dialog = MaFenetre() dialog.exec_()
Voici le résultat obtenu.
Nous avons obtenu une interface graphique mais celle-ci ne fonctionne pas : il est temps de relier les composants graphiques au code que nous avons écrit en début de chapitre.
Chaque widget de la fenêtre produit des signaux lorsqu'on l'utilise. Chaque signal peut être relié à un slot avec la méthode connect en fournissant en argument quelle fonction appeler lors de la réception du signal.
Pour illustrer cela, nous allons créer une méthode à notre objet MaFenetre, nommée quitter, et contenant la ligne self.accept() qui ferme la boîte de dialogue. Nous allons connecter le signal clicked (généré quand le bouton est cliqué) émis par le bouton Quitter à cette fonction :
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): # ... self.__boutonQuitter.clicked.connect(self.quitter) def quitter(self): self.accept()
Nous allons faire de même pour les boutons Vers le presse-papier et Générer. La copie vers le presse-papier nécessite la création d'un objet QtGui.QApplication.clipboard() et on y affecte, avec la méthode setText(texte) le contenu du champ de texte, lu avec la méthode text().
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): # ... self.__boutonCopier.clicked.connect(self.copier) def copier(self): pressePapier = QtGui.QApplication.clipboard() pressePapier.setText(self.__champTexte.text())
De manière analogue, nous allons créer une fonction generer permettant d'appeler notre fonction genererMotDePasse en lui fournissant comme arguments la taille souhaitée, lue depuis la glissière avec la méthode value() et en choisissant les caractères à partir des cases à cocher avec la méthode isChecked() qui retourne True lorsqu'elles sont cochées. Enfin, la valeur retournée par la fonction sera définie comme texte du champ de texte avec la méthode setText(texte).
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): # ... self.__boutonGenerer.clicked.connect(self.generer) def generer(self): tailleMotDePasse = self.__glissiereTaille.value() minuscules = self.__caseMinuscules.isChecked() majuscules = self.__caseMajuscules.isChecked() chiffres = self.__caseChiffres.isChecked() symboles = self.__caseSymboles.isChecked() self.__champTexte.setText(genererMotDePasse(tailleMotDePasse, minuscules, majuscules, chiffres, symboles))
Nous allons améliorer notre programme en modifiant la valeur du label Taille du mot de passe : en ajoutant la valeur actuelle de la glissière. Pour cela, nous modifions la ligne créant notre label et ajouter une fonction déclenchée par la modification de la valeur de la glissière, à savoir le signal valueChanged().
class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): # ... self.__labelTaille = QtWidgets.QLabel("Taille du mot de passe : " + str(self.__glissiereTaille.value())) # ... self.__glissiereTaille.valueChanged.connect(self.changerTailleMotDePasse) def changerTailleMotDePasse(self): self.__labelTaille.setText("Taille du mot de passe : " + str(self.__glissiereTaille.value()))
Voici le code source complet de notre application :
import sys from PySide2 import QtCore, QtGui, QtWidgets from random import choice def genererMotDePasse(tailleMotDePasse=8, minuscules=True, majuscules=True, chiffres=True, symboles=True): caracteres = "" if minuscules: caracteres += "abcdefghijklmnopqrstuvwxyz" if majuscules: caracteres += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if chiffres: caracteres += "0123456789" if symboles: caracteres += "&~#{([-|_\^@)=+$]}*%!/:.;?," motDePasse = "" for i in range(tailleMotDePasse): motDePasse += choice(caracteres) return(motDePasse) class MaFenetre(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # Les cases à cocher self.__caseMinuscules = QtWidgets.QCheckBox("Minuscules") self.__caseMajuscules = QtWidgets.QCheckBox("Majuscules") self.__caseChiffres = QtWidgets.QCheckBox("Chiffres") self.__caseSymboles = QtWidgets.QCheckBox("Symboles") # Les boutons self.__boutonQuitter = QtWidgets.QPushButton("Quitter") self.__boutonCopier = QtWidgets.QPushButton("Vers le presse-papier") self.__boutonGenerer = QtWidgets.QPushButton("Générer") # Le champ de texte self.__champTexte = QtWidgets.QLineEdit("") # La glissière self.__glissiereTaille = QtWidgets.QSlider(QtCore.Qt.Horizontal) self.__glissiereTaille.setMinimum(8) self.__glissiereTaille.setMaximum(30) # Le label self.__labelTaille = QtWidgets.QLabel("Taille du mot de passe : " + str(self.__glissiereTaille.value())) layout = QtWidgets.QGridLayout() layout.addWidget(self.__caseMajuscules, 0, 0) layout.addWidget(self.__labelTaille, 0, 1) layout.addWidget(self.__caseMinuscules, 0, 2) layout.addWidget(self.__caseChiffres, 1, 0) layout.addWidget(self.__glissiereTaille, 1, 1) layout.addWidget(self.__caseSymboles, 1, 2) layout.addWidget(self.__champTexte, 2, 1) layout.addWidget(self.__boutonQuitter, 3, 0) layout.addWidget(self.__boutonCopier, 3, 1) layout.addWidget(self.__boutonGenerer, 3, 2) self.setLayout(layout) self.setWindowTitle("Générateur de mot de passe") icone = QtGui.QIcon() icone.addPixmap(QtGui.QPixmap("cadenas.svg")) self.setWindowIcon(icone) self.__caseMinuscules.setChecked(True) self.__caseChiffres.setChecked(True) self.__boutonQuitter.clicked.connect(self.quitter) self.__boutonCopier.clicked.connect(self.copier) self.__boutonGenerer.clicked.connect(self.generer) self.__glissiereTaille.valueChanged.connect(self.changerTailleMotDePasse) def quitter(self): self.accept() def copier(self): pressePapier = QtWidgets.QApplication.clipboard() pressePapier.setText(self.__champTexte.text()) def generer(self): tailleMotDePasse = self.__glissiereTaille.value() minuscules = self.__caseMinuscules.isChecked() majuscules = self.__caseMajuscules.isChecked() chiffres = self.__caseChiffres.isChecked() symboles = self.__caseSymboles.isChecked() self.__champTexte.setText(genererMotDePasse(tailleMotDePasse, minuscules, majuscules, chiffres, symboles)) def changerTailleMotDePasse(self): self.__labelTaille.setText("Taille du mot de passe : " + str(self.__glissiereTaille.value())) app = QtWidgets.QApplication(sys.argv) dialog = MaFenetre() dialog.exec_()
L'ensemble des widgets présentés ici héritent de la classe QWidget qui offre une méthode setEnabled, qui permet d'activer ou de désactiver le widget. Nous allons découvrir dans cette section plusieurs widgets courants proposés par PySide.
Le champ de texte QLineEdit permet à l'utilisateur de lire et de modifier une chaîne de caractères. En voici les méthodes courantes :
Méthode | Description |
---|---|
text() | Retourne le texte contenu dans le champ de texte. |
setText(texte) | Modifie le contenu du champ de texte par le texte fourni en argument. |
clear() | Efface le contenu du champ de texte. |
setMaxLength(taille) | Définit la taille maximale du champ de texte. |
maxLength(taille) | Retourne la taille maximale du champ de texte. |
copy() | Copie le contenu du champ de texte dans le presse-papier. |
paste() | Colle le contenu du presse-papier dans le champ de texte. |
setEchoMode(mode) | Modifie l'affichage du contenu du champ de texte sans modifier son contenu :
|
setCompleter(completer) | Permet de définir une instance de QCompleter pour fournir de l'auto-complétion. |
setInputMask(masque) | Permet de configurer le format de données attendu avec une chaîne de caractères. Par exemple :
Il est possible de rendre le masque visible en lui ajoutant ";" suivi d'un caractère de remplacement. Par exemple, "00-00-0000;_" affichera "__-__-____" tant qu'il ne sera pas rempli. |
Voici quelques signaux proposés par la classe QLineEdit :
Signal | Déclencheur |
---|---|
textChanged | Lorsque le texte change (par l'utilisateur ou par le programme). |
textEdited | Lorsque le texte est changé par l'utilisateur. |
cursorPositionChanged | Lorsque le curseur est déplacé. |
returnPressed | Lorsque la touche Entrée est pressée. |
editingFinished | Lorsque le champ perd le focus ou la touche Entrée est pressée. |
La classe QAbstractButton est une classe regroupant différents boutons (comme QPushButton, QCheckBox …). Il est cependant impossible de créer un objet de cette classe.
Méthode | Description |
---|---|
setIcon(icone) | Permet d'ajouter une icône avec une instance de la classe QtGui.QIcon au bouton. |
setShortcut(raccourci) | Associe au bouton un raccourci clavier sous la forme d'une chaîne de caractères (exemple : CTRL + C). |
setCheckable() | Permet de rendre le bouton bistable (il maintient l'état après le clic). Les QRadioButton et QCheckBox sont bistables par défaut. |
setChecked() | Permet de valider un bouton. |
isChecked() | Retourne l'état du bouton. |
Voici quelques signaux proposés par la classe QAbstractButton :
Signal | Déclencheur |
---|---|
clicked | Lorsque le bouton est cliqué. |
pressed | Lorsque le bouton est appuyé. |
released | Lorsque le bouton est relâché. |
toogled | Lorsque le bouton change d'état comme par exemple les boutons de barre d'outils. |
La case à cocher permet de sélectionner entre 0 et plusieurs choix parmi l'ensemble de cases disponibles. On y retrouve les méthodes et signaux héritées de QAbstractButton. Par défaut les cases à cocher sont bistables.
Les boutons radios permettent un choix exclusif parmi plusieurs options présentes dans le même conteneur (layout ou widget). Un seul peut être coché à la fois. Ils héritent également des méthodes et signaux de la classe QAbstractButton. Le signal toggled permet de vérifier qu'un bouton radio change d'état.
Le bouton poussoir QPushButton permet de déclencher une action lors de son clic. Ce widget hérite également de la classe QAbstractButton. Il est possible d'associer un menu (instance de la classe QMenu) avec la méthode setMenu(menu).
La boîte de sélection QComboBox permet de choisir un élément parmi une liste. Voici les méthodes principales de cette classe :
Méthode | Description |
---|---|
addItem(chaine) | Permet d'ajouter une chaîne de caractères (avec ou sans icône) à la liste de choix. |
addItem(icone, chaine) | |
addItems(liste) | Permet d'ajouter une liste de chaîne de caractères à la liste de choix. |
currentIndex() | Retourne l'index de l'élément actuellement sélectionné. |
currentText() | Retourne la chaîne de caractères actuellement sélectionnée. |
setEditable() | Permet de modifier ou non les éléments de la boîte de sélection. Par défaut, cela n'est pas possible. |
insertItem(l, chaine) | Permet d'ajouter un ou plusieurs éléments pendant l'exécution du programme à l'index l. |
insertItems(l, liste) | |
insertSeparator() | Permet de grouper les éléments en ajoutant un séparateur entre ceux-ci. |
clear() | Efface les éléments contenus. |
Voici la liste des signaux proposés par QComboBox :
Signal | Déclencheur |
---|---|
activated | Lorsque l'utilisateur interagit avec. |
currentIndexChanged | Lorsque l'élément sélectionné change (par le programme ou l'utilisateur). Retourne l'index de l'élément sélectionné. |
highlighted | Retourne l'index de l'élément surligné. |
editTextChanged | Lorsque l'utilisateur modifie le contenu de la boîte de dialogue. |
Les champs numériques QSpinBox et QDoubleSpinBox forcent l'utilisateur à saisir des données numériques. Le champ QSpinBox n'accepte que les valeurs entières, et le champ QDoubleSpinBox les valeurs décimales. Voici les méthodes de ces champs :
Méthode | Description |
---|---|
setMinimum(minimum) | Définissent le minimum et le maximum autorisés par le champ. |
setMaximum(maximum) | |
setRange(min, max) | |
setSingleStep | Fixe le pas d'incrémentation. |
setSuffix(chaine) | Ajoutent un suffixe ou un préfixe au champ pour plus de lisibilité (exemple : €, £, litres, km, …). |
setPrefix(chaine) | |
setValue(valeur) | Définit la valeur du champ. |
value() | Retourne la valeur du champ. |
setDecimals(nbDecimales) | Pour QDoubleSpinBox. Permet de définir le nombre de décimales à l'affichage. |
Ces deux champs numériques offrent un seul signal : valueChanged qui est émit quand l'utilisateur change la valeur contenue. La valeur retournée est la valeur du champ. Pour QDoubleSpinBox, la chaîne de caractères est codée en fonction de la langue (en France, on utilise une virgule (,) comme séparateur de décimale).
Ces champs sont conçus pour saisir des données temporelles. Le nom des méthodes et signaux dépendent du type de données (QDateEdit pour la date, QTimeEdit pour l'heure et QDateTimeEdit pour la date et l'heure). Voici les méthodes de ces classes :
Méthode | Description |
---|---|
date() | Retournent la valeur du champ sous la forme d'un objet QDate, QTime ou QDateTime. |
time() | |
dateTime() | |
setDate() | On remplit le champ avec les objets QDate, QTime ou QDateTime. |
setTime() | |
setDateTime() | |
setMinimumDate() | Définissent le minimum du champ avec les objets QDate, QTime ou QDateTime. |
setMinimumTime() | |
setMinimumDateTime() | |
setMaximumDate() | Définissent le maximum du champ avec les objets QDate, QTime ou QDateTime. |
setMaximumTime() | |
setMaximumDateTime() | |
setCalendarPopup() | Permet d'afficher un calendrier pour les objets manipulant des dates. |
Les signaux émis dépendent des valeurs qui ont changés :
Signal | Déclencheur |
---|---|
dateChanged | Déclenchés lors de la modification de la valeur. |
timeChanged | |
dateTimeChanged |
À l'instar de la classe QLineEdit, ce widget permet d'éditer du texte, mais offre une zone d'édition plus grande et permet la mise en forme du contenu au format HTML. Voici les méthodes usuelles :
Méthode | Description |
---|---|
toPlainText() | Retourne le texte contenu dans le champ de texte. |
setText(texte) | Modifie le texte du champ par celui en argument. |
toHtml() | Retourne le code HTML contenu dans le champ de texte. |
setHtml(texte) | Modifie le contenu du champ de texte par le code HTML fourni en argument. |
clear() | Efface le contenu du champ de texte. |
copy() | Copie le contenu du champ de texte dans le presse-papier. |
paste() | Colle le contenu du presse-papier dans le champ de texte. |
undo() | Annule la dernière opération. |
redo() | Refait la dernière opération annulée. |
Voici quelques signaux proposés par la classe QLineEdit :
Signal | Déclencheur |
---|---|
textChanged | Lorsque le texte change (par l'utilisateur ou par le programme). |
cursorPositionChanged | Lorsque le curseur s'est déplacé. |
La classe QTabWidget permet de regrouper des widgets dans différents onglets. Voici les différentes méthodes offertes par cette classe :
Méthode | Description |
---|---|
addTab(widget, nom) | Permet d'ajouter un onglet contenant un widget et dont le nom et le widget sont passés en argument. |
insertTab(widget, nom) | Permet d'ajouter un onglet contenant un widget et dont le nom et le widget sont passés en argument pendant l'exécution du programme. |
tabPosition() | Retourne l'indice de l'onglet actuellement sélectionné. |
Le signal currentChanged est émis lorsque l'utilisateur change d'onglet. Voici un exemple de mise en œuvre de la boîte à onglets.
Voici le code source de l'exemple ci-dessous :
import sys from PySide2 import QtCore, QtGui, QtWidgets class Dialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) # Les champs self.__champTexteNomAuteur = QtWidgets.QLineEdit("") self.__champTextePrenomAuteur = QtWidgets.QLineEdit("") self.__champDateNaissanceAuteur = QtWidgets.QDateEdit() self.__champDateNaissanceAuteur.setCalendarPopup(True) self.__champTexteTitreLivre = QtWidgets.QLineEdit("") self.__champDatePublication = QtWidgets.QDateEdit() self.__champDatePublication.setCalendarPopup(True) # Les widgets self.__widgetAuteur = QtWidgets.QWidget() self.__widgetLivre = QtWidgets.QWidget() # Les layouts des onglets self.__layoutAuteur = QtWidgets.QFormLayout() self.__layoutAuteur.addRow("Nom : ", self.__champTexteNomAuteur) self.__layoutAuteur.addRow("Prénom : ", self.__champTextePrenomAuteur) self.__layoutAuteur.addRow("Date de naissance : ", self.__champDateNaissanceAuteur) self.__widgetAuteur.setLayout(self.__layoutAuteur) self.__layoutLivre = QtWidgets.QFormLayout() self.__layoutLivre.addRow("Titre : ", self.__champTexteTitreLivre) self.__layoutLivre.addRow("Date de publication : ", self.__champDatePublication) self.__widgetLivre.setLayout(self.__layoutLivre) # La boîte à onglets self.__tabWidget = QtWidgets.QTabWidget() self.__tabWidget.addTab(self.__widgetAuteur, "Auteur") self.__tabWidget.addTab(self.__widgetLivre, "Livre") # Le layout final self.__mainLayout = QtWidgets.QVBoxLayout() self.__mainLayout.addWidget(self.__tabWidget) self.setLayout(self.__mainLayout) app = QtWidgets.QApplication(sys.argv) dialog = Dialog() dialog.exec_()
Cette classe permet de regrouper des widgets dans une boîte avec un titre. Elles sont souvent utilisées pour organiser les choix proposés. Voici les méthodes offertes par cette classe :
Méthode | Description |
---|---|
setLayout(layout) | Définit le layout passé en argument comme le layout utilisé pour cette instance. |
setChecked(bool) | Permet de créer une boîte de regroupement optionnelle. |
isChecked() | Pour les boîtes de regroupement optionnelles, retourne si le groupe a été coché. |
Les zones de défilement sont utilisées pour l'affichage de widgets de grande taille tels que des images, des tableaux ou des zones de texte. Elles font apparaître des ascenseurs pour pouvoir faire défiler les zones non visibles. Cette classe ne contient qu'un seul widget placé avec la méthode setWidget(widget).
Le panneau séparé permet de placer plusieurs widgets côte à côte séparés par un séparateur pouvant être déplacé par l'utilisateur. La géométrie des widgets dépend donc de la position de ce séparateur. Il est possible d'ajouter plusieurs composants. Voici les méthodes de cette classe :
Méthode | Description |
---|---|
addWidget(widget) | Ajoute un widget aux panneaux. |
setStretchFactor(index, entier) | Permet de définir un cœfficient de la taille occupée par chaque widget. |
setOrientation(arg) | Permet de modifier l'orientation du panneau séparé. Voici les arguments possibles :
|
Cette classe permet l'affichage d'éléments sous forme d'une liste. Elle permet la vue en liste (par défaut) ou par icônes. Voici les méthodes offertes par la classe QListWidget :
Méthode | Description |
---|---|
addItem(chaine) | Ajoute un élément à la liste (texte seul). |
addItem(item) | Ajoute un objet QListWidgetItem à la liste (texte et icône). |
insertItem(l, chaine) | Permet d'ajouter un ou plusieurs éléments à la position pendant l'exécution du programme. |
insertItems(l, liste) | |
setViewMode(arg) | Permet de modifier le mode d'affichage de la liste :
|
currentRow() | Retourne l'index de la ligne sélectionnée. |
currentItem() | Retourne l'objet QListWidgetItem correspondant à la ligne sélectionnée. |
clear() | Efface les entrées présentes. |
Cette classe génère de nombreux signaux dont en voici un extrait :
Signal | Déclencheur |
---|---|
currentItemCanged | Déclenché lors du changement d'éléments sélectionnés. Retourne l'élément précédemment sélectionné et l'élément nouvellement sélectionné. |
itemActivated | Déclenché lors de la sélection d'un élément. Retourne l'élément sélectionné. |
itemClicked | Déclenché lors du clic d'un élément. Retourne l'élément cliqué. |
itemDoubleClicked | Déclenché lors du double-clic d'un élément. Retourne l'élément double-cliqué. |
Il est possible d'afficher des données sous la forme d'une table. Chaque cellule contient une instance de la classe QtWidgets.QTableWidgetItem. Lors de sa création, on passe le nombre de lignes et de colonnes en argument du constructeur. Voici les méthodes usuelles :
Méthode | Description |
---|---|
setItem(ligne, colonne, item) | Définit la cellule spécifiée par sa ligne et sa colonne. L'item passé en argument est un objet QTableWidgetItem(chaine). |
setHorizontalHeaderLabels (liste) | Modifie les en-têtes des colonnes de la table. |
setVerticalHeaderLabels (liste) | Modifie les en-têtes des lignes de la table. |
setRowCount(nombre) | Définit le nombre de lignes passées en argument. |
setColumnCount(nombre) | Définit le nombre de colonnes passées en argument. |
rowCount() | Retourne le nombre de lignes de la table. |
columnCount() | Retourne le nombre de colonnes de la table. |
Voici quelques signaux proposés :
Signal | Déclencheur |
---|---|
currentItemCanged | Déclenché lors du changement d'éléments sélectionnés. Retourne l'élément précédemment sélectionné et l'élément nouvellement sélectionné. |
itemActivated | Déclenché lors de la sélection d'un élément. Retourne l'élément sélectionné. |
itemClicked | Déclenché lors du clic d'un élément. Retourne l'élément cliqué. |
itemDoubleClicked | Déclenché lors du double-clic d'un élément. Retourne l'élément double-cliqué. |
currentCellCanged | Déclenché lors du changement d'éléments sélectionnés. Retourne les coordonnées de l'élément précédemment sélectionné et les coordonnées de l'élément nouvellement sélectionné. |
cellActivated | Déclenché lors de la sélection d'un élément. Retourne les coordonnées de l'élément sélectionné. |
cellClicked | Déclenché lors du clic d'un élément. Retourne les coordonnées de l'élément cliqué. |
cellDoubleClicked | Déclenché lors du double-clic d'un élément. Retourne les coordonnées de l'élément double-cliqué. |
Il est possible de représenter les données sous la forme d'un arbre. Chaque élément de l'arbre est une instance de la classe QtWidgets.QTreeWidgetItem avec en argument la chaîne de caractères de l'élément. Pour ajouter un élément enfant, on utilise la méthode addChild(item) avec comme argument l'item enfant implémentant QTreeWidgetItem. On définit l'élément racine de l'arbre avec la méthode addTopLevelItem(item). Les signaux sont identiques à la classe QListWidget.
Pour simplifier nos programmes, il existe la classe QInputDialog qui permet de demander une donnée à l'utilisateur. Cette boîte de dialogue comporte le champ à saisir, un titre, un message, un bouton pour valider et un bouton pour annuler. Cette classe s'utilise comme suit :
age = QtWidgets.QInputDialog.getInt(parent, "Votre âge", "Entrez votre âge : ")
Les méthodes offertes permettent de déterminer le type de données à demander :
Méthode | Description |
---|---|
getInt(parent, titre, message, valeur) | Demande un entier à l'utilisateur. |
getDouble(parent, titre, message, valeur) | Demande un réel à l'utilisateur. |
getItem(parent, titre, message, listeValeurs, editable) | Demande un élément parmi la liste à l'utilisateur. Peut être modifiable si editable=True. |
getText(parent, titre, message) | Demande une chaîne à l'utilisateur. Peut être caché si echo=QtWidgets.QLineEdit.Password. |
Cette boîte de dialogue permet de choisir une couleur parmi un nuancier. Voici les méthodes offertes par cette classe :
Méthode | Description |
---|---|
selectedColor() | Retourne la couleur choisie par l'utilisateur (classe QColor). |
setCurrentColor(couleur) | Définit la couleur de la boîte de dialogue. |
getColor() | Ouvre la boîte de dialogue pour choisir la couleur. |
Voici les signaux proposés par cette classe :
Signal | Déclencheur |
---|---|
colorSelected | Déclenché lors de la sélection d'une couleur. Retourne la couleur sélectionnée. |
currentColorChanged | Déclenché lors du changement de couleur choisie. Retourne la couleur sélectionnée. |
Cette boîte de dialogue permet de choisir une fonte parmi les polices installées sur le système. Voici les méthodes offertes par cette classe :
Méthode | Description |
---|---|
selectedFont() | Retourne la fonte choisie par l'utilisateur (classe QFont). |
setCurrentFont(fonte) | Définit la fonte de la boîte de dialogue. |
getFont() | Ouvre la boîte de dialogue pour choisir la fonte. |
Voici les signaux proposés par cette classe :
Signal | Déclencheur |
---|---|
fontSelected | Déclenché lors de la sélection d'une fonte. Retourne la fonte sélectionnée. |
currentFontChanged | Déclenché lors du changement de fonte choisie. Retourne la fonte sélectionnée. |
La boîte de dialogue QFileDialog permet de choisir un fichier ou un répertoire. Voici les méthodes permettant de créer ou choisir un fichier ou un répertoire. Toutes les méthodes présentées retournent le chemin du fichier et le filtre choisis dans le cas des fichiers :
Méthode | Description |
---|---|
getExistingDirectory() | Permet de sélectionner un répertoire. |
getOpenFileName() | Permet de sélectionner un fichier à ouvrir. |
getOpenFileNames() | Permet de sélectionner un ou plusieurs fichiers à ouvrir. |
getSaveFileName() | Permet de sauvegarder un fichier. |
Les layouts permettent de placer les widgets dans les conteneurs (fenêtre, QTabWidget, …). Voici les layouts proposés par PySide.
Ces layouts simplifient la mise en place des widgets en les juxtaposant (verticalement avec QVBoxLayout et horizontalement avec QHBoxLayout) avec la méthode addWidget(widget). Il est cependant possible d'ajouter un layout au sein du layout actuel avec la méthode addLayout(layout). Il est enfin possible d'ajouter un espace élastique qui occupe tout l'espace restant lors du redimensionnement de la fenêtre avec addStretch.
Voici un exemple de mise en œuvre de ces layouts.
Voici le code source permettant d'obtenir ce résultat :
import sys from PySide2 import QtCore, QtGui, QtWidgets class Dialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self,parent) self.setWindowTitle("Saisie de tarif") self.__labelLibelle = QtWidgets.QLabel("Libellé : ") self.__champLibelle = QtWidgets.QLineEdit("") self.__layoutLibelle = QtWidgets.QHBoxLayout() self.__layoutLibelle.addWidget(self.__labelLibelle) self.__layoutLibelle.addWidget(self.__champLibelle) self.__labelPrixHT = QtWidgets.QLabel("Prix HT : ") self.__champPrixHT = QtWidgets.QDoubleSpinBox() self.__champPrixHT.setSuffix("€") self.__labelTauxTVA = QtWidgets.QLabel("TVA : ") self.__champTauxTVA = QtWidgets.QDoubleSpinBox() self.__champTauxTVA.setSuffix("%") self.__layoutPrix = QtWidgets.QHBoxLayout() self.__layoutPrix.addWidget(self.__labelPrixHT) self.__layoutPrix.addWidget(self.__champPrixHT) self.__layoutPrix.addWidget(self.__labelTauxTVA) self.__layoutPrix.addWidget(self.__champTauxTVA) self.__boutonAnnuler = QtWidgets.QPushButton("Annuler") self.__boutonValider = QtWidgets.QPushButton("Valider") self.__layoutBoutons = QtWidgets.QHBoxLayout() self.__layoutBoutons.addWidget(self.__boutonAnnuler) self.__layoutBoutons.addStretch() self.__layoutBoutons.addWidget(self.__boutonValider) self.__layoutPrincipal = QtWidgets.QVBoxLayout() self.__layoutPrincipal.addLayout(self.__layoutLibelle) self.__layoutPrincipal.addLayout(self.__layoutPrix) self.__layoutPrincipal.addLayout(self.__layoutBoutons) self.setLayout(self.__layoutPrincipal) app = QtWidgets.QApplication(sys.argv) dialog = Dialog() dialog.exec_()
Le placement en formulaire permet de simplifier le code source de votre application en proposant un layout mettant en forme les widgets en formulaire. Cette mise en forme est divisée en deux colonnes, avec à gauche les labels associés aux widgets situés à droite.
Le layout possède la méthode addRow(chaine, widget) qui crée le label associé aux widgets avec comme texte la chaîne passée en argument.
Nous avons déjà vu ce type de layout au début de ce chapitre. Nous ajouterons comment faire en sorte qu'un widget ou un layout occupent plusieurs lignes ou colonnes. Pour cela, il faut ajouter deux arguments permettant de spécifier le nombres de lignes et de colonnes occupées.
Voici un exemple de mise en œuvre de cette fusion de cellules.
layout = QtWidgets.QGridLayout() layout.addWidget(widget0, 0 ,0, 1, 2) layout.addWidget(widget1, 0 ,2) layout.addWidget(widget2, 1 ,0, 2, 1) layout.addWidget(widget3, 1 ,1) layout.addWidget(widget4, 1 ,2) layout.addWidget(widget5, 2 ,1) layout.addWidget(widget6, 2 ,2)
Code source
Widget 0 | Widget 1 | |
Widget 2 | Widget 3 | Widget 4 |
Widget 5 | Widget 6 |
Rendu
Les fenêtres des applications sont généralement construites avec la classe QMainWindow qui gère automatiquement les barres d'outils, les menus, les barres d'états …
Nous allons étudier cette classe en créant un bloc-notes permettant d'ouvrir, d'éditer et d'enregistrer un fichier. Notre application s'articulera autour d'une zone de texte. Voici un schéma de la fenêtre à concevoir.
Nous allons instancier la classe QMainWindow qui est affichée en appelant la méthode show() ou la méthode setVisible(bool). Dans cette partie, nous aborderons uniquement la construction de la fenêtre avec la barre d'outils et de menu.
import sys from PySide2 import QtCore, QtGui, QtWidgets class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # La fenêtre sera décrite ici self.show() app = QtWidgets.QApplication(sys.argv) fenetre = BlocNotes() app.exec_()
Nous allons créer la zone de texte centrale et la définir comme widget central de la fenêtre :
class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.setWindowTitle("Bloc-notes") self.__zoneTexte = QtWidgets.QTextEdit() self.setCentralWidget(self.__zoneTexte) # ...
Pour définir un layout comme widget central, créez une instance QtWidgets.QWidget, affectez votre layout à ce widget avec la méthode setLayout et définissez ce widget comme widget central.
Dans notre application, nous avons la barre de menu et la barre d'outils qui comportent les mêmes actions. La classe QAction permet de regrouper tous ces éléments graphiques et les associer à la même méthode, en y ajoutant une icône et un raccourci clavier. Cette classe peut être utilisée de trois manières différentes :
Voici quelques méthodes offertes par cette classe :
Méthode | Description |
---|---|
setStatusTip(chaine) | Définit le texte affiché dans la barre d'actions des fenêtres. |
setShortcuts(raccourci) | Définit le raccourci clavier. L'argument est une instance de la classe QKeySequence. |
Les actions génèrent le signal triggered lorsqu'elles sont activées. Voici les actions utilisées pour notre application :
class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # ... self.__actionNew = QtWidgets.QAction(QtGui.QIcon("document-new.svg"), "Nouveau", self) self.__actionNew.setShortcuts(QtGui.QKeySequence.New) self.__actionNew.setStatusTip("Nouveau document") self.__actionOpen = QtWidgets.QAction(QtGui.QIcon("document-open.svg"), "Ouvrir", self) self.__actionOpen.setShortcuts(QtGui.QKeySequence.Open) self.__actionOpen.setStatusTip("Ouvrir un document existant") self.__actionSave = QtWidgets.QAction(QtGui.QIcon("document-save.svg"), "Enregistrer", self) self.__actionSave.setShortcuts(QtGui.QKeySequence.Save) self.__actionSave.setStatusTip("Enregistrer le document") self.__actionSaveAs = QtWidgets.QAction(QtGui.QIcon("document-save-as.svg"), "Enregistrer sous", self) self.__actionSaveAs.setShortcuts(QtGui.QKeySequence.SaveAs) self.__actionSaveAs.setStatusTip("Enregistrer le document sous") self.__actionQuit = QtWidgets.QAction(QtGui.QIcon("exit.svg"), "Quitter", self) self.__actionQuit.setShortcuts(QtGui.QKeySequence.Quit) self.__actionQuit.setStatusTip("Quitter l'application") self.__actionUndo = QtWidgets.QAction(QtGui.QIcon("undo.svg"), "Annuler", self) self.__actionUndo.setShortcuts(QtGui.QKeySequence.Undo) self.__actionUndo.setStatusTip("Annuler la dernière opération") self.__actionRedo = QtWidgets.QAction(QtGui.QIcon("redo.svg"), "Refaire", self) self.__actionRedo.setShortcuts(QtGui.QKeySequence.Redo) self.__actionRedo.setStatusTip("Refaire la dernière opération") self.__actionCut = QtWidgets.QAction(QtGui.QIcon("edit-cut.svg"), "Couper", self) self.__actionCut.setShortcuts(QtGui.QKeySequence.Cut) self.__actionCut.setStatusTip("Couper le texte vers le presse-papier") self.__actionCopy = QtWidgets.QAction(QtGui.QIcon("edit-copy.svg"), "Copier", self) self.__actionCopy.setShortcuts(QtGui.QKeySequence.Copy) self.__actionCopy.setStatusTip("Copier le texte vers le presse-papier") self.__actionPaste = QtWidgets.QAction(QtGui.QIcon("edit-paste.svg"), "Coller", self) self.__actionPaste.setShortcuts(QtGui.QKeySequence.Paste) self.__actionPaste.setStatusTip("Coller le texte depuis le presse-papier") # ...
Nous allons associer les actions aux méthodes créées (non décrites ici) :
class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # ... self.__actionNew.triggered.connect(self.newDocument) self.__actionOpen.triggered.connect(self.openDocument) self.__actionSave.triggered.connect(self.saveDocument) self.__actionSaveAs.triggered.connect(self.saveAsDocument) self.__actionQuit.triggered.connect(self.quit) self.__actionUndo.triggered.connect(self.undo) self.__actionRedo.triggered.connect(self.redo) self.__actionCut.triggered.connect(self.cut) self.__actionCopy.triggered.connect(self.copy) self.__actionPaste.triggered.connect(self.paste) # ...
Nous allons à présent utiliser les actions précédemment créées pour les insérer dans des menus. Pour cela, nous allons créer deux menus : le menu Fichier et le menu Édition. Pour créer un menu, on appelle la méthode addMenu(nom) de la fenêtre QMainWindow. On passe en argument le nom du menu.
Ce nouveau menu accepte deux méthodes, addAction(action) qui ajoute une action au menu, et la méthode addSeparator() qui ajoute un séparateur.
Voici la création de nos deux menus :
class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # ... self.__menuFile = self.menuBar().addMenu("Fichier") self.__menuFile.addAction(self.__actionNew) self.__menuFile.addAction(self.__actionOpen) self.__menuFile.addAction(self.__actionSave) self.__menuFile.addAction(self.__actionSaveAs) self.__menuFile.addSeparator() self.__menuFile.addAction(self.__actionQuit) self.__menuEdit = self.menuBar().addMenu("Édition") self.__menuEdit.addAction(self.__actionUndo) self.__menuEdit.addAction(self.__actionRedo) self.__menuEdit.addSeparator() self.__menuEdit.addAction(self.__actionCut) self.__menuEdit.addAction(self.__actionCopy) self.__menuEdit.addAction(self.__actionPaste) # ...
À l'instar des barres de menu, la classe QMainWindow possède une méthode addToolBar(nom) avec le nom passé en argument. Ces barres d'outils possèdent deux méthodes addAction(action) qui ajoute une action au menu et la méthode addSeparator() qui ajoute un séparateur.
Voici la création de la barre d'outils :
class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # ... self.__barreFile = self.addToolBar("Fichier") self.__barreFile.addAction(self.__actionNew) self.__barreFile.addAction(self.__actionOpen) self.__barreFile.addAction(self.__actionSave) self.__barreEdit = self.addToolBar("Édition") self.__barreEdit.addAction(self.__actionUndo) self.__barreEdit.addAction(self.__actionRedo) self.__barreEdit.addAction(self.__actionCut) self.__barreEdit.addAction(self.__actionCopy) self.__barreEdit.addAction(self.__actionPaste) # ...Voici le code source complet de notre application :
#!/usr/bin/env python3 import sys from PySide2 import QtCore, QtGui, QtWidgets class BlocNotes(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.setWindowTitle("Bloc-notes") self.__zoneTexte = QtWidgets.QTextEdit() self.setCentralWidget(self.__zoneTexte) self.__actionNew = QtWidgets.QAction(QtGui.QIcon("document-new.svg"), "Nouveau", self) self.__actionNew.setShortcuts(QtGui.QKeySequence.New) self.__actionNew.setStatusTip("Nouveau document") self.__actionOpen = QtWidgets.QAction(QtGui.QIcon("document-open.svg"), "Ouvrir", self) self.__actionOpen.setShortcuts(QtGui.QKeySequence.Open) self.__actionOpen.setStatusTip("Ouvrir un document existant") self.__actionSave = QtWidgets.QAction(QtGui.QIcon("document-save.svg"), "Enregistrer", self) self.__actionSave.setShortcuts(QtGui.QKeySequence.Save) self.__actionSave.setStatusTip("Enregistrer le document") self.__actionSaveAs = QtWidgets.QAction(QtGui.QIcon("document-save-as.svg"), "Enregistrer sous", self) self.__actionSaveAs.setShortcuts(QtGui.QKeySequence.SaveAs) self.__actionSaveAs.setStatusTip("Enregistrer le document sous") self.__actionQuit = QtWidgets.QAction(QtGui.QIcon("exit.svg"), "Quitter", self) self.__actionQuit.setShortcuts(QtGui.QKeySequence.Quit) self.__actionQuit.setStatusTip("Quitter l'application") self.__actionUndo = QtWidgets.QAction(QtGui.QIcon("undo.svg"), "Annuler", self) self.__actionUndo.setShortcuts(QtGui.QKeySequence.Undo) self.__actionUndo.setStatusTip("Annuler la dernière opération") self.__actionRedo = QtWidgets.QAction(QtGui.QIcon("redo.svg"), "Refaire", self) self.__actionRedo.setShortcuts(QtGui.QKeySequence.Redo) self.__actionRedo.setStatusTip("Refaire la dernière opération") self.__actionCut = QtWidgets.QAction(QtGui.QIcon("edit-cut.svg"), "Couper", self) self.__actionCut.setShortcuts(QtGui.QKeySequence.Cut) self.__actionCut.setStatusTip("Couper le texte vers le presse-papier") self.__actionCopy = QtWidgets.QAction(QtGui.QIcon("edit-copy.svg"), "Copier", self) self.__actionCopy.setShortcuts(QtGui.QKeySequence.Copy) self.__actionCopy.setStatusTip("Copier le texte vers le presse-papier") self.__actionPaste = QtWidgets.QAction(QtGui.QIcon("edit-paste.svg"), "Coller", self) self.__actionPaste.setShortcuts(QtGui.QKeySequence.Paste) self.__actionPaste.setStatusTip("Coller le texte depuis le presse-papier") self.__actionNew.triggered.connect(self.newDocument) self.__actionOpen.triggered.connect(self.openDocument) self.__actionSave.triggered.connect(self.saveDocument) self.__actionSaveAs.triggered.connect(self.saveAsDocument) self.__actionQuit.triggered.connect(self.quit) self.__actionUndo.triggered.connect(self.undo) self.__actionRedo.triggered.connect(self.redo) self.__actionCut.triggered.connect(self.cut) self.__actionCopy.triggered.connect(self.copy) self.__actionPaste.triggered.connect(self.paste) self.__menuFile = self.menuBar().addMenu("Fichier") self.__menuFile.addAction(self.__actionNew) self.__menuFile.addAction(self.__actionOpen) self.__menuFile.addAction(self.__actionSave) self.__menuFile.addAction(self.__actionSaveAs) self.__menuFile.addSeparator() self.__menuFile.addAction(self.__actionQuit) self.__menuEdit = self.menuBar().addMenu("Édition") self.__menuEdit.addAction(self.__actionUndo) self.__menuEdit.addAction(self.__actionRedo) self.__menuEdit.addSeparator() self.__menuEdit.addAction(self.__actionCut) self.__menuEdit.addAction(self.__actionCopy) self.__menuEdit.addAction(self.__actionPaste) self.__barreFile = self.addToolBar("Fichier") self.__barreFile.addAction(self.__actionNew) self.__barreFile.addAction(self.__actionOpen) self.__barreFile.addAction(self.__actionSave) self.__barreEdit = self.addToolBar("Édition") self.__barreEdit.addAction(self.__actionUndo) self.__barreEdit.addAction(self.__actionRedo) self.__barreEdit.addAction(self.__actionCut) self.__barreEdit.addAction(self.__actionCopy) self.__barreEdit.addAction(self.__actionPaste) self.show() def fonct(self): pass app = QtWidgets.QApplication(sys.argv) fenetre = BlocNotes() app.exec_()
Voici le rendu de notre fenêtre.
Vous êtes nouvellement embauché dans une entreprise en bâtiment pour créer un programme permettant au secrétariat de saisir les estimations de travaux de l'entreprise. Votre application devra s'interfacer avec une base de données SQLite qui stockera le catalogue des prestations proposées par l'entreprise et leurs tarifs. Cette base de données contiendra également les estimations déjà réalisées.
Pour créer une estimation, il faut d'abord saisir les informations relatives au client (nom, prénom, adresse, code postal, ville, téléphone, courriel), le titre du chantier, puis choisir dans le catalogue les prestations à ajouter. Chaque prestation est dans une catégorie et comporte un texte la décrivant, un prix unitaire et une unité (définissant le prix unitaire). Voici un exemple :
Prestation | Prix unitaire (€/unité) | Unité |
---|---|---|
Cloison sur ossature métallique. | 45 | m² |
Si la prestation n'existe pas, une boîte de dialogue permet d'en ajouter, de même pour les catégories. Lors de l'ajout d'une prestation à l'estimation, l'utilisateur doit choisir un taux de TVA (exprimé en %). Une fois la saisie de la prestation terminée, les totaux hors-taxes, de TVA et le total TTC (hors-taxes + TVA) sont automatiquement mis à jour.
Enfin, il sera possible d'exporter l'estimation au format texte suivant l'exemple ci-dessous :
Société Bati Plus 52 rue de Clairecombe 74930 Moulincourbe Lionel Paulin 48 Ruelle de Locvaux 74019 Mivran 01 98 74 30 52 lionel.paulin@exemple.com Estimation numéro 524 réalisée le 10 avril 2017. Chantier de plâtrerie Prestation Prix unitaire Quantité Total HT TVA Total TTC Cloison sur ossature métallique 45 €/m² 17 m² 765€ 153€ (20%) 918€ Pose d'une porte 78 €/porte 1 porte 78€ 15,6€ (20%) 93,6€ Total HT : 843€ Total TVA : 168,6€ Total TTC : 1011,6€
Tous les totaux seront arrondis à deux décimales.
#!/usr/bin/env python3 import sys from PySide2 import QtCore, QtGui, QtWidgets import sqlite3 import os import time fichierBaseDeDonnees = "tarification.db" if os.path.isfile(fichierBaseDeDonnees) == False: baseDeDonnees = sqlite3.connect(fichierBaseDeDonnees) curseur = baseDeDonnees.cursor() curseur.execute("CREATE TABLE Prestations (id INTEGER PRIMARY KEY AUTOINCREMENT, categorie TEXT NOT NULL, libelle TEXT NOT NULL, prixUnitaire REAL NOT NULL, unite TEXT NOT NULL)") baseDeDonnees.commit() curseur.execute("CREATE TABLE Estimations (id INTEGER PRIMARY KEY AUTOINCREMENT, nomClient TEXT NOT NULL, prenomClient TEXT NOT NULL, adresse TEXT NOT NULL, codePostal TEXT NOT NULL, ville TEXT NOT NULL, telephone TEXT, courriel TEXT, nomChantier TEXT)") baseDeDonnees.commit() curseur.execute("CREATE TABLE LignesEstimations (id INTEGER PRIMARY KEY AUTOINCREMENT, idEstimation INTEGER NOT NULL, idPrestation INTEGER NOT NULL, ordre INTEGER, quantite REAL NOT NULL, tauxTVA REAL)") baseDeDonnees.commit() baseDeDonnees = sqlite3.connect(fichierBaseDeDonnees) curseur = baseDeDonnees.cursor() class EditionPrestation(QtWidgets.QDialog): def __init__(self,idPrestation=None,parent=None): QtWidgets.QDialog.__init__(self,parent) self.__idPrestation = idPrestation self.setWindowTitle("Éditer une prestation") self.setWindowIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/stock_edit.svg")) self.__categorie = QtWidgets.QComboBox() requete = """SELECT categorie FROM Prestations GROUP BY categorie ORDER BY categorie;""" curseur.execute(requete) self.__categorie.setEditable(True) self.__categorie.addItems([resultat[0] for resultat in curseur.fetchall()]) self.__texteLibelle = QtWidgets.QLineEdit("") self.__prixUnitaire = QtWidgets.QDoubleSpinBox() self.__prixUnitaire.setMaximum(99999.99) self.__prixUnitaire.setSuffix("€") self.__unite = QtWidgets.QLineEdit("") self.__valider = QtWidgets.QPushButton("Valider") self.__valider.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/dialog-apply.svg")) self.__annuler = QtWidgets.QPushButton("Annuler") self.__annuler.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/dialog-cancel.svg")) self.__layoutPrincipal = QtWidgets.QVBoxLayout() self.__layoutLigne1 = QtWidgets.QFormLayout() self.__layoutLigne1.addRow("Categorie :", self.__categorie) self.__layoutLigne2 = QtWidgets.QFormLayout() self.__layoutLigne2.addRow("Prestation :", self.__texteLibelle) self.__layoutLigne3 = QtWidgets.QFormLayout() self.__layoutLigne3.addRow("Prix unitaire :", self.__prixUnitaire) self.__layoutLigne4 = QtWidgets.QFormLayout() self.__layoutLigne4.addRow("Unité :", self.__unite) self.__layoutBoutons = QtWidgets.QHBoxLayout() self.__layoutBoutons.addWidget(self.__valider) self.__layoutBoutons.addWidget(self.__annuler) self.__layoutPrincipal.addLayout(self.__layoutLigne1) self.__layoutPrincipal.addLayout(self.__layoutLigne2) self.__layoutPrincipal.addLayout(self.__layoutLigne3) self.__layoutPrincipal.addLayout(self.__layoutLigne4) self.__layoutPrincipal.addLayout(self.__layoutBoutons) self.setLayout(self.__layoutPrincipal) self.__unite.textChanged.connect(self.modifierUnite) self.__valider.clicked.connect(self.valider) self.__annuler.clicked.connect(self.accept) if self.__idPrestation != None: requete = """SELECT categorie, libelle, prixUnitaire, unite FROM Prestations WHERE id = ?;""" curseur.execute(requete, (self.__idPrestation, )) lignePrestation = curseur.fetchone() self.__categorie.setEditText(lignePrestation[0]) self.__texteLibelle.setText(lignePrestation[1]) self.__prixUnitaire.setValue(lignePrestation[2]) self.__unite.setText(lignePrestation[3]) def valider(self): categorie = self.__categorie.currentText() texteLibelle = self.__texteLibelle.text() prixUnitaire = self.__prixUnitaire.value() unite = self.__unite.text() if self.__idPrestation == None: requete = """INSERT INTO Prestations (categorie, libelle, prixUnitaire, unite) VALUES (?, ?, ?, ?);""" curseur.execute(requete, (categorie, texteLibelle, prixUnitaire, unite)) baseDeDonnees.commit() else: requete = """UPDATE Prestations SET categorie = ?, libelle = ?, prixUnitaire = ?, unite = ? WHERE id = ?;""" curseur.execute(requete, (categorie, texteLibelle, prixUnitaire, unite, self.__idPrestation)) baseDeDonnees.commit() self.accept() def modifierUnite(self): suffixe = "€" if self.__unite.text() != "": suffixe += "/" + self.__unite.text() self.__prixUnitaire.setSuffix(suffixe) class OuvrirPrestation(QtWidgets.QDialog): def __init__(self,parent=None): QtWidgets.QDialog.__init__(self,parent) self.__donneesRetournees = None self.setWindowTitle("Tarification") self.setWindowIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/document-open.svg")) self.__listeChiffrages = QtWidgets.QListWidget() self.__chiffrages = [] self.listerChiffrages() self.__ouvrir = QtWidgets.QPushButton("Ouvrir") self.__ouvrir.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/document-open.svg")) self.__supprimer = QtWidgets.QPushButton("Supprimer") self.__supprimer.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/stock_delete.svg")) self.__annuler = QtWidgets.QPushButton("Annuler") self.__annuler.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/dialog-cancel.svg")) self.__layoutPrincipal = QtWidgets.QVBoxLayout() self.__layoutPrincipal.addWidget(self.__listeChiffrages) self.__layoutBouton = QtWidgets.QHBoxLayout() self.__layoutBouton.addWidget(self.__annuler) self.__layoutBouton.addWidget(self.__supprimer) self.__layoutBouton.addWidget(self.__ouvrir) self.__layoutPrincipal.addLayout(self.__layoutBouton) self.setLayout(self.__layoutPrincipal) self.__annuler.clicked.connect(self.accept) self.__supprimer.clicked.connect(self.supprimerChiffrage) self.__ouvrir.clicked.connect(self.ouvrirChiffrage) def getDonneesRetournees(self): return(self.__donneesRetournees) def listerChiffrages(self): self.__listeChiffrages.clear() self.__chiffrages = [] requete = """SELECT id, nomClient, prenomClient, nomChantier FROM Estimations;""" curseur.execute(requete) for noLigne, ligne in enumerate(curseur.fetchall()): self.__chiffrages.append({"id":ligne[0], "nomClient":ligne[1], "prenomClient":ligne[2], "nomChantier":ligne[3]}) self.__listeChiffrages.insertItem(noLigne, ligne[2] + " " + ligne[1] + " - " + ligne[3]) def supprimerChiffrage(self): index = self.__listeChiffrages.currentRow() idChiffrage = self.__chiffrages[index]["id"] requete = """DELETE FROM Estimations WHERE id = ?;""" curseur.execute(requete, (idChiffrage, )) baseDeDonnees.commit() self.listerChiffrages() def ouvrirChiffrage(self): index = self.__listeChiffrages.currentRow() idChiffrage = self.__chiffrages[index]["id"] lignesChiffrage = [] donneesClient = {} requete = """SELECT nomClient, prenomClient, adresse, codePostal, ville, telephone, courriel, nomChantier FROM Estimations WHERE id = ?;""" curseur.execute(requete, (idChiffrage, )) resultat = curseur.fetchone() donneesClient["nomClient"] = resultat[0] donneesClient["prenomClient"] = resultat[1] donneesClient["adresse"] = resultat[2] donneesClient["codePostal"] = resultat[3] donneesClient["ville"] = resultat[4] donneesClient["telephone"] = resultat[5] donneesClient["courriel"] = resultat[6] donneesClient["nomChantier"] = resultat[7] requete = """SELECT idPrestation, libelle, prixUnitaire, unite, quantite, tauxTVA FROM LignesEstimations JOIN Prestations ON LignesEstimations.idPrestation = Prestations.id WHERE idEstimation = ? ORDER BY ordre;""" curseur.execute(requete, (idChiffrage, )) for ligne in curseur.fetchall(): ligneChiffrage = {} ligneChiffrage["idPrestation"] = ligne[0] ligneChiffrage["libelle"] = ligne[1] ligneChiffrage["prixUnitaire"] = float(ligne[2]) ligneChiffrage["unite"] = ligne[3] ligneChiffrage["quantite"] = float(ligne[4]) ligneChiffrage["tva"] = float(ligne[5]) ligneChiffrage["prixHT"] = round(ligneChiffrage["prixUnitaire"] * ligneChiffrage["quantite"], 2) ligneChiffrage["prixTVA"] = round(ligneChiffrage["prixHT"] * ligneChiffrage["tva"] / 100.0, 2) ligneChiffrage["prixTTC"] = round(ligneChiffrage["prixHT"] + ligneChiffrage["prixTVA"], 2) lignesChiffrage.append(ligneChiffrage) self.__donneesRetournees = {"idChiffrage":idChiffrage, "lignesChiffrage":lignesChiffrage, "donneesClient":donneesClient} self.accept() class Tarification(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.setWindowTitle("Tarification") self.setWindowIcon(QtGui.QIcon("/usr/share/icons/Humanity/emblems/32/emblem-money.svg")) self.__listeCategories = QtWidgets.QComboBox() self.__idChiffrage = None self.__prestations = [] self.__lignesChiffrage = [] self.__listePrestations = QtWidgets.QListWidget() self.__barreRecherche = QtWidgets.QLineEdit("") self.__creerPrestation = QtWidgets.QPushButton("Créer une prestation") self.__creerPrestation.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/edit-add.svg")) self.__modifierPrestation = QtWidgets.QPushButton("Modifier une prestation") self.__modifierPrestation.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/stock_edit.svg")) self.__supprimerPrestation = QtWidgets.QPushButton("Supprimer une prestation") self.__supprimerPrestation.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/stock_delete.svg")) self.__prixUnitaire = 0.0 self.__unite = "" self.__prixHT = 0.0 self.__prixTVA = 0.0 self.__prixTTC = 0.0 self.__prixUnitaireLabel = QtWidgets.QLabel("Prix unitaire : 0,00€") self.__quantiteLabel = QtWidgets.QLabel("Quantité : ") self.__quantite = QtWidgets.QDoubleSpinBox() self.__tvaLabel = QtWidgets.QLabel("TVA : ") self.__tva = QtWidgets.QDoubleSpinBox() self.__tva.setSuffix("%") self.__totauxLabel = QtWidgets.QLabel("HT : 0,00 €\nTVA : 0,00 €\nTTC : 0,00 €") self.__ajouterPrestation = QtWidgets.QPushButton("Ajouter la prestation") self.__ajouterPrestation.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/dialog-apply.svg")) self.__prestationLayout = QtWidgets.QVBoxLayout() self.__listeCategoriesLayout = QtWidgets.QFormLayout() self.__listeCategoriesLayout.addRow("Catégorie :", self.__listeCategories) self.__prestationLayout.addLayout(self.__listeCategoriesLayout) self.__actionPrestationLayout = QtWidgets.QHBoxLayout() self.__actionPrestationLayout.addWidget(self.__creerPrestation) self.__actionPrestationLayout.addWidget(self.__modifierPrestation) self.__actionPrestationLayout.addWidget(self.__supprimerPrestation) self.__prestationLayout.addLayout(self.__actionPrestationLayout) self.__barreRechercheLayout = QtWidgets.QFormLayout() self.__barreRechercheLayout.addRow("Recherche :", self.__barreRecherche) self.__prestationLayout.addLayout(self.__barreRechercheLayout) self.__prestationLayout.addWidget(self.__listePrestations) self.__quantiteTVALayout = QtWidgets.QHBoxLayout() self.__quantiteTVALayout.addWidget(self.__prixUnitaireLabel) self.__quantiteTVALayout.addStretch() self.__quantiteTVALayout.addWidget(self.__quantiteLabel) self.__quantiteTVALayout.addWidget(self.__quantite) self.__quantiteTVALayout.addStretch() self.__quantiteTVALayout.addWidget(self.__tvaLabel) self.__quantiteTVALayout.addWidget(self.__tva) self.__prestationLayout.addLayout(self.__quantiteTVALayout) self.__prestationLayout.addWidget(self.__totauxLabel) self.__prestationLayout.addWidget(self.__ajouterPrestation) self.__nomClient = QtWidgets.QLineEdit("") self.__prenomClient = QtWidgets.QLineEdit("") self.__adresseClient = QtWidgets.QLineEdit("") self.__cpClient = QtWidgets.QLineEdit("") self.__cpClient.setInputMask("00000;_") self.__villeClient = QtWidgets.QLineEdit("") self.__telephoneClient = QtWidgets.QLineEdit("") self.__telephoneClient.setInputMask("00 00 00 00 00;_") self.__courrielClient = QtWidgets.QLineEdit("") self.__nomChantier = QtWidgets.QLineEdit("") self.__nomClientLabel = QtWidgets.QLabel("Nom :") self.__prenomClientLabel = QtWidgets.QLabel("Prénom :") self.__adresseClientLabel = QtWidgets.QLabel("Adresse :") self.__cpClientLabel = QtWidgets.QLabel("Code postal :") self.__villeClientLabel = QtWidgets.QLabel("Ville :") self.__telephoneClientLabel = QtWidgets.QLabel("Téléphone :") self.__courrielClientLabel = QtWidgets.QLabel("Courriel :") self.__nomChantierLabel = QtWidgets.QLabel("Nom du chantier :") self.__ouvrir = QtWidgets.QPushButton("Ouvrir") self.__ouvrir.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/document-open.svg")) self.__enregistrer = QtWidgets.QPushButton("Enregistrer") self.__enregistrer.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/document-save.svg")) self.__exporter = QtWidgets.QPushButton("Exporter") self.__exporter.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/document-export.svg")) self.__monter = QtWidgets.QPushButton("Monter") self.__monter.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/go-up.svg")) self.__descendre = QtWidgets.QPushButton("Descendre") self.__descendre.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/go-down.svg")) self.__supprimerLigne = QtWidgets.QPushButton("Supprimer") self.__supprimerLigne.setIcon(QtGui.QIcon("/usr/share/icons/Humanity/actions/24/edit-delete.svg")) self.__tableChiffrage = QtWidgets.QTableWidget(0,6) enTeteTable = ("Prestation", "Prix unitaire", "Quantité", "Total HT", "TVA", "Total TTC") self.__tableChiffrage.setHorizontalHeaderLabels(enTeteTable) self.__totauxEstimationLabel = QtWidgets.QLabel("HT : 0,00 €\nTVA : 0,00 €\nTTC : 0,00 €") self.__chiffrageLayout = QtWidgets.QVBoxLayout() self.__clientFormLigne1 = QtWidgets.QHBoxLayout() self.__clientFormLigne1.addWidget(self.__nomClientLabel) self.__clientFormLigne1.addWidget(self.__nomClient) self.__clientFormLigne1.addWidget(self.__prenomClientLabel) self.__clientFormLigne1.addWidget(self.__prenomClient) self.__chiffrageLayout.addLayout(self.__clientFormLigne1) self.__clientFormLigne2 = QtWidgets.QHBoxLayout() self.__clientFormLigne2.addWidget(self.__adresseClientLabel) self.__clientFormLigne2.addWidget(self.__adresseClient) self.__chiffrageLayout.addLayout(self.__clientFormLigne2) self.__clientFormLigne3 = QtWidgets.QHBoxLayout() self.__clientFormLigne3.addWidget(self.__cpClientLabel) self.__clientFormLigne3.addWidget(self.__cpClient) self.__clientFormLigne3.addWidget(self.__villeClientLabel) self.__clientFormLigne3.addWidget(self.__villeClient) self.__chiffrageLayout.addLayout(self.__clientFormLigne3) self.__clientFormLigne4 = QtWidgets.QHBoxLayout() self.__clientFormLigne4.addWidget(self.__telephoneClientLabel) self.__clientFormLigne4.addWidget(self.__telephoneClient) self.__clientFormLigne4.addWidget(self.__courrielClientLabel) self.__clientFormLigne4.addWidget(self.__courrielClient) self.__chiffrageLayout.addLayout(self.__clientFormLigne4) self.__clientFormLigne5 = QtWidgets.QHBoxLayout() self.__clientFormLigne5.addWidget(self.__nomChantierLabel) self.__clientFormLigne5.addWidget(self.__nomChantier) self.__chiffrageLayout.addLayout(self.__clientFormLigne5) self.__boutonsLayout = QtWidgets.QHBoxLayout() self.__boutonsLayout.addWidget(self.__ouvrir) self.__boutonsLayout.addWidget(self.__enregistrer) self.__boutonsLayout.addWidget(self.__exporter) self.__boutonsLayout.addWidget(self.__monter) self.__boutonsLayout.addWidget(self.__descendre) self.__boutonsLayout.addWidget(self.__supprimerLigne) self.__chiffrageLayout.addLayout(self.__boutonsLayout) self.__chiffrageLayout.addWidget(self.__tableChiffrage) self.__chiffrageLayout.addWidget(self.__totauxEstimationLabel) self.__prestationLayoutWidget = QtWidgets.QWidget() self.__prestationLayoutWidget.setLayout(self.__prestationLayout) self.__chiffrageLayoutWidget = QtWidgets.QWidget() self.__chiffrageLayoutWidget.setLayout(self.__chiffrageLayout) self.__centralWidget = QtWidgets.QSplitter() self.__centralWidget.addWidget(self.__prestationLayoutWidget) self.__centralWidget.addWidget(self.__chiffrageLayoutWidget) self.setCentralWidget(self.__centralWidget) self.peuplerCategories() self.peuplerPrestations() self.__listeCategories.currentIndexChanged.connect(self.peuplerPrestations) self.__barreRecherche.textChanged.connect(self.peuplerPrestations) self.__listePrestations.itemClicked.connect(self.ouvrirFiche) self.__quantite.valueChanged.connect(self.calculerTotaux) self.__tva.valueChanged.connect(self.calculerTotaux) self.__ajouterPrestation.clicked.connect(self.ajouterLigneChiffrage) self.__monter.clicked.connect(self.monterLigne) self.__descendre.clicked.connect(self.descendreLigne) self.__supprimerLigne.clicked.connect(self.supprimerLigne) self.__creerPrestation.clicked.connect(self.nouvellePrestation) self.__modifierPrestation.clicked.connect(self.modifierPrestation) self.__supprimerPrestation.clicked.connect(self.supprimerPrestation) self.__enregistrer.clicked.connect(self.enregistrerChiffrage) self.__exporter.clicked.connect(self.exporterChiffrage) self.__ouvrir.clicked.connect(self.ouvrirPrestation) self.show() def peuplerCategories(self): self.__listeCategories.clear() requete = """SELECT categorie FROM Prestations GROUP BY categorie ORDER BY categorie;""" curseur.execute(requete) self.__listeCategories.insertItems(0, [resultat[0] for resultat in curseur.fetchall()]) def peuplerPrestations(self): self.__listePrestations.clear() self.__prestations = [] requete = """SELECT id, libelle FROM Prestations WHERE categorie = ? AND libelle LIKE ?;""" curseur.execute(requete, (self.__listeCategories.currentText(), "%" + self.__barreRecherche.text() + "%")) listeAAfficher = [] for resultat in curseur.fetchall(): self.__prestations.append({"id":resultat[0], "libelle":resultat[1]}) listeAAfficher.append(resultat[1]) self.__listePrestations.insertItems(0, listeAAfficher) def ouvrirFiche(self): index = self.__listePrestations.currentRow() idPrestation = self.__prestations[index]["id"] requete = """SELECT prixUnitaire, unite FROM Prestations WHERE id = ?;""" curseur.execute(requete, (idPrestation, )) self.__prixUnitaire, self.__unite = curseur.fetchone() self.__prixUnitaireLabel.setText("Prix unitaire : " + str(self.__prixUnitaire).replace(".",",") + "€/" + self.__unite) self.__quantite.setSuffix(" " + self.__unite) self.calculerTotaux() def calculerTotaux(self): self.__prixHT = round(self.__prixUnitaire * self.__quantite.value(), 2) self.__prixTVA = round(self.__prixHT * self.__tva.value() / 100.0, 2) self.__prixTTC = round(self.__prixHT + self.__prixTVA, 2) self.__totauxLabel.setText("HT : " + str(self.__prixHT).replace(".",",") + " €\nTVA : " + str(self.__prixTVA).replace(".",",") + " €\nTTC : " + str(self.__prixTTC).replace(".",",") + " €") def ajouterLigneChiffrage(self): lignePrestation = {} index = self.__listePrestations.currentRow() lignePrestation["idPrestation"] = self.__prestations[index]["id"] lignePrestation["libelle"] = self.__prestations[index]["libelle"] lignePrestation["prixUnitaire"] = self.__prixUnitaire lignePrestation["unite"] = self.__unite lignePrestation["quantite"] = self.__quantite.value() lignePrestation["prixHT"] = self.__prixHT lignePrestation["prixTVA"] = self.__prixTVA lignePrestation["tva"] = self.__tva.value() lignePrestation["prixTTC"] = self.__prixTTC self.__lignesChiffrage.append(lignePrestation) self.afficherChiffrage() def afficherChiffrage(self): totalHT = 0.0 totalTVA = 0.0 totalTTC = 0.0 self.__tableChiffrage.setRowCount(len(self.__lignesChiffrage)) for noLigne, lignePrestation in enumerate(self.__lignesChiffrage): self.__tableChiffrage.setItem(noLigne, 0, QtWidgets.QTableWidgetItem(lignePrestation["libelle"])) self.__tableChiffrage.setItem(noLigne, 1, QtWidgets.QTableWidgetItem(str(lignePrestation["prixUnitaire"]) + "€/" + lignePrestation["unite"])) self.__tableChiffrage.setItem(noLigne, 2, QtWidgets.QTableWidgetItem(str(lignePrestation["quantite"]) + " " + lignePrestation["unite"])) self.__tableChiffrage.setItem(noLigne, 3, QtWidgets.QTableWidgetItem(str(lignePrestation["prixHT"]) + "€")) self.__tableChiffrage.setItem(noLigne, 4, QtWidgets.QTableWidgetItem(str(lignePrestation["prixTVA"]) + "€ (" + str(lignePrestation["tva"]) + "%)")) self.__tableChiffrage.setItem(noLigne, 5, QtWidgets.QTableWidgetItem(str(lignePrestation["prixTTC"]) + "€")) totalHT += lignePrestation["prixHT"] totalTVA += lignePrestation["prixTVA"] totalTTC += lignePrestation["prixTTC"] self.__totauxEstimationLabel.setText("HT : " + str(round(totalHT, 2)).replace(".",",") + " €\nTVA : " + str(round(totalTVA, 2)).replace(".",",") + " €\nTTC : " + str(round(totalTTC, 2)).replace(".",",") + " €") def monterLigne(self): index = self.__tableChiffrage.currentRow() if index > 0: self.__lignesChiffrage[index-1], self.__lignesChiffrage[index] = self.__lignesChiffrage[index], self.__lignesChiffrage[index-1] self.afficherChiffrage() def descendreLigne(self): index = self.__tableChiffrage.currentRow() if index < len(self.__lignesChiffrage)-1: self.__lignesChiffrage[index+1], self.__lignesChiffrage[index] = self.__lignesChiffrage[index], self.__lignesChiffrage[index+1] self.afficherChiffrage() def supprimerLigne(self): index = self.__tableChiffrage.currentRow() self.__lignesChiffrage.pop(index) self.afficherChiffrage() def nouvellePrestation(self): dialog = EditionPrestation() dialog.exec_() self.peuplerCategories() self.peuplerPrestations() self.ouvrirFiche() def modifierPrestation(self): index = self.__listePrestations.currentRow() idPrestation = self.__prestations[index]["id"] dialog = EditionPrestation(idPrestation=idPrestation) dialog.exec_() self.peuplerCategories() self.peuplerPrestations() self.ouvrirFiche() def supprimerPrestation(self): index = self.__listePrestations.currentRow() idPrestation = self.__prestations[index]["id"] requete = """DELETE FROM Prestations WHERE id = ?;""" curseur.execute(requete, (idPrestation, )) self.peuplerCategories() self.peuplerPrestations() def enregistrerChiffrage(self): nomClient = self.__nomClient.text() prenomClient = self.__prenomClient.text() adresse = self.__adresseClient.text() codePostal = self.__cpClient.text() ville = self.__villeClient.text() telephone = self.__telephoneClient.text() courriel = self.__courrielClient.text() nomChantier = self.__nomChantier.text() if self.__idChiffrage == None: requete = """INSERT INTO Estimations (nomClient, prenomClient, adresse, codePostal, ville, telephone, courriel, nomChantier) VALUES (?, ?, ?, ?, ?, ?, ?, ?);""" curseur.execute(requete, (nomClient, prenomClient, adresse, codePostal, ville, telephone, courriel, nomChantier)) baseDeDonnees.commit() self.__idChiffrage = curseur.lastrowid else: requete = """UPDATE Estimations SET nomClient = ?, prenomClient = ?, adresse = ?, codePostal = ?, ville = ?, telephone = ?, courriel = ?, nomChantier = ? WHERE id = ?;""" curseur.execute(requete, (nomClient, prenomClient, adresse, codePostal, ville, telephone, courriel, nomChantier, self.__idChiffrage)) baseDeDonnees.commit() requete = """DELETE FROM LignesEstimations WHERE idEstimation = ?;""" curseur.execute(requete, (self.__idChiffrage, )) for noLigne, lignePrestation in enumerate(self.__lignesChiffrage): requete = """INSERT INTO LignesEstimations (idEstimation, idPrestation, ordre, quantite, tauxTVA) VALUES (?, ?, ?, ?, ?);""" curseur.execute(requete, (self.__idChiffrage, lignePrestation["idPrestation"], noLigne, lignePrestation["quantite"], lignePrestation["tva"])) baseDeDonnees.commit() def exporterChiffrage(self): dialogFichierExport = QtWidgets.QFileDialog() adresseFichierExport = dialogFichierExport.getSaveFileName(caption="Exporter le chiffrage", filter="Fichier texte (*.txt)")[0] if adresseFichierExport[-4:] != ".txt": adresseFichierExport += ".txt" fichier = open(adresseFichierExport, "wt") fichier.write("Société Bati Plus\n52 rue de Clairecombe\n74930 Moulincourbe\n") totalHT = 0.0 totalTVA = 0.0 totalTTC = 0.0 nomClient = self.__nomClient.text() prenomClient = self.__prenomClient.text() adresse = self.__adresseClient.text() codePostal = self.__cpClient.text() ville = self.__villeClient.text() telephone = self.__telephoneClient.text() courriel = self.__courrielClient.text() nomChantier = self.__nomChantier.text() lignesDestinataire = [prenomClient + " " + nomClient, adresse, codePostal + " " + ville, telephone, courriel] for ligne in lignesDestinataire: fichier.write("\t\t\t\t\t\t\t" + ligne + "\n") fichier.write("Estimation ") if self.__idChiffrage != None: fichier.write("numéro " + str(self.__idChiffrage) + " ") fichier.write("réalisée le " + time.strftime("%d %B %Y") + "\n") fichier.write(nomChantier + "\n") fichier.write("Prestation\tPrix unitaire\tQuantité\tTotal HT\tTVA\tTotal TTC\n") for lignePrestation in self.__lignesChiffrage: fichier.write(lignePrestation["libelle"] + "\t") fichier.write(str(lignePrestation["prixUnitaire"]) + "€/" + lignePrestation["unite"] + "\t") fichier.write(str(lignePrestation["quantite"]) + " " + lignePrestation["unite"] + "\t") fichier.write(str(lignePrestation["prixHT"]) + "€\t") fichier.write(str(lignePrestation["prixTVA"]) + "€ (" + str(lignePrestation["tva"]) + "%)\t") fichier.write(str(lignePrestation["prixTTC"]) + "€\n") totalHT += lignePrestation["prixHT"] totalTVA += lignePrestation["prixTVA"] totalTTC += lignePrestation["prixTTC"] fichier.write("Total HT : " + str(round(totalHT, 2)).replace(".",",") + " €\nTotal TVA : " + str(round(totalTVA, 2)).replace(".",",") + " €\nTotal TTC : " + str(round(totalTTC, 2)).replace(".",",") + " €") fichier.close() def ouvrirPrestation(self): dialog = OuvrirPrestation() dialog.exec_() donneesChiffrage = dialog.getDonneesRetournees() self.__idChiffrage = donneesChiffrage["idChiffrage"] self.__lignesChiffrage = donneesChiffrage["lignesChiffrage"] self.__nomClient.setText(donneesChiffrage["donneesClient"]["nomClient"]) self.__prenomClient.setText(donneesChiffrage["donneesClient"]["prenomClient"]) self.__adresseClient.setText(donneesChiffrage["donneesClient"]["adresse"]) self.__cpClient.setText(donneesChiffrage["donneesClient"]["codePostal"]) self.__villeClient.setText(donneesChiffrage["donneesClient"]["ville"]) self.__telephoneClient.setText(donneesChiffrage["donneesClient"]["telephone"]) self.__courrielClient.setText(donneesChiffrage["donneesClient"]["courriel"]) self.__nomChantier.setText(donneesChiffrage["donneesClient"]["nomChantier"]) self.afficherChiffrage() app = QtWidgets.QApplication(sys.argv) fenetre = Tarification() app.exec_() baseDeDonnees.close()