自定义的部件库原则上尽量做到前后端分离,接口方便,复制简单。
单选框部件
# encoding: utf-8
###################################################
# 自定义的单选框
#################################################### 对外接口:
# stateNum = Signal(int) 状态号,从1开始,是几就几被选中
# customSetup(): # 定制的初始化程序import PySide6
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QWidget, QGroupBox, QFrameclass PysideCustomRadiobutton(QFrame):stateNum = Signal(int) # 状态号,从1开始,是几就几被选中def __init__(self, parent=None):super().__init__(parent)self.RadioButtons = []def customSetupUi(self): # 定制的初始化程序self.RadioButtons = self.findChildren(PySide6.QtWidgets.QRadioButton) # 部件中所有的单选按钮self.RadioButtons.sort(key=lambda child: child.objectName()) # 按名称排序for i in range(len(self.RadioButtons)):def callback(idx):return lambda: self.stateNum.emit(idx+1)self.RadioButtons[i].clicked.connect(callback(i)) # 单击按钮后发射信号self.stateNum.connect(self.changeState) # 状态号信号的连接self.stateNum.emit(1) # 初始化状态下,状态1被选中def changeState(self, thisTurnOn): # 改变状态self.RadioButtons[thisTurnOn-1].setChecked(True)
前端的范例:
框架的建立和调用
# 阶段v1的调用
import sys
from time import strftimefrom PySide6.QtCore import QObject, Signal, QTimer
from PySide6.QtWidgets import QWidget, QApplication, QMainWindow
import template_rc # 导入需要显示的画面# ############导入并需要显示的画面集中在这里定义#############################
# ############template.ui的显示#############################
class MainWindow(QMainWindow, template_rc.Ui_MainWindow): # 定义需要显示的画面类def __int__(self):super().__init__()# ########################部件初始化的通用函数###################################
def init_widgets(widget): # 画面里的部件的初始化sons = widget.children() # 儿辈部件def find_child(child): # 查找子部件try:child.customSetupUi() # 自定义的初始化except AttributeError:passif child.children():for grandson in child.children():find_child(grandson) # 递归查找for son in sons:find_child(son)# #############################主程序###################################
if __name__ == '__main__':app = QApplication(sys.argv)# #######################本项目的定义###################################class UI(QObject):# ##########项目范围内的信号#############sysClock_1S = Signal() # 秒时钟信号# ##########定时器#############timer_1s = QTimer() # 定义全局0.1s的周期定时器timer_1s.start(1000)# ###########__init__###############def __init__(self):super().__init__()self.widgets = [] # 所有的视窗列表# ########################本项目的实例化###################################ui = UI()# #######################画面实例化和初始化################################## ########################实例化模板画面#################################mainWindow = MainWindow()mainWindow.setupUi(mainWindow) # 模板画面的本体初始化init_widgets(mainWindow) # 模板画面的部件初始化mainWindow.show()ui.widgets.append(mainWindow) # 将画面加入视窗列表,方便管理# ###########################信号的连接##################################### ###########################项目级别信号的连接###############################ui.timer_1s.timeout.connect(ui.sysClock_1S.emit)ui.sysClock_1S.connect(lambda: mainWindow.labelSystemClock.setText(strftime('%Y-%m-%d %H:%M:%S')))# ###########################mainWindow画面信号的连接######################mainWindow.RadioButtons_1.stateNum.connect(lambda x: mainWindow.label_1.setText(str(x)))mainWindow.pushButton_1.clicked.connect(lambda: mainWindow.RadioButtons_1.stateNum.emit(1))mainWindow.doubleSpinBox_1.valueChanged.connect(lambda x: mainWindow.label_22.setText(str(x)))# ###########################槽函数####################################sys.exit(app.exec())
运行截图:
本阶段的资源链接:(v1)https://download.csdn.net/download/xulibo5828/89120987?spm=1001.2101.3001.9499
自定义功能的仪表盘
一个具有设定值输入、微调,实时值显示,历史曲线实时显示的仪表盘。
在CAD软件中大致规划一下功能区的尺寸和排列:
功能和分工规划:编程阶段可视的部件在QT designer中完成部署和设置,运行阶段可视的动态部件在程序中根据QT designer中的设置值自动计算和部署。
对外接口:外部传入设定值和实时值。
画面设计:在QT Designer里面完成画面的组态,布置和复制部件也仅在QT Designer中进行。部件的总容器和历史数据显示窗口用QFrame,进度条和历史曲线的显示在程序里用QPainter实现。历史曲线尝试过用专门的绘图库matplotlib,这个库在嵌入pyside6的部件时无法做到0边距无缝衔接,左右两边无法布满pyside6的部件。如下图:
所以,还是决定用QPainter的 drawline来实现。
在QT Designer里设计好部件的各个组件:
为之配套编写代码,命名为PysideCustomDashboard.py,并将部件“提升为”这个自定义的类。
部件的代码:
# encoding: utf-8
###################################################
# 自定义的仪表盘
#################################################### 对外接口:
# ActualValueInput = Signal(float) # 从外部传入的实时值
# customSetup(): # 定制的初始化程序import PySide6
from PySide6.QtCore import Signal, Qt, QPoint
from PySide6.QtGui import QColor, QPen, QPainter
from PySide6.QtWidgets import QFrameclass PysideCustomDashboard(QFrame):ActualValueInput = Signal(float) # 从外部传入的实时值PresetValueInput = Signal(float) # 从外部传入的设定值PresetValueOutput = Signal(float) # 向外部传出的设定值def __init__(self, parent=None):super().__init__(parent)# self.preset_value = 0.0 # 预设值的valueself.processBar_preset_width = 0 # 预设值进度条的宽度self.processBar_preset_radius = 0 # 预设值进度条的半径self.processBar_actual_radius = 0 # 实时值进度条的半径self.processBar_y_start = 0 # 进度条的外轮廓距y方向边缘距离self.processBar_x_start = 0 # 进度条的外轮廓距x方向边缘距离self.center_point = QPoint(0, 0) # 部件的中心坐标self.actual_text_color = QColor() # 实时值窗口文字颜色self.spinBox_preset_text_color = QColor() # 预设值窗口文字颜色self.progressBar_backColor = QColor('#515d80') # 进度条背景色self.frame_history_Palette = None # 历史曲线显示窗口的调色板self.frame_history_styleSheet = None # 历史曲线显示窗口的样式表self.frame_history_geometry = None # 历史曲线显示窗口的几何特征self.label_name = None # 仪表盘名称文字框self.label_unit = None # 显示单位文字框self.label_actual = None # 实时值的显示文字框self.spinBox_preset = None # 带微调的设置值输入部件self.frame_history = None # 历史曲线显示窗口self.begin_degree = 225 # 进度条总的起始角度self.span_degree = -225 # 进度条总的跨度角度self.actual_begin_degree = 225 # 实时值的进度条起始角度self.actual_span_degree = 0 # 实时值的进度条跨度始角度self.preset_begin_degree = 225 # 设定值的进度条起始角度self.preset_span_degree = 0 # 设定值的进度条跨度始角度self.min_value = 0.0 # 最小值self.max_value = 100.0 # 最大值self.processBar_actual_width = 0 # 进度条的宽度self.processBar_center = QPoint(0, 0) # 进度条的中心self.points = 10 # 显示的点数# #######################定制的初始化程序###########################def customSetupUi(self): # 定制的初始化程序# ######################子部件的定义############################children = self.children()for child in children:# ##############历史曲线显示窗口#####################if 'frame_history' in child.objectName():self.frame_history_geometry = child.geometry() # 几何数据self.frame_history_styleSheet = child.styleSheet() # 样式表self.frame_history_Palette = child.palette() # 颜色表# #################带微调的设置值输入部件################elif 'spinBox_preset' in child.objectName():self.spinBox_preset = childp = child.palette() # 颜色表self.spinBox_preset_text_color = p.color(p.ColorRole.Text) # 获取预设值窗口文字颜色self.min_value = child.minimum() # 最小值self.max_value = child.maximum() # 最大值# ###################实时值的显示文字框###################elif 'label_actual' in child.objectName():self.label_actual = childp = child.palette() # 颜色表self.actual_text_color = p.color(p.ColorRole.Text) # 获取实时值窗口文字颜色# #################显示单位文字#######################elif 'label_unit' in child.objectName():self.label_unit = child# ##############仪表盘名称文字####################elif 'label_name' in child.objectName(): #self.label_name = child# ##################本体几何参数########################self.center_point = QPoint(int(self.width() / 2), int((self.height() / 2))) # 中心点坐标# ####################历史曲线显示窗口的定义###################self.frame_history = CustomLineChart(self.spinBox_preset_text_color, self.actual_text_color, self.min_value,self.max_value, self.points, self) # 实体化历史曲线显示窗口self.frame_history.setGeometry(self.frame_history_geometry) # 克隆几何尺寸self.frame_history.setStyleSheet(self.frame_history_styleSheet) # 克隆样式表self.frame_history.setPalette(self.frame_history_Palette) # 克隆颜色表self.frame_history.show() # 显示# self.frame_history.customSetupUi() # 历史曲线显示窗口初始化# ####################进度条的定义##############################self.processBar_actual_width = int((self.width() / 50)) # 实时值进度条的宽度# self.processBar_preset_width = self.processBar_actual_width # 预设值进度条的宽度self.processBar_preset_width = int(self.processBar_actual_width * 0.66) # 预设值进度条的宽度# self.processBar_preset_width = int(self.processBar_actual_width / 3)self.processBar_x_start = int(self.width() / 20) # 进度条的外轮廓距x方向边缘距离self.processBar_y_start = int(self.width() / 20) # 进度条的外轮廓距y方向边缘距离self.processBar_actual_radius = int((self.width() / 2) - self.processBar_x_start - (self.processBar_actual_width / 2) * 0.9) # 实时值进度条的半径self.processBar_preset_radius = int(self.processBar_actual_radius - self.processBar_actual_width) # 预设值进度条的半径self.signal_handing([self.spinBox_preset.value(), 'preset']) # 初始化一下预设值进度条的显示# ######################信号的连接############################self.ActualValueInput.connect(lambda x: self.signal_handing([x, 'actual'])) # 接收到外部传入实时数据后的信号处理self.PresetValueInput.connect(self.spinBox_preset.setValue) # 接收到外部传入设置值后的信号处理self.spinBox_preset.valueChanged.connect(lambda x: self.signal_handing([x, 'preset'])) # 设置值发生改变后的信号处理# #######################发射信号的处理############################def signal_handing(self, args):if args[1] == 'actual': # 如果是实时值发射的信号self.frame_history.PointValues.emit(self.spinBox_preset.value(), args[0]) # 将预设值和实际值打包发送到历史曲线的部件的输入信号self.label_actual.setText(str(args[0])) # 实时显示实际值的数字self.actual_span_degree = int((args[0] - self.min_value) / (self.max_value - self.min_value) * self.span_degree) # 计算实时值进度条的角度self.update() # 刷新画面else: # 如果是设定值发射的信号self.frame_history.PointValues.emit(self.spinBox_preset.value(),float(self.label_actual.text())) # 将预设值和实际值打包发送到历史曲线的部件的输入信号self.preset_span_degree = int((args[0] - self.min_value) / (self.max_value - self.min_value) * self.span_degree) # 计算设定值进度条的角度self.update() # 刷新画面# ########################绘制进度条圆弧###########################def draw_arc(self, color, center, startAngle, spanAngle, width, radius):painter = QPainter(self) # 设定画板painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿painter.setPen(QPen(color, width, Qt.SolidLine)) # 设置画笔颜色、粗细和线型painter.drawArc(center[0] - radius, center[1] - radius, radius * 2, radius * 2, startAngle * 16, spanAngle * 16)# #########################重写paintEvent########################def paintEvent(self, event):# ##################绘制实时值背景进度条###############################self.draw_arc(self.progressBar_backColor, (self.center_point.x(), self.center_point.y()), self.begin_degree,self.span_degree, self.processBar_actual_width, self.processBar_actual_radius)# ##################绘制设置值背景进度条###############################self.draw_arc(self.progressBar_backColor, (self.center_point.x(), self.center_point.y()), self.begin_degree,self.span_degree, self.processBar_preset_width, self.processBar_preset_radius)# ##################绘制实时值的显示进度条###############################self.draw_arc(self.actual_text_color, (self.center_point.x(), self.center_point.y()), self.actual_begin_degree,self.actual_span_degree, self.processBar_actual_width, self.processBar_actual_radius)# ##################绘制设置值的显示进度条###############################self.draw_arc(self.spinBox_preset_text_color, (self.center_point.x(), self.center_point.y()),self.preset_begin_degree,self.preset_span_degree, self.processBar_preset_width, self.processBar_preset_radius)# #########################历史曲线显示窗口的class定义########################
class CustomLineChart(QFrame): # 自定义的折线表PointValues = Signal(float, float) # 点值,设置值在前,实时值在后def __init__(self, preset_color, actual_color, min_value, max_value, points, parent=None):super().__init__(parent)self.points = points # 显示的点数self.preset_value_points = [0.0] # 设置值的数据点集self.actual_value_points = [0.0] # 实时值的数据点集self.overflow = False # 数据超量程self.background_color = QColor() # 背景色self.preset_color = preset_color # 预置值颜色self.actual_color = actual_color # 实时值颜色self.preset_pen = QPen() # 预设值波形的画笔self.actual_pen = QPen() # 实时值波形的画笔self.min_value = min_value # 最小值self.max_value = max_value # 最大值self.x_points = [] # X轴的分度self.x_step = 0 # X轴的步调节拍def customSetupUi(self): # 定制的初始化程序# #####################建立x轴的分度表############################x = 0s = self.width() / (self.points - 1)for i in range(self.points):self.x_points.append(int(x))x += s# #####################初始化颜色特征############################p = self.palette() # 获取调色板self.background_color = p.color(p.ColorRole.Window) # 获取背景色self.preset_pen = QPen(self.preset_color, 1) # 预置值的笔self.actual_pen = QPen(self.actual_color, 1) # 实时值的笔# #####################信号的连接############################self.PointValues.connect(self.pretreat) # 当传入值以后预处理数据self.PointValues.connect(self.update) # 当传入值以后更新画面def pretreat(self, preset_value, actual_value): # 数据的预处理# #####################预设值数据的预处理############################k = (preset_value - self.min_value) / (self.max_value - self.min_value)v = int(self.height() * (1 - k))self.preset_value_points.append(v)if len(self.preset_value_points) > self.points:del self.preset_value_points[0]# #####################实时值数据的预处理############################if actual_value < self.min_value: # 输入值超出量程actual_value = self.min_valueself.overflow = Trueelif actual_value > self.max_value:actual_value = self.max_valueself.overflow = Trueelse:self.overflow = Falsek = (actual_value - self.min_value) / (self.max_value - self.min_value) # 显示比例转换v = int(self.height() * (1 - k))self.actual_value_points.append(v)if len(self.actual_value_points) > self.points:del self.actual_value_points[0]# ############################绘图事件####################################def paintEvent(self, event): # 重新定义paintEventpainter = QPainter(self) # 设定画板painter.setPen(Qt.NoPen) # 设定画笔# #########################重画背景###################################if self.overflow:color = QColor('#363636')else:color = self.background_colorpainter.setBrush(color) # 设置笔刷painter.drawRect(0, 0, self.width(), self.height()) # 重画背景# #########################绘制预设值的折线#############################pen = QPen(self.preset_color)pen.setWidth(1)painter.setPen(pen)if len(self.preset_value_points) > 1:i = 1while i < len(self.preset_value_points):painter.drawLine(self.x_points[i - 1], int(self.preset_value_points[i - 1]), self.x_points[i],int(self.preset_value_points[i]))i += 1# #########################绘制实时值的折线#############################pen = QPen(self.actual_color)pen.setWidth(1)painter.setPen(pen)if len(self.actual_value_points) > 1:i = 1while i < len(self.actual_value_points): painter.drawLine(self.x_points[i - 1], int(self.actual_value_points[i - 1]),self.x_points[i], int(self.actual_value_points[i]))i += 1
编写调用程序:基本的调用框架.py
# 阶段v1的调用
import random
import sys
from time import strftimefrom PySide6.QtCore import QObject, Signal, QTimer
from PySide6.QtWidgets import QApplication, QMainWindowimport template_rc # 导入需要显示的画面
import 按钮图标_rc# ############导入并需要显示的画面集中在这里定义#############################
# ############template.ui的显示#############################
class MainWindow(QMainWindow, template_rc.Ui_MainWindow): # 定义需要显示的画面类def __int__(self):super().__init__()# ########################部件初始化的通用函数###################################
def init_widgets(widget): # 画面里的部件的初始化sons = widget.children() # 儿辈部件def find_child(child): # 查找子部件try:child.customSetupUi() # 自定义的初始化except AttributeError:passif child.children():for grandson in child.children():find_child(grandson) # 递归查找for son in sons:find_child(son)def test(): # 测试程序n1 = random.randint(-300, 300)window1.dashboard_1.ActualValueInput.emit(float(n1 / 100.0 + 80))# #############################主程序###################################
if __name__ == '__main__':app = QApplication(sys.argv)# #######################项目级别的定义###################################class UI(QObject): # 将项目定义为QObject,用来管理项目级别的信号和变量# ##########项目范围内的信号#############sysClock_1S = Signal() # 秒时钟信号# ##########定时器#############timer_1s = QTimer() # 定义全局0.1s的周期定时器timer_1s.start(300)# ###########__init__###############def __init__(self):super().__init__()self.widgets = [] # 所有的视窗列表# ########################本项目的实例化###################################ui = UI()# #######################画面实例化和初始化################################## ########################实例化模板画面#################################window1 = MainWindow()window1.setupUi(window1) # 模板画面的本体初始化window1.show() # 显示画面ui.widgets.append(window1) # 将画面加入视窗列表,方便管理# ###########################信号的连接##################################### # ###########################项目级别信号的连接###############################ui.timer_1s.timeout.connect(ui.sysClock_1S.emit)ui.timer_1s.timeout.connect(test)ui.sysClock_1S.connect(lambda: window1.labelSystemClock.setText(strftime('%Y-%m-%d %H:%M:%S')))# ###########################Window1画面信号的连接####################### ###########################槽函数####################################init_widgets(window1) # 画面的子部件初始化sys.exit(app.exec())
运行截图:
进一步的,在组态画面里按ctrl拖动复制一份 ,并改变一些几何和颜色特征,增加一个外部设定部件:
编写调用程序:仪表盘调用demo.py
# 阶段v1的调用
import random
import sys
from time import strftime
from PySide6.QtCore import QObject, Signal, QTimer
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QDoubleSpinBoximport template_rc # 导入需要显示的画面
import 按钮图标_rc# ############导入并需要显示的画面集中在这里定义#############################
# ############template.ui的显示#############################
class MainWindow(QMainWindow, template_rc.Ui_MainWindow): # 定义需要显示的画面类def __int__(self):super().__init__()# ########################部件初始化的通用函数###################################
def init_widgets(widget): # 画面里的部件的初始化sons = widget.children() # 儿辈部件def find_child(child): # 查找子部件try:child.customSetupUi() # 自定义的初始化except AttributeError:passif child.children():for grandson in child.children():find_child(grandson) # 递归查找for son in sons:find_child(son)def test(): # 测试程序n1 = random.randint(-300, 300)n2 = random.randint(-300, 300)window1.dashboard_1.ActualValueInput.emit(float(n1 / 100.0 + 80))window1.dashboard_2.ActualValueInput.emit(float(n2 / 100.0 + 150))# #############################主程序###################################
if __name__ == '__main__':app = QApplication(sys.argv)# #######################项目级别的定义###################################class UI(QObject): # 将项目定义为QObject,用来管理项目级别的信号和变量# ##########项目范围内的信号#############sysClock_1S = Signal() # 秒时钟信号# ##########定时器#############timer_1s = QTimer() # 定义全局0.1s的周期定时器timer_1s.start(1000)# ###########__init__###############def __init__(self):super().__init__()self.widgets = [] # 所有的视窗列表# ########################本项目的实例化###################################ui = UI()# #######################画面实例化和初始化################################## ########################实例化模板画面#################################window1 = MainWindow()window1.setupUi(window1) # 模板画面的本体初始化window1.show() # 显示画面ui.widgets.append(window1) # 将画面加入视窗列表,方便管理# ###########################信号的连接##################################### # ###########################项目级别信号的连接###############################ui.timer_1s.timeout.connect(ui.sysClock_1S.emit)ui.timer_1s.timeout.connect(test)ui.sysClock_1S.connect(lambda: window1.labelSystemClock.setText(strftime('%Y-%m-%d %H:%M:%S')))# ###########################Window1画面信号的连接######################window1.doubleSpinBox.valueChanged.connect(window1.dashboard_1.PresetValueInput.emit)# ###########################槽函数####################################init_widgets(window1) # 画面的子部件初始化# QLabel# QDoubleSpinBoxsys.exit(app.exec())
运行截图:
基本实现了设计初衷 ,前后端图像和代码分离,接口和调用简单,复制部署方便。
资源链接:(本阶段为v2)https://download.csdn.net/download/xulibo5828/89148225?spm=1001.2101.3001.9499