通知用户不同类型字段的未保存更改

Notify user of unsaved changes across fields of different types

我的 PyQt5 应用程序包含一堆不同类型的输入字段:QLineEdit、QSpinBox、QComboBox、QTableView 等

如果用户输入数据或更改一个或多个字段的内容并尝试在不保存的情况下关闭 window,我想提醒用户。

我是否必须将 textChanged 信号的所有不同变体连接到某种簿记函数,或者是否有更简单的方法?

编辑:有关事件顺序的更多详细信息:

  1. UI 从 Qt Designer .ui 文件中获取 build,因此 some 字段具有默认值(如 QSpinBox , QDateEdit)
  2. 不同 QTableViews 的模型使用某些默认数据结构进行初始化,例如 None 的二维数组,或键全部为 return None
  3. 的字典
  4. 从文档存储中加载了一堆文档,并将字段设置为这些文档的值。可能会发生文档中不存在相应的键,因此不会设置该字段。但也有可能文档中的某个值恰好是默认值。
  5. 用户更改了某些字段的内容,存储中的文档将相应更新。

我想知道在第 3 步之后是否有任何字段被修改,也就是说用户进行了更改。我想避免将所有字段与文档存储进行比较。

对于存储单个 属性 的简单小部件,解决方案是使用每个小部件的元对象的 用户 属性

项目视图需要自定义检查以比较模型。

您需要创建一个需要监视更改的小部件列表,在初始化期间获取值,然后在关闭时验证它。
为了简化事情,您可以为所有需要检查的小部件设置一个动态 属性,这样您就可以遍历所有小部件,检查是否设置了 属性,并将这些小部件添加到列表中。要在 Designer 中向小部件添加自定义 属性,select 小部件并单击 属性 编辑器中的“+”符号;在下面的示例中,我使用了带有基本真实性检查的布尔值 属性(“验证”):请记住未设置 return None 的属性,因此在这种情况下您还必须设置属性 到 True.

class Test(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        uic.loadUi('mapper.ui', self)
        self.loadData()

    def loadData(self):
        # some stuff setting the initial values
        # ...

        # save the current values
        self.fieldData = []
        for widget in self.findChildren(QtWidgets.QWidget):
            if not widget.property('validate'):
                continue
            if isinstance(widget, QtWidgets.QAbstractItemView):
                model = widget.model()
                if not model:
                    continue
                data = []
                for row in range(model.rowCount()):
                    rowData = []
                    data.append(rowData)
                    for column in range(model.columnCount()):
                        rowData.append(model.index(row, column).data())
                self.fieldData.append((widget, data))
            else:
                property = widget.metaObject().userProperty()
                self.fieldData.append((widget, widget.property(property.name())))

    def ignoreChanges(self):
        for widget, default in self.fieldData:
            if isinstance(widget, QtWidgets.QAbstractItemView):
                model = widget.model()
                for row, rowData in enumerate(default):
                    for column, itemData in enumerate(rowData):
                        if model.index(row, column).data() != default:
                            break
            else:
                property = widget.metaObject().userProperty()
                if widget.property(property.name()) != default:
                    break
        else:
            return True
        res = QtWidgets.QMessageBox.question(self, 'Ignore changes', 
            'Fields have been modified, do you want to ignore?', 
            QtWidgets.QMessageBox.Ok|QtWidgets.QMessageBox.Cancel)
        if res != QtWidgets.QMessageBox.Ok:
            return False
        return True

    def reject(self):
        if self.ignoreChanges():
            super().reject()

    def closeEvent(self, event):
        if not self.ignoreChanges():
            event.ignore()