将按钮添加到 ListView 项的右侧
Add Button to Right of ListView Item
如何在 QListViewItems 的右侧添加一个按钮?我正在尝试重新创建一个与您在 Whosebug 上看到的类似的标签编辑器。
当前:
目标:
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = QtWidgets.QListView()
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
更新#1
我尝试使用 ItemDelegate,但它似乎会产生更多问题,例如列表中文本周围的间距和填充。我不明白为什么我覆盖的 SizeHint 似乎无法正常工作。 X 按钮在不应该重叠的时候仍然重叠。
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QItemDelegate.__init__(self, parent, *args)
def paint(self, painter, option, index):
painter.save()
isw, ish = option.decorationSize.toTuple()
x, y = option.rect.topLeft().toTuple()
dx, dy = option.rect.size().toTuple()
value = index.data(QtCore.Qt.DisplayRole)
rect = QtCore.QRect(x, y, dx, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignLeft, value)
rect = QtCore.QRect(x+dx-5, y, 16, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
# rect.adjust(0, 0, 15, 0)
return QtCore.QSize(rect.width(), rect.height())
return super(TagDelegate, self).sizeHint(option, index)
class TagListView(QtWidgets.QListView):
def __init__(self, *arg, **kwargs):
super(TagListView, self).__init__()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setMovement(QtWidgets.QListView.Static)
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setMouseTracking(True)
self.setItemDelegate(TagDelegate())
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
# self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = TagListView()
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
sizeHint()
返回的尺寸必须覆盖整个项目尺寸。在您的 paintEvent
方法中,QtCore.QRect(x+dx-5, y, 16, dy)
定义了一个在项目边界矩形 (16 - 5) 之外有 11 个像素的矩形。
计算正确尺寸的最简单方法是考虑 sizeHint
中的按钮尺寸:
class TagDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._buttonSize = QSize(16, 16)
def buttonRect(self, boundingRect):
return boundingRect.adjusted(boundingRect.width() - self._buttonSize.width(), 0, 0, 0)
def labelRect(self, boundingRect):
return boundingRect.adjusted(0, 0, -self._buttonSize.width(), 0)
def paint(self, painter, option, index):
text = index.data(QtCore.Qt.DisplayRole)
painter.save()
painter.drawText(self.labelRect(option.rect), QtCore.Qt.AlignLeft, text)
rect = self.buttonRect(option.rect)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
return QSize(rect.width() + self._buttonSize.width(), max(rect.height(), self._buttonSize.height()))
return super(TagDelegate, self).sizeHint(option, index)
我使用了两种方法(buttonRect
和labelRect
)来获取项目每个部分的位置。它将更容易处理关闭按钮上的点击:
def editorEvent(self, event, model, option, index):
if not isinstance(event, QMouseEvent) or event.type() != QEvent.MouseButtonPress:
return super().editorEvent(event, model, option, index)
if self.buttonRect(option.rect).contains(event.pos()):
print("Close button on ", model.data(index, Qt.DisplayRole))
return True
elif self.labelRect(option.rect).contains(event.pos()):
print("Label clicked on ", model.data(index, Qt.DisplayRole))
return True
return False
如何在 QListViewItems 的右侧添加一个按钮?我正在尝试重新创建一个与您在 Whosebug 上看到的类似的标签编辑器。
当前:
目标:
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = QtWidgets.QListView()
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
更新#1
我尝试使用 ItemDelegate,但它似乎会产生更多问题,例如列表中文本周围的间距和填充。我不明白为什么我覆盖的 SizeHint 似乎无法正常工作。 X 按钮在不应该重叠的时候仍然重叠。
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QItemDelegate.__init__(self, parent, *args)
def paint(self, painter, option, index):
painter.save()
isw, ish = option.decorationSize.toTuple()
x, y = option.rect.topLeft().toTuple()
dx, dy = option.rect.size().toTuple()
value = index.data(QtCore.Qt.DisplayRole)
rect = QtCore.QRect(x, y, dx, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignLeft, value)
rect = QtCore.QRect(x+dx-5, y, 16, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
# rect.adjust(0, 0, 15, 0)
return QtCore.QSize(rect.width(), rect.height())
return super(TagDelegate, self).sizeHint(option, index)
class TagListView(QtWidgets.QListView):
def __init__(self, *arg, **kwargs):
super(TagListView, self).__init__()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setMovement(QtWidgets.QListView.Static)
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setMouseTracking(True)
self.setItemDelegate(TagDelegate())
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
# self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = TagListView()
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
sizeHint()
返回的尺寸必须覆盖整个项目尺寸。在您的 paintEvent
方法中,QtCore.QRect(x+dx-5, y, 16, dy)
定义了一个在项目边界矩形 (16 - 5) 之外有 11 个像素的矩形。
计算正确尺寸的最简单方法是考虑 sizeHint
中的按钮尺寸:
class TagDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._buttonSize = QSize(16, 16)
def buttonRect(self, boundingRect):
return boundingRect.adjusted(boundingRect.width() - self._buttonSize.width(), 0, 0, 0)
def labelRect(self, boundingRect):
return boundingRect.adjusted(0, 0, -self._buttonSize.width(), 0)
def paint(self, painter, option, index):
text = index.data(QtCore.Qt.DisplayRole)
painter.save()
painter.drawText(self.labelRect(option.rect), QtCore.Qt.AlignLeft, text)
rect = self.buttonRect(option.rect)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
return QSize(rect.width() + self._buttonSize.width(), max(rect.height(), self._buttonSize.height()))
return super(TagDelegate, self).sizeHint(option, index)
我使用了两种方法(buttonRect
和labelRect
)来获取项目每个部分的位置。它将更容易处理关闭按钮上的点击:
def editorEvent(self, event, model, option, index):
if not isinstance(event, QMouseEvent) or event.type() != QEvent.MouseButtonPress:
return super().editorEvent(event, model, option, index)
if self.buttonRect(option.rect).contains(event.pos()):
print("Close button on ", model.data(index, Qt.DisplayRole))
return True
elif self.labelRect(option.rect).contains(event.pos()):
print("Label clicked on ", model.data(index, Qt.DisplayRole))
return True
return False