香,一套邏輯輕鬆且智慧解決PyQt中控制元件數值驗證的問題

2023-04-15 18:00:47

PyQt開發中,時常需要對控制元件的值進行校驗,如需要校驗QCheckBox是否被選中,QLabel是否校驗值是否為空等等。在複雜的業務場景下,這類控制元件如果數量很多,逐個校驗就顯得麻煩,需要一一獲得控制元件名稱,再呼叫對應的方法來判斷是否被選中、是否為空等。而且開發過程中如果多控制元件做了增減,還需要增減校驗的邏輯,那會要了老命。

此篇文章,推薦使用__dict__屬性 + 字典對映來快速校驗控制元件值,並且無視後面控制元件的增減,無需調整程式碼。

__dict__是什麼?

python開發的,或多或少都接觸過該屬性,它是獨有的一個特性,用來儲存的一些屬性,關於這個屬性的相關文章,網上一抓一大把,此處不作贅述,需要說明的是,類範例也有自己的__dict__屬性,而且和__dict不同,``類範例dict只儲存了通過self.xxx`所宣告的屬性和方法。

校驗邏輯演示

接下來的演示中,僅使用QCheckBox(為了省事),同時要保持這些控制元件的名稱要具備同樣的特徵,下面的截圖中,所有的控制元件名稱都以checkbox結尾。

1、使用.ui檔案生成.py檔案

2、簡單寫個入口程式

繼承剛才生成的.py檔案,在這裡可以實現自己的方法,在自定義類中,先列印__dict__,看看有哪些值。

{
  'centralwidget': <PyQt6.QtWidgets.QWidget object at 0x00000231DD4E9D30>, 
  'gridLayout': <PyQt6.QtWidgets.QGridLayout object at 0x00000231DD4E9DC0>, 
  'c_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD4E9E50>, 
  'd_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD4E9EE0>, 
  'a_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD4E9F70>, 
  'b_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD9A3040>, 
  'f_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD9A30D0>, 
  'e_checkbox': <PyQt6.QtWidgets.QCheckBox object at 0x00000231DD9A3160>, 
  'menubar': <PyQt6.QtWidgets.QMenuBar object at 0x00000231DD9A31F0>, 
  'statusbar': <PyQt6.QtWidgets.QStatusBar object at 0x00000231DD9A3280>
}

可以看到,它是一個字典,它包含了介面上所有的控制元件的名稱和範例物件,這裡就體現出了控制元件名稱命名時遵守統一特徵的好處了,即能望文生義,也方便處理。

3、提取所有QCheckBox的控制元件名稱,構建一個校驗的通用邏輯

import sys
from PyQt6.QtWidgets import QMainWindow, QApplication, QCheckBox
from ui_main import Ui_MainWindow

class MainWindow(Ui_MainWindow, QMainWindow):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.show()
        self.checkboxs()

    
    def checkboxs(self):
        # print(self.__dict__)

        # 增加這個字典對映是為了可以自動處理更多型別的控制元件值校驗
        widget_mapping = {
            'QCheckBox': QCheckBox.isChecked
        }
        
        # 獲取所有QCheckBox的控制元件名稱
        boxs = [
            item for item in self.__dict__ \
                if item.endswith('_checkbox')
        ]

        # 用來儲存未被勾選的控制元件
        un_checked = []

        for item in boxs:
            widget_instance = self.__dict__.get(item)
            widget_method = widget_mapping.get(
                self.__dict__.get(item).__class__.__name__
            )
            # 如果該QCheckBox處於未選中,則被新增到列表中
            if not widget_method(widget_instance):
                un_checked.append(item)

        print(un_checked)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

4、邏輯分析

(1)
在程式碼

boxs = [
            item for item in self.__dict__ \
                if item.endswith('_checkbox')
        ]

中,通過__dict__提取了所有以_checkbox結尾的控制元件名稱,注意,這只是字串而已,並非控制元件本身。

(2)構建一個通用的控制元件名稱和控制元件方法的字典對映

# 增加這個字典對映是為了可以自動處理更多型別的控制元件值校驗
        widget_mapping = {
            'QCheckBox': QCheckBox.isChecked
        }

這麼做的目的是為了讓這個邏輯更具通用性,讓這個邏輯相容其他控制元件,簡單來說,可以通過控制元件的名稱(boxs列表)找到該控制元件對應的方法,因為每個控制元件獲取值所用的方法不盡相同。

(3)遍歷boxs列表,逐個去widget_mapping找對應的方法,假如這裡要較多種控制元件的話,字典對映的優勢就體現出來了。widget_method就是該控制元件獲取值所要用的方法了,QCheckBox控制元件,就用isChecked方法來獲取控制元件是否被選中。

(4)呼叫所找到的方法widget_method。這裡之所以要把控制元件範例widget_instance傳入方法中,是因為控制元件方法isChecked是單獨呼叫的,它預設要傳入self引數即範例本身。

執行程式碼看看效果

可以看到6個選項均為選中,列印結果符合該事實。

qt desinger中預設勾選兩個,再試試效果

有4個未選中,列印結果符合事實。

總結

上面的僅為演示程式碼,只是演示處理此類問題的邏輯,剛構建邏輯時會顯得很繞,但是構建起來後就很好用了,如果再漸增控制元件,只要遵守控制元件名稱命名規範,那麼所增加的控制元件校驗也無需增加校驗程式碼,減少控制元件也一樣。

延申用法

如果一個介面中有很多控制元件需要填寫數值或清空數值,如:

  • 提交表單後,控制元件數值初始化
  • 校驗不通過,需要在控制元件中做資訊提示(諸如placeholder)

一樣可以使用這個邏輯進行處理,只要前期構建好,程式碼複用完全不是問題。