对组合式部件的制作又改进了一版,组合式部件的子部件不再需要单独“提升为”,如果在模板文件的提升部件窗口内选择了“全局包含”,那么只需要在模板文件和应用文件中直接复制粘贴即可,部件的应用更为简便。如下图:按住ctrl,直接拖拽即可。
主程序
from sys import exit, argv
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5 import uicimport buttons # 按钮的内嵌图片资源# 显示界面的初始化
def form_init(form): # 显示界面的初始化 (backup)sons = form.findChildren(QWidget) # 儿辈部件def find_child(child): # 查找子部件try:child.init() # 初始化部件except AttributeError:passif not isinstance(child, QWidget):for grandson in child.findChilldren(QWidget):find_child(grandson) # 递归查找for son in sons:find_child(son)if __name__ == '__main__':app = QApplication(argv)# 读取 UI 文件并转换为 Python 代码ui_file = '../UIS/main.ui' # 更换为实际的ui文件地址form0 = uic.loadUi(ui_file) # 创建显示界面form_init(form0) # 初始化form0.dash_2.init(minValue=5000.0, maxValue=10000.0, step=100.0)form0.dash_1._extra1.setText('222') # dash_1的扩展显示1form0.dash_1._extra2.setText('999') # dash_1的扩展显示2form0.verticalSlider_1.valueChanged.connect(lambda x: form0.dash_1.actualValue_signal.emit(float(x)))form0.verticalSlider_2.valueChanged.connect(lambda x: form0.dash_2.actualValue_signal.emit(float(x)))form0.verticalSlider_3.valueChanged.connect(lambda x: form0.dash_1.presetValue_signal.emit(float(x)))form0.verticalSlider_4.valueChanged.connect(lambda x: form0.dash_2.presetValue_signal.emit(float(x)))form0.show()exit(app.exec_())
选择器
##########################
# 多选一的选择器 #
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QGroupBox, QLabel, QWidget, QDoubleSpinBox, QRadioButton
from PyQt5.Qt import pyqtSignalclass MySelectorBox(QGroupBox):def __init__(self, parent=None):super().__init__(parent)self.start_on = 0 # 初始为on的序号self.f_controls = [] # 所有的控制器(前端,用来产生动作)self.b_controls = [] # 所有的控制器(后台,用来显示状态)self.f_states = [] # 所有的状态指示器(前端)self.b_states = [] # 所有的状态指示器(后台)def init(self): # 需要在部件初始化的时候运行一次members = self.findChildren(QWidget) # 查找子部件for i, m in enumerate(members): # 查找子部件n = m.objectName().find('control') # 子部件的名称是‘control’if n != -1:self.b_controls.append(m)continuen = m.objectName().find('state') # 子部件的名称是‘state’if n != -1:t = f'self.state{m.objectName()[n + 5]}'exec(f'{t} =m') # 将指示器的变量定义映射到查找到的对应子部件,这里最多可以有10个指示器,如果超过10个,需要修改程序exec(f'self.b_states.append({t})')self.b_controls.sort(key=lambda child: child.objectName()) # 根据名称排序self.b_states.sort(key=lambda child: child.objectName()) # 根据名称排序for i, c in enumerate(self.b_controls):t = f'self.control{i}'exec(f'{t} =Controller(c)') # 将控制器的变量定义映射到查找到的对应子部件,这里最多可以有10个控制器,如果超过10个,需要修改程序exec(f'self.f_controls.append({t})')for i, s in enumerate(self.b_states):t = f'self.state{i}'exec(f'{t} =State_lamp(s)') # 将控制器的变量定义映射到查找到的对应子部件,这里最多可以有10个控制器,如果超过10个,需要修改程序exec(f'self.f_states.append({t})')self.connect() # 信号的连接def connect(self): # 信号的连接for i in range(len(self.f_controls)):def callback(idx):return lambda: self.toggle_states(idx)self.f_controls[i].Pressed.connect(callback(i))def toggle_states(self, n): # 切换指示器的显示状态for i in range(len(self.f_states)):if i == n:self.f_states[i].set_color(self.f_states[i].on_color) # 点亮对应的指示器else:self.f_states[i].set_color(self.f_states[i].off_color) # 熄灭对应的指示器class Controller(QLabel): # 自定义的QlabelPressed = pyqtSignal() # 键按下Enter = pyqtSignal() # 鼠标进入Leave = pyqtSignal() # 鼠标离开Release = pyqtSignal() # 鼠标松开Adjust = pyqtSignal(float) # 微调值的输出def __init__(self, label):super().__init__(label)self.label = labelself.style_first = Noneself.style_last = Noneself.style_normal = self.label.styleSheet().replace('\n', '')self.style_press = self.style_normal # 常态styleboard_begin = self.style_normal.find('border:')board_end = self.style_normal.find(' ', board_begin,)if board_begin == -1:self.style_enter = self.style_normal + 'border: 2px solid rgba(255, 255, 255, 0);'else:board = self.style_normal[board_begin:board_end]self.style_enter = self.style_normal.replace(board, 'border:1px')self.setFixedSize(self.label.width(), self.label.height()) # 获取部件的几何尺寸self.connect() # 信号的连接def enterEvent(self, event): # 重新定义鼠标悬停事件self.Enter.emit()def leaveEvent(self, event): # 重新定义鼠标离开事件self.Leave.emit()def mousePressEvent(self, event): # 重新定义鼠标按下事件self.Pressed.emit()def mouseReleaseEvent(self, event): # 重新定义鼠标抬起事件self.Release.emit()def connect(self):self.Enter.connect(self.enter)self.Leave.connect(self.leave)self.Pressed.connect(self.pressed)self.Release.connect(self.release)def enter(self):self.label.setStyleSheet(self.style_enter)def leave(self):self.label.setStyleSheet(self.style_normal)def pressed(self):self.style_last = self.label.styleSheet()self.label.setStyleSheet(self.style_press)def release(self):self.label.setStyleSheet(self.style_last)class State_lamp(QLabel): # 自定义的Qlabeldef __init__(self, label):super().__init__(label)self.label = labelself.rad = None# self.blink = False# self.style_normal = self.label.styleSheet().replace('\n', '')self.setFixedSize(self.label.width(), self.label.height()) # 获取部件的几何尺寸self.setStyleSheet(self.label.styleSheet().replace('\n', '') + 'background-color: rgba(255, 255, 255, 0);') # 前端部件背景透明self.style = ''self.off_color = ''self.on_color = ''self.border_color = ''self.init()def init(self,isRound=True,off_color='#767676',on_color='#039806',border_color='#868686'): # 在部件初始化的时候运行一次if isRound:self.rad = str(round(self.width() / 2))else:self.rad = 2self.off_color = off_color # 默认off颜色self.on_color = on_color # 默认on颜色self.border_color = border_color # 默认的边框颜色def set_color(self, color): # 设置自定义的颜色self.style = f'border-radius:{self.rad}px;background-color:{color};border:1px solid {self.border_color}; 'self.label.setStyleSheet(self.style)def color_on_bool(self, bool_in): # 由输入的bool来控制颜色的显示,默认为on:绿色,off:灰色if bool_in:self.style = f'border-radius:{self.rad}px;background-color:{self.on_color}; border:1px solid {self.border_color};'else:self.style = f'border-radius: {self.rad}px;background-color:{self.off_color}; border:1px solid {self.border_color};'self.label.setStyleSheet(self.style)
仪表盘
##########################
# 仪表盘 #
# 注意:标准部件之外的附加的部件名称用字符"_"(下划线)开头from PyQt5.QtGui import QPixmap, QTransform
from PyQt5.QtWidgets import QLabel, QFrame, QWidget
from PyQt5.QtCore import QTimer, Qt
from PyQt5.Qt import pyqtSignalclass MyDash(QFrame): # 自定义的仪表盘actualValue_signal = pyqtSignal(float) # 实际值的信号presetValue_signal = pyqtSignal(float) # 预设值的信号def __init__(self, parent=None):super().__init__(parent)# 图片资源 ##########self.panel_image = QPixmap("../SOURCE/img/表盘.png") # 表盘图片self.pointer_image = QPixmap("../SOURCE/img/指针.png") # 指针图片self.preset_image = QPixmap("../SOURCE/img/预设.png") # 预设图片# 所有部件 ########### self.presetValue = None # 预设值# self.actualValue = 0.0 # 实际值self.actualValue_connected = False # 实时值是否定义连接self.presetValue_connected = False # 预设值是否定义连接self.presetValue_value = None # 预设值的数值self.actualValue_value = None # 实时值的数值self.minValue = None # 最小值self.maxValue = None # 最大值self.step_up = None # +微调步距self.step_down = None # -微调步距self.panel = None # 表盘self.pointer = None # 指针self.preset = None # 预设值进度条self.adjust_up = None # 微调+按钮self.adjust_down = None # 微调-按钮self.label_setting = None # "设置值“标签self.label_actual = None # "实际值“标签self.first_init = True # 如果不是首次初始化,就只执行一部分def init(self, minValue=0.0, maxValue=100.0, step=1.0):self.minValue = minValueself.maxValue = maxValuemembers = self.findChildren(QWidget) # 查找子部件for i, m in enumerate(members): # 查找子部件if m.objectName().find('setting') != -1: # 子部件的名称包含‘setting’if self.first_init:self.label_setting = SettingLabel(m)self.label_setting.connected = False # 连接未定义过try:self.presetValue_value = float(m.text()) # 预设值的数值except ValueError:self.presetValue_value = 0.0self.preset_redraw(self.presetValue_value) # 更新预设值elif m.objectName().find('actual') != -1: # 子部件的名称包含‘actual’if self.first_init:self.label_actual = m # 实际值的显示标签self.label_actual.connected = Falsetry:self.actualValue_value = float(m.text()) # 实时值的数值except ValueError:self.actualValue_value = 0.0self.actual_redraw(self.actualValue_value) # 更新实时值elif m.objectName().find('pointer') != -1: # 子部件的名称是‘pointer’if self.first_init:self.pointer = mself.pointer.setPixmap(self.pointer_image) # 设置图片self.pointer.setAlignment(Qt.AlignCenter) # 居中显示self.pointer.connected = Falseelif m.objectName().find('panel') != -1: # 子部件的名称是‘panel’if self.first_init:self.panel = mself.panel.setPixmap(self.panel_image)self.panel.setAlignment(Qt.AlignCenter)self.panel.connected = Falseelif m.objectName().find('preset') != -1: # 子部件的名称是‘preset’if self.first_init:self.preset = mself.preset.setPixmap(self.preset_image)self.preset.setAlignment(Qt.AlignCenter)self.preset.connected = Falseelif m.objectName().find('adjust_up') != -1: # 子部件的名称包含‘adjust_up’if self.step_up != step: # 如果设置值有变self.step_up = stepif self.adjust_up is not None: # 隐藏掉默认的self.adjust_up.hide()self.adjust_up = MyAdjustButton(m, 'up', step) # 创建前端的操作部件self.adjust_up.connected = Falseelif m.objectName().find('adjust_down') != -1: # 子部件的名称包含‘adjust_down’if self.step_down != step:self.step_down = stepif self.adjust_down is not None:self.adjust_down.hide()self.adjust_down = MyAdjustButton(m, 'down', step)self.adjust_down.connected = Falseelse: # 子部件的名称是其他的自定义内容if self.first_init:n = m.objectName().find('_dash')variable_name = m.objectName()[:n]if variable_name.find('_') == 0:exec(f'self.{variable_name}=m')self.connect() # 信号的连接def connect(self):if not self.adjust_up.connected: # “+”按钮的连接self.adjust_up.Adjust.connect(self.preset_process) # 如果该连接未被定义过,就执行定义self.adjust_up.connected = True # 所有的部件只执行一次连接if not self.adjust_down.connected: # “-”按钮的连接self.adjust_down.Adjust.connect(self.preset_process)self.adjust_down.connected = Trueif not self.actualValue_connected: # 实时值的连接self.actualValue_signal.connect(self.actual_redraw) # 考虑换为valueChangedself.actualValue_connected = Trueif not self.presetValue_connected:self.presetValue_signal.connect(self.preset_redraw) # 考虑换为valueChangedself.presetValue_connected = Truedef preset_process(self, arg): # 预设值的处理preset = self.presetValue_value + arg # 中间变量,加上本次的微调值if preset >= self.maxValue:preset = self.maxValueelif preset <= self.minValue:preset = self.minValueself.presetValue_signal.emit(preset)def preset_redraw(self, arg): # 预设值的刷新preset = round(float(arg), 1) # 中间变量if round(self.presetValue_value, 1) != preset or self.first_init: # 判断是否需要刷新self.presetValue_value = presetdegree = (preset - self.minValue) / (self.maxValue - self.minValue) * 180 # 仪表盘指示部件的偏转角度self.preset.setPixmap(self.preset_image.transformed(QTransform().rotate(degree))) # 刷新画面self.label_setting.label.setText(str(preset)) # 刷新数字显示def actual_redraw(self, arg): # 实时值的刷新actual = round(float(arg), 1) # 中间变量if round(self.actualValue_value, 1) != actual:self.actualValue_value = actualdegree = (actual - self.minValue) / (self.maxValue - self.minValue) * 180 # 仪表盘指示部件的偏转角度self.pointer.setPixmap(self.pointer_image.transformed(QTransform().rotate(degree))) # 刷新画面self.label_actual.setText(str(actual)) # 刷新数字显示class MyAdjustButton(QLabel): # 自定义的微调按钮Pressed = pyqtSignal() # 键按下Enter = pyqtSignal() # 鼠标进入Leave = pyqtSignal() # 鼠标离开Release = pyqtSignal() # 鼠标松开Adjust = pyqtSignal(float) # 微调值的输出def __init__(self, label, ud, step):super().__init__(label)self.label = label # 后端的显示部件self.ud = ud # up or downself.step = step # 微调的步距self.n = 0 # 可变步距的计时值self.style_first = Noneself.style_last = Noneself.style_press = Noneself.style_normal = Noneself.style_enter = Noneself.timer = QTimer()self.timer.setInterval(100)self.timer.timeout.connect(self.time_out)self.style_normal = self.label.styleSheet().replace('\n', '') # 去掉样式表中的回车board_begin = self.style_normal.find('border:') # 边框定义的起点board_end = self.style_normal.find(';', board_begin, ) # 边框定义的终点if board_begin == -1:self.style_enter = self.style_normal + 'border: 1px solid rgba(255, 255, 255, 0);' # 鼠标悬停的样式表self.style_clicked = self.style_normal + 'border: 12px #039806);' # 鼠标点击的样式表else:board = self.style_normal[board_begin:board_end] # 样式表中关于边框的定义board_color_begin = board.find('solid') # 边框颜色的定义的起点board_color = board[board_color_begin:] # 边框颜色的定义new_enter_board = board.replace(board_color, 'solid #e6e6e6') # 新的鼠标悬停边框定义new_clicked_board = board.replace(board_color, 'solid #039806') # 新的鼠标按下边框定义self.style_enter = self.style_normal.replace(board, new_enter_board) # 新的鼠标悬停样式表self.style_press = self.style_normal.replace(board, new_clicked_board) # 新的鼠标按下样式表self.setFixedSize(self.label.width(), self.label.height()) # 获取部件的几何尺寸self.setStyleSheet('background-color: rgba(0, 0, 0, 0);') # 部件透明,叠加在原有的部件上self.connect() # 信号的连接def enterEvent(self, event): # 重新定义鼠标悬停事件self.Enter.emit()def leaveEvent(self, event): # 重新定义鼠标离开事件self.Leave.emit()def mousePressEvent(self, event): # 重新定义鼠标按下事件self.Pressed.emit()self.timer.start()def mouseReleaseEvent(self, event): # 重新定义鼠标抬起事件self.timer.stop()self.n = 0self.Release.emit()def time_out(self): # 定时器超时响应self.n += 1 # 计时器递增if self.timer.isActive():if self.ud == 'up': # 如果是+i = self.stepelse: # 如果是-i = -1.0 * self.stepif self.n < 20: # 定时器在2秒内self.Adjust.emit(0.1 * i) # 设定步距的0.1倍elif self.n < 50: # 定时器在5秒内self.Adjust.emit(i) # 设定步距else: # 定时器超过5秒self.Adjust.emit(10 * i) # 设定步距的10倍def connect(self):self.Enter.connect(self.enter)self.Leave.connect(self.leave)self.Pressed.connect(self.pressed)self.Release.connect(self.release)def pressed(self):self.style_last = self.label.styleSheet()self.label.setStyleSheet(self.style_press)def release(self):self.label.setStyleSheet(self.style_last)def enter(self):self.style_first = self.label.styleSheet().replace('\n', '')self.style_last = self.style_firstself.label.setStyleSheet(self.style_enter)def leave(self):self.label.setStyleSheet(self.style_first)class SettingLabel(QLabel): # “设置”标签的定义Clicked = pyqtSignal() # 鼠标点击DoubleClicked = pyqtSignal() # 鼠标双击Enter = pyqtSignal() # 鼠标进入Leave = pyqtSignal() # 鼠标离开Release = pyqtSignal() # 鼠标释放def __init__(self, label):super().__init__(label)self.font = Noneself.label = labelself.label.setText('')self.style_normal = self.label.styleSheet().replace('\n', '')board_begin = self.style_normal.find('border:')board_end = self.style_normal.find(';', board_begin, )if board_begin == -1:self.style_enter = self.style_normal + 'border: 1px solid rgba(255, 255, 255, 0);'self.style_clicked = self.style_normal + 'border: 12px #039806);'else:board = self.style_normal[board_begin:board_end]board_color_begin = board.find('solid')board_color = board[board_color_begin:]new_enter_board = board.replace(board_color, 'solid #e6e6e6')new_clicked_board = board.replace(board_color, 'solid #039806') #self.style_enter = self.style_normal.replace(board, new_enter_board)self.style_clicked = self.style_normal.replace(board, new_clicked_board)self.style_last = Noneself.style_first = Noneself.timer = QTimer()self.n = 0 # 点击次数self.first_click = True # 首次点击self.init()self.connect()def init(self):self.setFixedSize(self.label.width(), self.label.height()) # 获取部件的几何尺寸self.setStyleSheet('background-color: rgba(0, 0, 0, 0);') # 部件透明,叠加在原有的部件上self.setFont(self.label.font()) # 设置字体def connect(self):self.Enter.connect(self.enter)self.Clicked.connect(self.clicked)self.DoubleClicked.connect(self.doubleClicked)self.Release.connect(self.release)self.Leave.connect(self.leave)self.timer.timeout.connect(self.time_out)def mousePressEvent(self, event):self.style_last = self.label.styleSheet()self.label.setStyleSheet(self.style_clicked)if self.first_click: # 如果是首次点击self.timer.start(300) # 开始计时,如果出现了超时,就计算点击次数self.first_click = Falseelse:self.n += 1def time_out(self):if self.n == 0:self.Clicked.emit() # 单击else:self.DoubleClicked.emit() # 双击def mouseReleaseEvent(self, event): # 重新定义鼠标释放事件mouseReleaseEventself.Release.emit()def enterEvent(self, event): # 重新定义鼠标悬停事件# self.style_first = self.label.styleSheet().replace('\n', '')self.Enter.emit()def leaveEvent(self, event): # 重新定义鼠标离开事件self.label.setStyleSheet(self.style_normal)self.Leave.emit()def clicked(self):if self.timer.isActive():self.timer.stop()self.first_click = Trueself.n = 0def doubleClicked(self):if self.timer.isActive():self.timer.stop()self.first_click = Trueself.n = 0def release(self):self.label.setStyleSheet(self.style_last)def enter(self):self.style_first = self.label.styleSheet().replace('\n', '')self.style_last = self.style_firstself.label.setStyleSheet(self.style_enter)def leave(self):self.label.setStyleSheet(self.style_first)
运行截图: