将按钮添加到 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)

我使用了两种方法(buttonRectlabelRect)来获取项目每个部分的位置。它将更容易处理关闭按钮上的点击:

    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