Pyside6(3): Qt-Parametereingabedialogfeld für die Benutzeroberfläche automatisch generieren

1. Einleitung

Die Parametereingabeschnittstelle ist einer der mühsamsten Teile der Desktop-Softwareentwicklung. Insbesondere wenn im System mehrere bearbeitbare numerische Modelle vorhanden sind, ist es erforderlich, unterschiedliche Benutzeroberflächen zu erstellen und unterschiedliche UI-Steuerelemente zu verwenden, da die Felder jedes Modells unterschiedlich sind und auch die Eingabetypen jedes Felds unterschiedlich sind, was zweifellos einen verbraucht viel Zeit. Auch die Skalierbarkeit ist sehr schlecht, da jedes Mal, wenn ein numerisches Modell hinzugefügt wird, eine entsprechende Parametereingabeschnittstelle erstellt werden muss.

Die in diesem Artikel vorgeschlagene Lösung besteht darin, davon auszugehen, dass das numerische Modell eine json-ähnliche Baumstruktur ist , und den entsprechenden Qt-Kontrollbaum durch rekursives Durchlaufen des Baums zu generieren , um die dynamische Generierung von Parametereingabeschnittstellen gemäß verschiedenen numerischen Modellen zu realisieren . Wenn der Benutzer nach der Bearbeitung des numerischen Modells auf die Schaltfläche „OK“ klickt, wird der generierte Qt-Kontrollbaum erneut durchlaufen, um die entsprechenden Eingabedaten zu erhalten. Darüber hinaus verfügt es über eine Funktion zur Überprüfung des zulässigen Werts, mit der das der illegalen Eingabe entsprechende Steuerelement lokalisiert und mit einem roten Rand hervorgehoben werden kann. Der Effekt ist wie folgt:

2. Analysieren Sie das numerische Modell in einen Qt-Kontrollbaum

2.1 Eingabeparametertyp

Zu den in diesem Artikel verwendeten numerischen Modellfeldern gehören Zeichenfolgen , Gleitkommazahlen, Ganzzahlen, boolesche Werte, Aufzählungswerte (dargestellt durch eine Liste) und zusammengesetzte Felder . Die Entsprechung zwischen Parametertypen und Qt-Steuerelementen ist wie folgt:

  • String – QLineEdit

  • Float - QDoubleSpinBox

  • Ganzzahl - QSpinBox

  • Boolean – QCheckBox

  • 帾值-QComboBox

  • Zusammengesetzte Felder können in die oben genannten Datentypen zerlegt werden, sodass sie einem QWidget entsprechen, das mehrere oben genannte Steuerelemente enthält.

2.2 Bearbeitbare numerische Modelldefinition

In diesem Artikel wird davon ausgegangen, dass numerische Modelle verschiedenen Klassen angehören können, aber alle über ein editableField- Attribut verfügen, das bearbeitbare Feldinformationen definiert. Zum Beispiel:

class A:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": True
            },
            "B": {
                "name": "复合字段B",
                "value": B()
            },
        }


class B:
    @property
    def editableField(self) -> dict:
        return {
            "string": {
                "name": "字符串",
                "value": "111111"
            },
            "bool": {
                "name": "布尔值",
                "value": False,
                "required": True
            },
            "float": {
                "name": "浮点值",
                "value": 1.0,
                "unit": "km",
                "min": 0,
                "max": -1
            },
            "int": {
                "name": "整数",
                "value": 1,
                "unit": "km",
                "min": 0,
                "max": -1
            },
            "dataObj": {
                "name": "复合字段C",
                "value": C(),
                "required": True
            },
            "enum": {
                "name": "枚举值",
                "value": [1, 2, 3],
                "required": True
            }
        }


class C:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": True
            },
            "gender": {
                "name": "性别",
                "value": "",
                "required": True
            }
        }


class Model:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": False
            },
            "he": {
                "name": "人",
                "value": A(),
                "root": True
            },
        }

Beachten

Alle in editableField definierten zusammengesetzten Felder können nicht miteinander verschachtelt werden. Beispielsweise enthält B ein bearbeitbares Feld vom Typ C und C ein bearbeitbares Feld vom Typ B, was beim Parsen zu einer Endlosschleife führt.

2.3 Grundaufbau des Dialogfensters

Die grundlegende Definition eines Dialogs lautet wie folgt:

from PySide6.QtGui import Qt
from PySide6.QtWidgets import QDialog, QMessageBox, QWidget, QTabWidget, QSpacerItem, QSizePolicy, QFormLayout, \
    QScrollArea, QFrame, QDialogButtonBox

from editDialog_utils import isDataObj, generate_widget, getContentLayout, checkInput, showErrorInputWidget


class AEditDialog(QDialog):
    def __init__(self, dataObj):
        """
        数据对象编辑对话框
        :param dataObj:  数据对象
        """
        super().__init__()
        self.__dataObj = dataObj
        # 用户数输入数据
        self.inputData = None
        self.resize(480, 360)
        # 控件树
        self.widget_tree = dict()
        self.__loadWidget()
        self.__connectWidget()

    def __loadWidget(self):
        """
        初始化控件
        :return:
        """
        tab = QTabWidget()
        self.tab = tab
        tab1 = QWidget()
        tab1.setLayout(getContentLayout(direction='v'))
        # 滚动视图
        tab1_scroll = QScrollArea()
        tab1_scroll.setObjectName(u"scroll")
        tab1_scroll.setWidgetResizable(True)
        tab1_scroll.setFrameShadow(QFrame.Raised)
        tab1.layout().addWidget(tab1_scroll)
        tab1_scrollArea = QWidget()
        tab1_layout = QFormLayout(tab1_scrollArea)
        tab.addTab(tab1, "common")
        # 控件所处tab索引,用于错误提示
        tab_index = 0
        # 根据数据对象动态生成ui
        for key, item in self.__dataObj.editableField.items():
            # 值
            value = item["value"]
            if isDataObj(value):
                # 顶级属性是数据对象时生成一个tab
                sub_tree = dict()
                self.widget_tree[key] = sub_tree
                # 新建tab
                tab2 = QWidget()
                tab2.setLayout(getContentLayout(direction='v'))
                # 布局和滚动视图
                scrollAreaWidgetContents = QWidget()
                content_layout = getContentLayout(direction='v')
                tab_index += 1
                # 添加控件
                w = generate_widget(sub_tree, key, item, tab_index)
                content_layout.addWidget(w)
                scrollAreaWidgetContents.setLayout(content_layout)
                # spacer
                vSpacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
                content_layout.addItem(vSpacer)
                # 滚动视图
                scroll = QScrollArea()
                scroll.setObjectName(u"scroll")
                scroll.setFrameShadow(QFrame.Raised)
                scroll.setWidgetResizable(True)
                tab2.layout().addWidget(scroll)
                scroll.setWidget(scrollAreaWidgetContents)
                # 添加tab
                tab.addTab(tab2, item["name"])
            else:
                w = generate_widget(self.widget_tree, key, item, 0)
                if isinstance(w, tuple):
                    tab1_layout.addRow(w[0], w[1])
                else:
                    tab1_layout.addWidget(w)
        # 添加控件到tab1 滚动视图
        tab1_scroll.setWidget(tab1_scrollArea)
        # 外部容器
        container_layout = getContentLayout(direction='v', margin=(4, 4, 4, 4))
        container_layout.addWidget(tab)
        # 添加底部按钮
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setObjectName(u"buttonBox")
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
        container_layout.addWidget(self.buttonBox)
        # 设置布局
        self.setLayout(container_layout)
        # 只有一页,隐藏tabbar
        if tab.count() == 1:
            tab.tabBar().setVisible(False)

    def __connectWidget(self):
        """
        连接槽函数
        :return:
        """
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def accept(self) -> None:
        """
        确定
        :return:
        """
        input_data = dict()
        # 输入数据校验
        inputData = checkInput(self.widget_tree, data_key="", input_data=input_data)
        if "widget" in inputData.keys():
            showErrorInputWidget(errorData=inputData, tab=self.tab)
            # 显示数据错误提示
            QMessageBox.critical(self, "错误", str("{} 必须填写!".format(inputData["name"])))
            return
        self.inputData = inputData
        super().accept()

Im obigen QDialog wird der Kontrollbaum im widget_tree- Wörterbuch gespeichert. Die Hauptfunktion hier ist __loadWidget . Diese Funktion erstellt zunächst ein QTabWidget , fügt es dem Dialogfeld hinzu und beginnt dann mit dem Durchlaufen des editableField des numerischen Modells zur Kontrollanalyse. Wenn es sich bei dem Popup-Feld um einen Basisdatentyp handelt, rufen Sie „generate_widget“ auf, um das Steuerelement zu generieren und es der aktuellen Registerkarte hinzuzufügen. Wenn es sich bei dem Feld um ein zusammengesetztes Feld handelt, wird eine neue Registerkarte erstellt, die Bildlaufansicht festgelegt und es dann der hinzugefügt QTabWidget. Unter diesen ist generic_widget die Methode zum rekursiven Durchlaufen der Felder des numerischen Modells, um den Kontrollbaum zu generieren.

Die Accpet- Funktion ist die Methode, die aufgerufen wird, wenn der Benutzer auf die Schaltfläche „OK“ klickt. Die Methoden checkInput und showErrorInputWidget zum Überprüfen von Benutzereingaben und zum Hervorheben falscher Eingabenwerden später beschrieben.

isDataObj und getContentLayout sind Hilfsmethoden, die zur Beurteilung zusammengesetzter Felder und zur Generierung von QVBoxLayout bzw. QHBoxLayout mit einheitlichem Stil verwendet werden . Es ist wie folgt definiert:

def isDataObj(data):
    """
    判断数据是否是基本类型之外的数据对象
    :param data:
    :return:
    """
    return type(data) not in [str, float, int, list, tuple, bool]


def getContentLayout(direction: str = "v", margin: tuple = (0, 0, 0, 0)) -> QVBoxLayout:
    """
    生成存放内容的布局
    :param margin:
    :param direction:
    :return:
    """
    if direction == "v":
        layout = QVBoxLayout()
    else:
        layout = QHBoxLayout()
    layout.setContentsMargins(*margin)
    return layout

2.4 Analysieren Sie, um den Qt-Kontrollbaum zu erhalten

Generieren Sie zunächst Steuerelemente basierend auf grundlegenden Datentypen:

def generateFloatWidget(item: dict) -> QDoubleSpinBox:
    """
    生成浮点数值控件
    :param item:
    :return:
    """
    dSpinbox = QDoubleSpinBox()
    minVal = 0
    maxVal = 0
    if "min" in item.keys():
        minVal = item["min"]
    if "max" in item.keys():
        maxVal = item["max"]
    if minVal > maxVal:
        # 设置无穷大
        maxVal = 1e20
        minVal = -maxVal
    dSpinbox.setMinimum(minVal)
    dSpinbox.setMaximum(maxVal)
    dSpinbox.setValue(item["value"])
    dSpinbox.setMinimumHeight(24)
    return dSpinbox

def generateIntWidget(item: dict) -> QSpinBox:
    """
    生成整数控件
    :param item:
    :return:
    """
    spinbox = QSpinBox()
    minVal = 0
    maxVal = 0
    if "min" in item.keys():
        minVal = item["min"]
    if "max" in item.keys():
        maxVal = item["max"]
    if minVal > maxVal:
        # 设置无穷大
        maxVal = int(1e9)
        minVal = -maxVal
    spinbox.setMinimum(minVal)
    spinbox.setMaximum(maxVal)
    spinbox.setValue(item["value"])
    spinbox.setMinimumHeight(24)
    return spinbox


def generateStrWidget(item: dict) -> QLineEdit:
    """
    生成单行文本输入控件
    :param item:
    :return:
    """
    lineEdit = QLineEdit()
    lineEdit.setMinimumHeight(24)
    value = item["value"]
    lineEdit.setText(value)
    return lineEdit


def generateBoolWidget(item: dict) -> QCheckBox:
    """
    生成布尔值输入控件
    :param item:
    :return:
    """
    checkBox = QCheckBox()
    name = item["name"]
    value = item["value"]
    checkBox.setChecked(value)
    checkBox.setText(name)
    return checkBox


def generateEnumWidget(item: dict) -> QComboBox:
    """
    生成枚举值对应的列表控件
    :param item:
    :return:
    """
    comboBox = QComboBox()
    comboBox.setMinimumHeight(24)
    value = item["value"]
    value = [str(i) for i in value]
    comboBox.addItems(value)
    return comboBox

Dann iterieren Sie rekursiv über die numerischen Modellfelder:

# 展开和收起图标
ic_down_arrow = "ic_down_arrow.svg"
ic_right_arrow = "ic_right_arrow.svg"

def generate_widget(widget_tree: dict, key, item, tab_index: int):
    """
    根据数据对象生成控件
    :param tab_index: 顶层tab索引
    :param item: 数据对象
    :param key: 数据键
    :param widget_tree: 控件树
    :return:
    """
    # 数据项名称
    name = item["name"]
    # 值
    value = item["value"]
    # 单位
    unit = None
    if "unit" in item.keys():
        # 单位
        unit = item["unit"]

    # 控件容器
    w = QWidget()
    w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

    # 字段名称和单位
    if unit is not None:
        label = QLabel("{}({}):".format(name, unit))
    else:
        label = QLabel("{}:".format(name))
    label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)

    required = False
    if "required" in item.keys():
        # 必填字段
        required = item["required"]

    widget_tree_node = {"tab_index": tab_index, "required": required, "name": name}

    if type(value) is float:
        doubleSpinBox = generateFloatWidget(item)
        widget_tree_node["widget"] = doubleSpinBox
        widget_tree[key] = widget_tree_node
        return label, doubleSpinBox

    if type(value) is int:
        intSpinBox = generateIntWidget(item)
        widget_tree_node["widget"] = intSpinBox
        widget_tree[key] = widget_tree_node
        return label, intSpinBox

    if type(value) is str:
        lineEdit = generateStrWidget(item)
        widget_tree_node["widget"] = lineEdit
        widget_tree[key] = widget_tree_node
        return label, lineEdit

    if type(value) is bool:
        checkBox = generateBoolWidget(item)
        widget_tree_node["widget"] = checkBox
        widget_tree[key] = widget_tree_node
        return checkBox

    if type(value) is list:
        comboBox = generateEnumWidget(item)
        widget_tree_node["widget"] = comboBox
        widget_tree[key] = widget_tree_node
        return label, comboBox

    if isDataObj(value):
        # 复合字段容器
        container = QWidget()
        container_layout = getContentLayout(direction='v')
        container_layout.setSpacing(0)
        container.setLayout(container_layout)
        # 子控件容器
        contentWidget = QWidget()
        contentWidget.setObjectName(u"contentWidget")
        if "root" not in item.keys() or not item['root']:
            # 非顶级对象添加收放按钮
            btnCollapse = QPushButton()
            btnCollapse.setObjectName(u"btnCollapse")
            btnCollapse.setLayout(getContentLayout(direction='h'))
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            btnCollapse.setSizePolicy(sizePolicy)
            btnCollapse.setFocusPolicy(Qt.NoFocus)
            btnCollapse.setFlat(True)
            # 图标
            iconBtn = QPushButton(btnCollapse)
            iconBtn.setObjectName(u"iconBtn")
            iconBtn.setFlat(True)
            iconBtn.setIconSize(QSize(20, 20))
            iconBtn.setIcon(QIcon(ic_down_arrow))
            btnCollapse.layout().addWidget(iconBtn)
            # 名称
            labelName = QLabel(name)
            labelName.setStyleSheet("font-size:12pt")
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            labelName.setSizePolicy(sizePolicy)
            btnCollapse.layout().addWidget(labelName)
            container.layout().addWidget(btnCollapse)
            # 收放槽函数
            iconBtn.clicked.connect(partial(collapseWidget, iconBtn, contentWidget))
            btnCollapse.clicked.connect(partial(collapseWidget, iconBtn, contentWidget))

        contentWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        contentWidget_layout = QFormLayout()
        # formlayout换行策略
        contentWidget_layout.setRowWrapPolicy(QFormLayout.WrapLongRows)
        contentWidget.setLayout(contentWidget_layout)
        # 添加子控件
        for key, item in value.editableField.items():
            sub_tree = dict()
            widget_tree[key] = sub_tree
            w = generate_widget(sub_tree, key, item, tab_index)
            if isinstance(w, tuple):
                # 子控件有label
                contentWidget_layout.addRow(w[0], w[1])
            else:
                # 无label,跨列
                row_index = contentWidget_layout.rowCount()
                contentWidget_layout.setWidget(row_index, QFormLayout.SpanningRole, w)
        container.layout().addWidget(contentWidget)
        return container

Wenn in der obigen Funktion der Feldtyp ein Basisdatentyp ist, generieren Sie die oben genannten Basissteuerelemente und fügen Sie sie dem Kontrollbaum hinzu. Wenn das Feld ein zusammengesetztes Feld ist, erstellen Sie ein QWidget als Container und fügen Sie eine einziehbare Schaltfläche hinzu Erstellen Sie dann ein neues contentWidget und generieren Sie darin rekursiv Untersteuerelemente. Jedes Mal, wenn ein zusammengesetzter Feldtyp angetroffen wird, wird dem Kontrollbaum ein Zweig, das sub_tree-Wörterbuch, hinzugefügt. Die Funktion collapWidget wird verwendet, um das Widget zurückzuziehen, definiert als:

def collapseWidget(iconBtn: QPushButton, collapsedWidget: QWidget, hold: bool = None):
    """
    收放控件
    :param hold: 保持某个状态
    :param iconBtn: 指示图标控件
    :param collapsedWidget:
    :return:
    """
    show = not collapsedWidget.isVisible()
    if hold is not None:
        show = hold
    if show:
        icon = QIcon(ic_down_arrow)
    else:
        icon = QIcon(ic_right_arrow)
    collapsedWidget.setVisible(show)
    iconBtn.setIcon(icon)

3. Überprüfen und holen Sie Benutzereingaben ein

3.1 Durchlaufen Sie den Kontrollbaum, um Benutzereingaben zu erhalten und zu überprüfen

Das Erhalten von Benutzereingaben erfolgt tatsächlich durch das Durchlaufen des Kontrollbaums, d. h. des widget_tree- Wörterbuchs. Definieren Sie zunächst die Funktion, um die Eingabe eines einzelnen Steuerelements zu erhalten:

def getWidgetInput(widget):
    """
    获取单个控件输入
    :param widget:
    :return:
    """
    if type(widget) == QLineEdit:
        return widget.text()
    if type(widget) == QCheckBox:
        return widget.isChecked()
    if type(widget) == QDoubleSpinBox:
        return widget.value()
    if type(widget) == QSpinBox:
        return widget.value()
    if type(widget) == QComboBox:
        return widget.currentText()

Definieren Sie dann die Funktion, die die Eingabe eines einzelnen Steuerelements prüft:

def checkWidget(widget, required: bool) -> bool:
    """
    检查单个控件输入
    :param widget:
    :param required: 是否必填
    :return:
    """
    if not required:
        # 非必填
        return True
    # 检查必填
    filled = False
    if type(widget) == QLineEdit:
        filled = widget.text() != ""
    if type(widget) == QCheckBox:
        filled = widget.isChecked()
    if type(widget) == QComboBox:
        filled = widget.currentText() != ""
    return filled

Durchlaufen Sie dann rekursiv den Kontrollbaum, rufen Sie Benutzereingaben ab und überprüfen Sie sie:

def checkInput(widget_tree: dict, data_key: str, input_data: dict):
    """
    获取并检查用户输入
    :param input_data: 存储输入数据
    :param data_key: 数据键,a.b.c
    :param widget_tree: 控件树
    :return:
    """
    for key, value in widget_tree.items():
        if "widget" not in value.keys():
            # value是个树节点
            if data_key != "":
                key = "{}.{}".format(data_key, key)
            input_data = checkInput(value, key, input_data)
            if "widget" in input_data.keys():
                # 出错终止
                return input_data
        else:
            # value是叶
            widget = value["widget"]
            tab_index = value["tab_index"]
            required = value["required"]
            name = value["name"]
            if not checkWidget(widget, required):
                # 边框显示为红色
                widget.setStyleSheet("border:1px solid red;")
                # 检查到输入错误,切换到出错的那个tab
                return {"name": name, "widget": widget, "tab_index": tab_index}
            else:
                # 清空错误样式
                widget.setStyleSheet(QWidget().styleSheet())

            if data_key == "":
                # 顶层数据字段
                input_data[key] = getWidgetInput(widget)
            else:
                # 控件树叶的数据字段
                input_data[data_key] = getWidgetInput(widget)
    return input_data

Die erhaltene Benutzereingabe lautet wie folgt:

{
  'name': 'comm名称',
  'he.name': '人名',
  'he.dataObj_B.string': '111111',
  'he.dataObj_B.bool': True,
  'he.dataObj_B.float': 1.0,
  'he.dataObj_B.int': 1,
  'he.dataObj_B.dataObj_C.name': 'C名称',
  'he.dataObj_B.dataObj_C.gender': 'C性别',
  'he.dataObj_B.enum': '1'
}

Da das Python-Objekt über die Setattr- Funktion zum Festlegen von Attributen verfügt, muss die Benutzereingabe hier nicht in einer Baumstruktur verschachtelt werden, und die Rekursion kann weniger verwendet werden, und stattdessen kann die Schleife verwendet werden, um die Laufgeschwindigkeit zu verbessern. Siehe Abschnitt 4 für Einzelheiten.

3.2 Suchen und markieren Sie falsche Eingaben

Wenn in einem bestimmten Steuerelement eine unzulässige Eingabe gefunden wird, stoppt die checkInput- Funktion die Rekursion und gibt Informationen über das Fehlersteuerelement zurück, zum Beispiel:

{
  'name': '名称',
  'widget': <PySide6.QtWidgets.QLineEdit(0x1b9b2830)at 0x000000001C4A9200>,
  'tab_index': 1
}

Dabei gibt tab_index den Tab- Index an , in dem sich das Steuerelement befindet . Die Funktion zum Hervorheben der Position einer fehlerhaften Eingabe lautet wie folgt:

def showErrorInputWidget(errorData: dict, tab: QTabWidget):
    """
    显示输入错误的控件
    :param tab: 顶层tab
    :param errorData: 错误数据
    :return:
    """
    # 切换到所处tab
    tab.setCurrentIndex(errorData["tab_index"])
    # 取出对应错误控件
    widget = errorData["widget"]
    if not widget.isVisible():
        # 展开未展开的父节点显示控件
        parent = widget
        while True:
            parent = parent.parent()
            if parent is None:
                break
            # 查找下拉图标按钮和收放控件
            iconBtn = parent.findChild(QPushButton, "iconBtn")
            contentWidget = parent.findChild(QWidget, u"contentWidget")
            if contentWidget is not None and not contentWidget.isVisible():
                #展开
                collapseWidget(iconBtn, contentWidget, hold=True)

    # 滚动到控件所在位置
    tab_page = tab.currentWidget()
    scrollArea = tab_page.findChild(QScrollArea, "scroll")
    pointTab = tab_page.mapToGlobal(QPoint(0, 0))
    # 计算控件是否在滚动区可视范围内
    y1 = pointTab.y()
    h1 = tab_page.height()
    pointW = widget.mapToGlobal(QPoint(0, 0))
    y2 = pointW.y()
    h2 = widget.height()
    cond1 = y2 + h2 < y1 - 4
    cond2 = y1 + h1 < y2 + 4
    dy = 0
    # 计算滚动距离
    if cond1:
        dy = y2 - y1 - 4
    if cond2:
        dy = (y2 + h2) - (y1 + h1) + 4
    # 滚动到错误控件
    verticalScrollBar = scrollArea.verticalScrollBar()
    verticalScrollBar.setValue(verticalScrollBar.value() + dy)

4. Schreiben Sie Benutzereingaben in das numerische Modellobjekt

Die Funktion des Schreibens von Benutzereingaben in das numerische Modellobjekt ist wie folgt: Da Python eine interpretierte Sprache ist, die Attribute für Objekte dynamisch festlegen kann, werden hier Schleifen anstelle von Rekursionen verwendet, um die Effizienz zu verbessern.

class Field:
    """
    字段属性
    """


def setField(self, data: dict):
    """
    设置字段
    :param self: 数值模型
    :param data:
    :return:
    """
    for key, value in data.items():
        if "." not in key:
            setattr(self, key, value)
            continue
        # 根据键定位到复合字段类型
        dataObj = self
        sub_keys = str(key).split(".")
        for sub_key in sub_keys[:-1]:
            try:
                dataObj = getattr(dataObj, sub_key)
            except AttributeError:
                # 没有预创建该属性则新建属性
                field = Field()
                setattr(dataObj, sub_key, field)
                dataObj = field
        setattr(dataObj, sub_keys[-1], value)

Die setField-Methode kann auf folgende Weise einheitlich zum numerischen Modell hinzugefügt werden:

class A:
    def __init__(self):
        self.setField = partial(setField, self)


class B:
    def __init__(self):
        self.setField = partial(setField, self)


class C:
    def __init__(self):
        self.setField = partial(setField, self)



class Model:
    def __init__(self):
        self.setField = partial(setField, self)

Das Ergebnis der Operation ist wie folgt:

Supongo que te gusta

Origin blog.csdn.net/anbuqi/article/details/129049601
Recomendado
Clasificación