Vamos ver como desenvolver um objeto gráfico que apresenta uma lista de objectos que pode ser deslocada e selecionada. Pode utilizar este objeto para criar interfaces gráficas modulares.
Criar um objeto QScrollArea
Para criar uma lista de objectos com deslocação, vamos utilizar o objeto QScrollArea que, como o nome sugere, cria uma área com barras de deslocação.
Assim, criamos um objeto que herda de QScrollArea e inclui uma lista de caixas de verificação (QCheckBox). Utilizamos uma disposição vertical para organizar a lista de objectos.
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])
Nota: Neste exemplo, utilizamos os objectos QCheckBox, mas pode utilizar qualquer QWidget.
Para recuperar o estado das CheckBoxes, adicionamos uma função changeChk que ligamos ao sinal stateChanged de cada CheckBox
Podemos então adicionar este código diretamente a uma aplicação Qt. Aqui está o código para testar seu objeto 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()

Exemplo de utilização da lista de objectos com deslocação
Como exemplo, vamos pegar no projeto Display a signal with PyQtGraph, no qual vamos integrar o objeto ListContainer. A lista de objectos QCheckBox permitir-nos-á selecionar os sinais a apresentar no gráfico.
No objeto SignalContainer, criamos diferentes sinais de tempo e estilo de forma aleatória utilizando o pacote 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
Em seguida, criamos uma função setSignal que recebe a lista de estados como argumento e actualiza o gráfico de acordo com os sinais a apresentar.
@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])
Ligamos a função setSignal ao sinal changeItem no objeto ListContainer para que a função seja chamada sempre que os estados da CHeckBox sejam actualizados.
self.select.changeItem.connect(self.setSignal)
Aqui está o código completo para uma aplicação PyQt com uma lista rolante de objetos usados para selecionar os sinais a serem exibidos no PyQTGraph
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
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())
