问题
I'm building a QTreeWidget where I'm implementing adding new item and renaming functionality. I'd like to check the validity of the new name given by user, including:
- the name can only contain a list of valid characters. This is achieved already by adding a
QRegExpValidator
to a subclassedQItemDelegate
, and assigning the new delegate to theQTreeWidget
. - the name can't conflict with its siblings. This I don't know now to achieve.
Here is my current attempt:
import sys
from PyQt5.QtWidgets import QItemDelegate, QTreeWidget, QVBoxLayout, QLineEdit,\
QMainWindow, QWidget, QTreeWidgetItem, QApplication
from PyQt5.QtCore import QRegExp, Qt
from PyQt5.QtGui import QRegExpValidator
class TreeWidgetDelegate(QItemDelegate):
def __init__(self, parent=None):
QItemDelegate.__init__(self, parent=parent)
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
reg=QRegExp('[A-z0-9\[\]_-]+')
vd=QRegExpValidator(reg)
editor.setValidator(vd)
return editor
class MainWindow(QMainWindow):
def __init__(self):
super(self.__class__, self).__init__()
frame=QWidget()
self.setCentralWidget(frame)
hl=QVBoxLayout()
frame.setLayout(hl)
self.tree=QTreeWidget(self)
mydele=TreeWidgetDelegate()
self.tree.setItemDelegate(mydele)
hl.addWidget(self.tree)
# add treewidgetitems
for ii in range(5):
item=QTreeWidgetItem([str(ii),])
self.tree.addTopLevelItem(item)
self.tree.itemDoubleClicked.connect(self.rename)
self.tree.itemChanged.connect(self.checkString)
dele=self.tree.itemDelegate()
print('dele',dele)
self.show()
def rename(self):
item=self.tree.selectedItems()
if item:
item=item[0]
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.tree.scrollToItem(item)
self.tree.editItem(item)
def checkString(self,item,column):
text=item.data(0,column)
print('newname:',text)
siblings=[self.tree.topLevelItem(ii).data(0,0) for ii in \
range(self.tree.topLevelItemCount())]
print('siblings:',siblings)
if text in siblings:
print('invalid name')
# this gives "edit: editing failed"
self.tree.editItem(item)
if __name__ == "__main__":
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
In particular, I'm connecting tree.itemChanged.connect(self.checkString)
, and checkString()
checks name conflicts. However, when conflict detected I don't know how to revert to the old name and re-enter into the edit mode and let user rename again. The tree.editItem(item)
would throw an error
edit: editing failed
. I guess that would trigger the signal again and end up in an endless loop?
I've found PyQt - Using Multiple Validators for Item Delegates related, but no answer is given, only a suggestion in the comment that one should subclass QValidator
to handle name conflict detection in the same regex. No idea how to do this, the validator is created and assigned before those QTreeWidgetItems
, isn't it?
Also this question Make QTreeWidgetItem unique among siblings. No body answered.
回答1:
I've found a solution:
import sys
from PyQt5.QtWidgets import QItemDelegate, QTreeWidget, QVBoxLayout, QLineEdit,\
QMainWindow, QWidget, QTreeWidgetItem, QApplication
from PyQt5.QtCore import QRegExp, Qt
from PyQt5.QtGui import QRegExpValidator
class TreeWidgetDelegate(QItemDelegate):
def __init__(self, parent=None):
QItemDelegate.__init__(self, parent=parent)
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
# allow only these chars
reg=QRegExp('[A-z0-9\[\]_-]+')
regvd=QRegExpValidator(reg)
editor.setValidator(regvd)
return editor
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
frame=QWidget()
self.setCentralWidget(frame)
hl=QVBoxLayout()
frame.setLayout(hl)
self.tree=QTreeWidget(self)
hl.addWidget(self.tree)
# assign custom delegate to treewidget
dele=TreeWidgetDelegate()
self.tree.setItemDelegate(dele)
# add treewidgetitems
for ii in range(5):
item=QTreeWidgetItem([str(ii)*3,])
self.tree.addTopLevelItem(item)
self.tree.itemDoubleClicked.connect(self.rename)
# QueuedConnection cures the editting failed issue
self.tree.itemChanged.connect(self.checkName, Qt.QueuedConnection)
self.show()
def getSiblings(self,item):
siblings=[self.tree.topLevelItem(ii).data(0,0) for ii in \
range(self.tree.topLevelItemCount())]
item_text=item.data(0,0)
if item_text in siblings:
siblings.remove(item_text)
return siblings
def rename(self):
item=self.tree.selectedItems()
if item:
item=item[0]
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.tree.scrollToItem(item)
self.tree.editItem(item)
def checkName(self,item,column):
text=item.data(0,0)
siblings=self.getSiblings(item)
print('checkName: slibings:', siblings)
if text in siblings:
print('checkName: ivalid')
item.setData(0,0,'New_name_needed')
self.tree.editItem(item)
if __name__ == "__main__":
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
It's still using a custom delegate to check for invalid characters. I tried adding the sibling conflict check in the delegate's editor, by subclassing QValidator
and providing it the current list of siblings. However this would perform on-the-fly validation rather than post-commit validation. For instance when checking for the 'abc' v.s. 'abc' conflict, I won't be able to type 'c' after 'ab', even though I meant to type 'abcd'.
I found this question regarding the edit: editting failed
error, and it seems that the Qt.QueuedConnection
does the trick. So the tree.itemChanged
is connected to a duplication check function, and if it fails the check, it prompts the user to re-enter name again. One can optionally popup a tooltip notifying the conflict.
May not be the ideal solution though.
来源:https://stackoverflow.com/questions/54443129/pyqt-validity-check-on-qtreewidgetitem