对组合式部件的制作又改进了一版,组合式部件的子部件不再需要单独“提升为”,如果在模板文件的提升部件窗口内选择了“全局包含”,那么只需要在模板文件和应用文件中直接复制粘贴即可,部件的应用更为简便。如下图:按住ctrl,直接拖拽即可。
主程序
from sys import exit, argv
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5 import uic
import buttons # 按钮的内嵌图片资源
# 显示界面的初始化
def form_init(form): # 显示界面的初始化 (backup)
sons = form.findChildren(QWidget) # 儿辈部件
def find_child(child): # 查找子部件
try:
child.init() # 初始化部件
except AttributeError:
pass
if 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的扩展显示1
form0.dash_1._extra2.setText('999') # dash_1的扩展显示2
form0.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 pyqtSignal
class 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)
continue
n = 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): # 自定义的Qlabel
Pressed = pyqtSignal() # 键按下
Enter = pyqtSignal() # 鼠标进入
Leave = pyqtSignal() # 鼠标离开
Release = pyqtSignal() # 鼠标松开
Adjust = pyqtSignal(float) # 微调值的输出
def __init__(self, label):
super().__init__(label)
self.label = label
self.style_first = None
self.style_last = None
self.style_normal = self.label.styleSheet().replace('\n', '')
self.style_press = self.style_normal # 常态style
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: 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): # 自定义的Qlabel
def __init__(self, label):
super().__init__(label)
self.label = label
self.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 = 2
self.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 pyqtSignal
class 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 = minValue
self.maxValue = maxValue
members = 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.0
self.preset_redraw(self.presetValue_value) # 更新预设值
elif m.objectName().find('actual') != -1: # 子部件的名称包含‘actual’
if self.first_init:
self.label_actual = m # 实际值的显示标签
self.label_actual.connected = False
try:
self.actualValue_value = float(m.text()) # 实时值的数值
except ValueError:
self.actualValue_value = 0.0
self.actual_redraw(self.actualValue_value) # 更新实时值
elif m.objectName().find('pointer') != -1: # 子部件的名称是‘pointer’
if self.first_init:
self.pointer = m
self.pointer.setPixmap(self.pointer_image) # 设置图片
self.pointer.setAlignment(Qt.AlignCenter) # 居中显示
self.pointer.connected = False
elif m.objectName().find('panel') != -1: # 子部件的名称是‘panel’
if self.first_init:
self.panel = m
self.panel.setPixmap(self.panel_image)
self.panel.setAlignment(Qt.AlignCenter)
self.panel.connected = False
elif m.objectName().find('preset') != -1: # 子部件的名称是‘preset’
if self.first_init:
self.preset = m
self.preset.setPixmap(self.preset_image)
self.preset.setAlignment(Qt.AlignCenter)
self.preset.connected = False
elif m.objectName().find('adjust_up') != -1: # 子部件的名称包含‘adjust_up’
if self.step_up != step: # 如果设置值有变
self.step_up = step
if self.adjust_up is not None: # 隐藏掉默认的
self.adjust_up.hide()
self.adjust_up = MyAdjustButton(m, 'up', step) # 创建前端的操作部件
self.adjust_up.connected = False
elif m.objectName().find('adjust_down') != -1: # 子部件的名称包含‘adjust_down’
if self.step_down != step:
self.step_down = step
if self.adjust_down is not None:
self.adjust_down.hide()
self.adjust_down = MyAdjustButton(m, 'down', step)
self.adjust_down.connected = False
else: # 子部件的名称是其他的自定义内容
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 = True
if not self.actualValue_connected: # 实时值的连接
self.actualValue_signal.connect(self.actual_redraw) # 考虑换为valueChanged
self.actualValue_connected = True
if not self.presetValue_connected:
self.presetValue_signal.connect(self.preset_redraw) # 考虑换为valueChanged
self.presetValue_connected = True
def preset_process(self, arg): # 预设值的处理
preset = self.presetValue_value + arg # 中间变量,加上本次的微调值
if preset >= self.maxValue:
preset = self.maxValue
elif preset <= self.minValue:
preset = self.minValue
self.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 = preset
degree = (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 = actual
degree = (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 down
self.step = step # 微调的步距
self.n = 0 # 可变步距的计时值
self.style_first = None
self.style_last = None
self.style_press = None
self.style_normal = None
self.style_enter = None
self.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 = 0
self.Release.emit()
def time_out(self): # 定时器超时响应
self.n += 1 # 计时器递增
if self.timer.isActive():
if self.ud == 'up': # 如果是+
i = self.step
else: # 如果是-
i = -1.0 * self.step
if 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_first
self.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 = None
self.label = label
self.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 = None
self.style_first = None
self.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 = False
else:
self.n += 1
def time_out(self):
if self.n == 0:
self.Clicked.emit() # 单击
else:
self.DoubleClicked.emit() # 双击
def mouseReleaseEvent(self, event): # 重新定义鼠标释放事件mouseReleaseEvent
self.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 = True
self.n = 0
def doubleClicked(self):
if self.timer.isActive():
self.timer.stop()
self.first_click = True
self.n = 0
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_first
self.label.setStyleSheet(self.style_enter)
def leave(self):
self.label.setStyleSheet(self.style_first)
运行截图: