QDataWidgetMapper 和验证
我有一个树形视图,里面有好几列。我用一个叫QDataWidgetMapper的东西,把每一列和旁边的几个小工具连接起来。用户可以通过双击树形视图中的单元格,或者使用旁边的小工具来修改数据。
其中一列的数据是字符串,需要进行验证。我创建了一个自定义的代理,并把它连接到树形视图和数据小工具映射器上。这个代理里有一个QRegExpValidator,用来防止用户在输入时输入错误的信息。此外,在setModelData()这个函数里,还有一个不同的验证检查,用于用户按下“Enter”键后进行验证。对于树形视图,这个代理工作得很好。但对于映射到QLineEdit的小工具,有两个问题:
- QRegExpValidator没有被调用(可能是因为旁边的小工具没有使用createEditor()这个方法),所以用户可以在QLineEdit里输入错误的信息。
- 如果在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
根据我的理解:
QDataWidgetMapper
是用来把模型数据映射到现有的控件上。所以,正如你提到的,代理方法createEditor
是不会被调用的。换句话说,QDataWidgetMapper
是用来替代在视图中使用代理方法createEditor
的一种方式。另一种方法是为你的侧边栏创建一个视图,注册你的代理,这样视图就会调用createEditor
,并共享tableView
的选择机制。你可以查看这个链接了解更多信息:http://qt-project.org/doc/qt-5/model-view-programming.html#sharing-selections-among-views。- 如果你按照上面的建议创建了一个侧边栏视图,你可以处理
setModelData
返回 false 的情况,比如通过恢复编辑器的值来解决。
我得承认,我对模型-视图的理解还比较浅。我很想听听其他人的看法。这是个很好的问题!
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))
如果数据在代理中检查失败,我会调用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