Site icon AranaCorp

Criando uma lista rolante de objetos com PyQt

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())

Fontes

Exit mobile version