????????對組合式部件的制作又改進了一版,組合式部件的子部件不再需要單獨“提升為”,如果在模板文件的提升部件窗口內選擇了“全局包含”,那么只需要在模板文件和應用文件中直接復制粘貼即可,部件的應用更為簡便。如下圖:按住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)
運行截圖: