QDataWidgetMapper 和验证

2 投票
2 回答
997 浏览
提问于 2025-04-18 06:46

我有一个树形视图,里面有好几列。我用一个叫QDataWidgetMapper的东西,把每一列和旁边的几个小工具连接起来。用户可以通过双击树形视图中的单元格,或者使用旁边的小工具来修改数据。

其中一列的数据是字符串,需要进行验证。我创建了一个自定义的代理,并把它连接到树形视图和数据小工具映射器上。这个代理里有一个QRegExpValidator,用来防止用户在输入时输入错误的信息。此外,在setModelData()这个函数里,还有一个不同的验证检查,用于用户按下“Enter”键后进行验证。对于树形视图,这个代理工作得很好。但对于映射到QLineEdit的小工具,有两个问题:

  1. QRegExpValidator没有被调用(可能是因为旁边的小工具没有使用createEditor()这个方法),所以用户可以在QLineEdit里输入错误的信息。
  2. 如果在setModelData()时数据验证失败,QLineEdit里的文本不会恢复到原来的内容。所以当用户点击QLineEdit以外的地方时,错误信息又会被打印出来。

我这样做是不是不对?

这里有一个简化的例子。我把树形视图换成了列表视图,以便更简单:

class TestWidgetMapperValidate(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(TestWidgetMapperValidate, self).__init__(parent)

        self.centralWidget = QtGui.QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainLayout = QtGui.QVBoxLayout(self.centralWidget)

        # Set up the list view
        self.listView = QtGui.QListView()
        self.listModel = QtGui.QStringListModel(['aaa', 'bbb', 'ccc', 'ddd'])
        self.listView.setModel(self.listModel)

        # Set up the delegate
        self.testDelegate = TestDelegate()
        self.listView.setItemDelegateForColumn(0, self.testDelegate)

        self.lineEdit = QtGui.QLineEdit()

        self.mainLayout.addWidget(self.listView)
        self.mainLayout.addWidget(self.lineEdit)

        # Set up the QDataWidgetMapper
        self.mapper = QtGui.QDataWidgetMapper()
        self.mapper.setModel(self.listModel)
        self.mapper.addMapping(self.lineEdit, 0)
        self.mapper.setItemDelegate(self.testDelegate)

        self.listView.selectionModel().currentChanged.connect(self.mapper.setCurrentModelIndex)

class TestDelegate(QtGui.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(TestDelegate, self).__init__(parent)

    def createEditor(self, parentWidget, option, qModelIndex):
        editor = QtGui.QLineEdit(parentWidget)
        nameRegex = QtCore.QRegExp('[a-zA-Z][a-zA-Z0-9_]+')
        editor.setValidator(QtGui.QRegExpValidator(nameRegex))
        return editor

    def setEditorData(self, editor, qModelIndex):
        value = qModelIndex.data(QtCore.Qt.DisplayRole)
        editor.setText(value)

    def setModelData(self, editor, model, qModelIndex):
        if not editor.hasAcceptableInput():
            return False

        oldValue = qModelIndex.data(QtCore.Qt.DisplayRole)
        newValue = editor.text()

        if oldValue != newValue:
            if newValue in model.stringList():
                print 'That name already exists: {0}'.format(newValue)
                return False
            else:
                return model.setData(qModelIndex, newValue)
        else:
            return True

(注意:我使用的是PySide和Python 2.7)

2 个回答

1

根据我的理解:

  1. QDataWidgetMapper 是用来把模型数据映射到现有的控件上。所以,正如你提到的,代理方法 createEditor 是不会被调用的。换句话说,QDataWidgetMapper 是用来替代在视图中使用代理方法 createEditor 的一种方式。另一种方法是为你的侧边栏创建一个视图,注册你的代理,这样视图就会调用 createEditor,并共享 tableView 的选择机制。你可以查看这个链接了解更多信息:http://qt-project.org/doc/qt-5/model-view-programming.html#sharing-selections-among-views
  2. 如果你按照上面的建议创建了一个侧边栏视图,你可以处理 setModelData 返回 false 的情况,比如通过恢复编辑器的值来解决。

我得承认,我对模型-视图的理解还比较浅。我很想听听其他人的看法。这是个很好的问题!

1

这是我想到的解决办法。我不知道这是不是最好的方法,但对我来说是有效的。

  1. 我直接在我的文本输入框上添加了一个QRegExpValidator,因为我没法让它读取来自代理的那个。以下是__init__()中的更新代码:

    class TestWidgetMapperValidate(QtGui.QMainWindow):
        def __init__(self, parent=rsui.getMayaMainWindow()):
            # some code omitted here
            self.lineEdit = QtGui.QLineEdit()
            nameRegex = QtCore.QRegExp('[a-zA-Z][a-zA-Z0-9_]+')
            self.lineEdit.setValidator(QtGui.QRegExpValidator(nameRegex))
    
  2. 如果数据在代理中检查失败,我会调用setEditorData(),把原来的值传回去,这样就能强制它恢复到旧值。这可以防止错误信息被打印两次。以下是TestDelegate类中更新后的setModelData()代码:

    def setModelData(self, editor, model, qModelIndex):
        if not editor.hasAcceptableInput():
            return False
    
        oldValue = qModelIndex.data(QtCore.Qt.DisplayRole)
        newValue = editor.text()
    
        if oldValue != newValue:
            if newValue in model.stringList():
                # The new value is not valid.  Set the data back to the original value.
                self.setEditorData(editor, qModelIndex)
                print 'That name already exists: {0}'.format(newValue)
                return False
            else:
                return model.setData(qModelIndex, newValue)
        else:
            return True
    

撰写回答