【问题标题】:Notify user of unsaved changes across fields of different types通知用户跨不同类型字段的未保存更改
【发布时间】:2022-01-06 03:20:51
【问题描述】:

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

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

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

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

  1. UI 是从 Qt Designer .ui 文件构建的,因此 一些 字段具有默认值(如 QSpinBox、QDateEdit)
  2. 不同 QTableViews 的模型使用某些默认数据结构进行初始化,例如 None 的二维数组,或者其键都返回 None 的 dict
  3. 从文档存储中加载了一堆文档,并将字段设置为这些文档的值。可能会发生文档中不存在对应键的情况,因此不会设置该字段。但也有可能文档中的某个值恰好是默认值。
  4. 用户更改了某些字段的内容,并且在将文档保存到商店时会相应更新。

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

【问题讨论】:

  • 如果小部件是简单的元素,它们都有一个属性,那么使用每个小部件的 QMetaObject 是可行的,但是 QTableView 具有更复杂的结构;用户如何更改模型的数据?用户可以更改模型大小吗?
  • @musicamante 模型的列数和行数如果固定,用户可以更改单元格的内容
  • 数据字段在开始时是否为空,或者它们是否具有可以根据情况更改的“默认”值?
  • 所有字段都使用默认值初始化(无、“”、0等)
  • 这些看起来更像是空值,而不是真正的默认值。我问这个是因为检查一个字段的值是否被小部件的默认值更改,并检查它是否有一个被“初始化”状态更改的值是非常不同的:第一种情况更容易,因为标准小部件的默认值是已知的(按钮未选中,行编辑为空等),而第二个有点不同,因为我们需要保留对原始值的引用,用它们初始化小部件,并最终将它们的值与原始值进行比较。您能否提供您当前状态的基本minimal reproducible example

标签: qt pyqt


【解决方案1】:

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

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

您需要创建一个需要监控更改的小部件列表,在初始化期间获取值,然后在关闭时验证它。
为了简化事情,您可以为所有需要检查的小部件设置一个动态属性,以便您可以遍历所有小部件,检查是否设置了属性,并将这些小部件添加到列表中。要在 Designer 中向小部件添加自定义属性,请选择小部件并单击属性编辑器中的“+”符号;在以下示例中,我使用了带有基本真实性检查的布尔属性(“验证”):请记住,未设置的属性返回 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()

【讨论】:

    猜你喜欢
    • 2011-07-22
    • 2011-12-09
    • 1970-01-01
    • 2019-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多