Nous allons voir comment développer un objet graphique permettant d’afficher une liste d’objets défilante et sélectionnable. Grâce à cet objet vous pourrez créer des interfaces graphiques modulables.
Création d’un objet QScrollArea
Pour créer une liste d’objets défilante, nous allons utiliser l’objet QScrollArea qui permet comme son nom l’indique de créer une zone avec des barres de défilement.
Nous créons donc un objet qui hérite de QScrollArea et qui intègre une liste de boîtes à cocher (QCheckBox). Nous utilisons un layout vertical pour agencer la liste d’objet.
class ListContainer(QScrollArea):
changeItem=pyqtSignal(list)
def __init__(self,items=None, parent=None):
super(ListContainer, self).__init__(parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
self.listItem=items
if self.listItem==None:
self.listItem=[0]*20
self.listState=[False]*len(self.listItem)
self.itemChk=[]
self.initUI()
def initUI(self):
container=QWidget()
self.setWidget(container)
layout = QVBoxLayout(container)
for i,s in enumerate(self.listItem):
self.itemChk.append(QCheckBox("Objet"+str(i)))
self.itemChk[i].setChecked(False)
layout.addWidget(self.itemChk[i])
N.B.: Nous utilisons dans cet exemple des objets QCheckBox mais vous pouvez utiliser n’importe quel QWidget.
Pour récupérer l’état des CheckBox, nous ajoutons une fonction changeChk que nous connectons au signal stateChanged de chaque CheckBox
Nous pouvons ensuite ajouter ce code directement dans une application Qt. Voici le code pour tester votre objet ListContainer
#from PyQt6.QtWidgets import (QWidget, QSlider, QLineEdit, QLabel, QPushButton, QScrollArea,QApplication,
# QHBoxLayout, QVBoxLayout, QMainWindow)
#from PyQt6.QtCore import Qt, QSize
#from PyQt6 import QtWidgets, uic
from PySide6.QtWidgets import (QWidget, QSlider, QLineEdit, QLabel, QPushButton, QScrollArea,QApplication,
QHBoxLayout, QVBoxLayout, QMainWindow, QFrame, QCheckBox)
from PySide6.QtCore import Qt, QSize, Signal, Slot
pyqtSignal = Signal
pyqtSlot = Slot
import sys
class ListContainer(QScrollArea):
changeItem=pyqtSignal(list)
def __init__(self,items=None, parent=None):
super(ListContainer, self).__init__(parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
self.listItem=items
if self.listItem==None:
self.listItem=[0]*20
self.listState=[False]*len(self.listItem)
self.itemChk=[]
self.initUI()
def initUI(self):
container=QWidget()
self.setWidget(container)
layout = QVBoxLayout(container)
for i,s in enumerate(self.listItem):
self.itemChk.append(QCheckBox("Objet"+str(i)))
self.itemChk[i].setChecked(False)
self.itemChk[i].stateChanged.connect(self.changeChk)
layout.addWidget(self.itemChk[i])
def changeChk(self,state):
print("{} : {}".format(self.sender().text(),True if state>0 else False))
for i,s in enumerate(self.itemChk):
if s.text() == self.sender().text():
self.listState[i]=True if state>0 else False
self.changeItem.emit(self.listState)
def main():
app = QApplication(sys.argv)
main = ListContainer()
main.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

Exemple d’utilisation de la liste d’objets défilante
A titre d’exemple, nous allons reprendre le projet Afficher un signal avec PyQtGraph, dans lequel nous allons intégrer l’objet ListContainer. La liste d’objets QCheckBox va nous permettre de sélectionner les signaux à afficher sur le graphique.
Dans l’objet SignalContainer, nous créons différents signaux temporels et style aléatoirement à l’aide du paquet numpy.random.
self.time = np.linspace(0,1,1000)*10 for i,d in enumerate(self.data): x = np.random.choice([-1, 1]) #random sign s = np.random.rand()*0.2 #random scale fun=np.random.choice([np.sin, np.cos]) #random function self.data[i]=x*fun(self.time) + np.random.normal(scale=s, size=len(self.time)) r=np.random.randint(255) #random color g=np.random.randint(255) b=np.random.randint(255) style = np.random.choice([Qt.DashLine, Qt.SolidLine, Qt.DotLine, Qt.DashDotLine]) symbol = np.random.choice(['','+','o','x']) self.pen.append(mkPen(color=(r, g, b), width=3, style=Qt.DashLine)) #line style
Nous créons ensuite une fonction setSignal qui prend en argument la liste des états et met à jour le graphique en fonction des signaux à afficher.
@pyqtSlot(list) def setSignal(self,states): print(states) self.sigstate=states #update graph self.graphWidget.clear() #self.graphWidget.plot(self.time, self.data, name = "signal",pen=self.pen,symbol='+', symbolSize=5, symbolBrush='w') for i,data in enumerate(self.data): if self.sigstate[i]: #display signal self.graphWidget.plot(self.time, self.data[i],name = "signal"+str(i),pen=self.pen[i])
Nous connectons la fonction setSignal au signal changeItem de l’objet ListContainer afin que la fonction soit appelée à chaque mise à jour des états des CHeckBoxs.
self.select.changeItem.connect(self.setSignal)
Voici le code complet d’une application PyQt avec une liste d’objets défilante permettant de sélectionner les signaux à afficher sur PyQTGraph
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
#from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QApplication
#from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
#from PyQt5.QtGui import QImage, QPixmap
from pyqtgraph import PlotWidget, mkPen
from PySide6.QtWidgets import QMainWindow, QWidget, QLabel, QApplication, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QCheckBox, QScrollArea, QSplitter
from PySide6.QtCore import QThread, Qt, Signal, Slot
from PySide6.QtGui import QImage, QPixmap
pyqtSignal = Signal #convert pyqt to pyside
pyqtSlot = Slot
import numpy as np
import time
class ListContainer(QScrollArea):
changeItem=pyqtSignal(list)
def __init__(self,items=None, parent=None):
super(ListContainer, self).__init__(parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
self.listItem=items
self.listState=[False]*len(self.listItem)
if self.listItem==None:
self.listItem=[0]*50
self.itemChk=[]
self.initUI()
def initUI(self):
container=QWidget()
self.setWidget(container)
layout = QVBoxLayout(container)
for i,s in enumerate(self.listItem):
self.itemChk.append(QCheckBox("Signal"+str(i)))
self.itemChk[i].setChecked(False)
self.itemChk[i].stateChanged.connect(self.changeChk)
layout.addWidget(self.itemChk[i])
def changeChk(self,state):
print("{} : {}".format(self.sender().text(),True if state>0 else False))
for i,s in enumerate(self.itemChk):
if s.text() == self.sender().text():
self.listState[i]=True if state>0 else False
self.changeItem.emit(self.listState)
class SignalContainer(QWidget):
changeParam = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.title = 'Signal'
self.span=10
self.time = [0]*1000
self.data = [[0]*1000]*20#[[0]*1000,[0]*1000,[0]*1000]
self.pen=[]
self.time = np.linspace(0,1, 1000)*10
for i,d in enumerate(self.data):
x = np.random.choice([-1, 1]) #random sign
s = np.random.rand()*0.2 #random scale
fun=np.random.choice([np.sin, np.cos]) #random function
self.data[i]=x*fun(self.time) + np.random.normal(scale=s, size=len(self.time))
r=np.random.randint(255) #random color
g=np.random.randint(255)
b=np.random.randint(255)
style = np.random.choice([Qt.DashLine, Qt.SolidLine, Qt.DotLine, Qt.DashDotLine])
symbol = np.random.choice(['','+','o','x'])
self.pen.append(mkPen(color=(r, g, b), width=3, style=Qt.DashLine)) #line style
self.sigstate=[False,False,False]
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.resize(800, 400)
self.mainLayout = QHBoxLayout()
self.setLayout(self.mainLayout)
self.splitter=QSplitter()
self.mainLayout.addWidget(self.splitter)
self.select=ListContainer(["signal{}".format(i) for i in range(len(self.data))])
self.select.changeItem.connect(self.setSignal)
self.splitter.addWidget(self.select)
self.signalLayout=QVBoxLayout()
# create widget
self.graphWidget = PlotWidget()
self.signalLayout.addWidget(self.graphWidget)
#self.mainLayout.addLayout(self.signalLayout)
self.splitter.addWidget(self.graphWidget)
#tune plots
self.graphWidget.setBackground((50,50,50,220)) # RGBA #background
self.graphWidget.setTitle("Signal(t)", color="w", size="20pt") #add title
styles = {'color':'r', 'font-size':'20px'} #add label style
self.graphWidget.setLabel('left', 'signal [SI]', **styles) #add ylabel
self.graphWidget.setLabel('bottom', 'time [s]', **styles) #add xlabel
self.graphWidget.showGrid(x=True, y=True) #add grid
self.graphWidget.addLegend() #add grid
self.graphWidget.setXRange(0, self.span, padding=0)
self.graphWidget.setYRange(-2, 2, padding=0.1)
#plot data
#self.graphWidget.plot(self.time, self.data[0],name = "signal",pen=self.pen,symbol='+', symbolSize=5, symbolBrush='w')
@pyqtSlot(list)
def setSignal(self,states):
print(states)
self.sigstate=states
#update graph
self.graphWidget.clear()
#self.graphWidget.plot(self.time, self.data,name = "signal",pen=self.pen,symbol='+', symbolSize=5, symbolBrush='w')
for i,data in enumerate(self.data):
if self.sigstate[i]: #display signal
self.graphWidget.plot(self.time, self.data[i],name = "signal"+str(i),pen=self.pen[i])
import signal #close signal with Ctrl+C
signal.signal(signal.SIGINT, signal.SIG_DFL)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SignalContainer()
ex.show()
sys.exit(app.exec())

Bonjour, je suis nouveau avec la programmation avec Python.
J’ai copié le code pour mieu comprendre votre travail (il me semble très intéresant), mais je ne suis pas capable de l’exécuter correctement.
Voici le message dans la console :
File « C:\Users\Utilisateur\App3\Appv\PyQt6wII\aranacorp\Créer une liste d’objets défilante avec PyQt.py », line 140, in
ex = SignalContainer()
File « C:\Users\Utilisateur\App3\Appv\PyQt6wII\aranacorp\Créer une liste d’objets défilante avec PyQt.py », line 83, in __init__
self.pen.append(mkPen(color=(r, g, b), width=3, style=Qt.DashLine)) # line style
File « C:\Users\Utilisateur\App3\Appv\lib\site-packages\pyqtgraph\functions.py », line 366, in mkPen
pen.setStyle(style)
TypeError: setStyle(self, a0: Qt.PenStyle): argument 1 has unexpected type ‘PenStyle’.
S.V.P. pourriez-vous me donner votre commentaire?.
Merci à l’avance.
Juan Cortez
Bonjour,
avez-vous installé pyqtgraph?
Bonjour Xukyo,
Merci de ton commentaire.
J’ai déjà installé pyqtgraph (version 0.13.4).
La version de python est : 3.10.12
Je ne sais pas que je dois vérifier.
Est-ce que tu as un autre code similaire pour le comparer? ou si tu peux me recommander un site web qui pourrait m’aider à comprendre le code (ci-dessus)?.
Merci à l’avance.
Salutations.
Juan.
Tu peux essayer le code dans https://www.aranacorp.com/fr/afficher-un-signal-dans-pyqt-avec-pyqtgraph/
Je viens de faire un installation propre de python
pip install pyside6
pip install pyqtgraph
sans rencontrer de problème. Je suspecte un conflit d’installation
tu peux essayer d’enlever le paramètre qui pose soucis (style) self.pen.append(mkPen(color=(r, g, b), width=3))
Sinon pour comprendre le code le mieux est de se plonger dans la documentation de Qt
Bonjour Xukyo,
Merci pour ton message.
J’ai effacé PySide6 (version 6.6.2) et pyqtgraph (version 0.13.4).
Après, j’ai installé les nouvelles versions.
PySide6 (version 6.7.0) et pyqtgraph (version 0.13.7).
Maintenant, le message de la consola a changé:
File « C:\Users\Utilisateur\App3\Appv\PyQt6wII\aranacorp\Créer une liste d’objets défilante avec PyQt.py », line 13, in
from PySide6.QtWidgets import QMainWindow, QWidget, QLabel, QApplication, QVBoxLayout, QHBoxLayout, QLineEdit, \
ImportError: DLL load failed while importing QtWidgets: La procédure spécifiée est introuvable.
J’ai le même message pour le code numéro 2.
Je ne sais pas si le problème est Pycharm 2022.3.2 (Community Edition) avec Windows.
Par curiosité, quel logiciel tu utilises pour exécuter ton code?.
Merci à l’avance.
Bonjour,
DLL load failed montre un problème d’installation ou de configuration.
J’utilise Geany ou VSCode
Bonjour,
Merci Xukyo pour ton commentaire, c’est bien apprécié !