【问题标题】:QListWidget: How to drag and drop insert custom widget w/ icon?QListWidget:如何拖放插入带有图标的自定义小部件?
【发布时间】:2019-02-14 07:04:45
【问题描述】:

我创建了一个自定义 QListWidget,其中添加了自定义列表小部件(在本例中只是 QPushButton 小部件)添加到它的 QListWidgetItems,所有设置以便我可以从 QPushButton 拖放,以向 QListWidget 添加另一个按钮。在第一个列表小部件中,您会注意到在两个列表小部件项目之间有一条虚线,表示您可以在两个现有小部件之间拖动和插入小部件。但是,使用我的自定义列表,我已经失去了这种行为。我仍然可以将它们放在现有的小部件之间,但是没有像以前那样向用户提供视觉指示,即两个小部件之间的水平条将被插入。有谁知道我会怎么做?您可以在下图中看到,第一个列表中的行是在两者之间插入项目的默认指示符。红色箭头指向我想要创建的那种自定义插入指示器。

其次,当我从自定义 QPushButton 拖放时,它会突出显示为黑色,但再也不会恢复到正常的灰色,如上图所示。如何让这个按钮恢复到默认状态?

from inspect import isclass
from shiboken2 import wrapInstance
from PySide2 import QtCore, QtGui, QtWidgets
from maya import OpenMayaUI as omui


def show_ui():
    main_win_obj = omui.MQtUtil.mainWindow()
    main_win = wrapInstance(long(main_win_obj), QtWidgets.QWidget)
    win = Test(parent=main_win)
    win.show()


class DragButton(QtWidgets.QPushButton):

    def __init__(self, parent=None, text=''):
        super(DragButton, self).__init__()
        self.setText(text)

    def mouseMoveEvent(self, event):

        if event.buttons() != QtCore.Qt.LeftButton:
            super(DragButton, self).mouseMoveEvent(event)
            return

        btn_img = self.grab()
        painter = QtGui.QPainter(btn_img)
        painter.setCompositionMode(
            painter.CompositionMode_DestinationIn
        )
        painter.fillRect(btn_img.rect(), QtGui.QColor(0, 0, 0, 127))
        painter.end()

        data = QtCore.QMimeData()
        data.setText('ReorderListAdd()')

        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.setPixmap(btn_img)
        drag.setHotSpot(event.pos())
        drag.exec_(QtCore.Qt.CopyAction)

        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):

        data = QtCore.QMimeData()
        data.setText('my text')

        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

        super(DragButton, self).mouseReleaseEvent(event)


class ReorderList(QtWidgets.QListWidget):

    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)

        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )

    def add_btn(self, text='', index=-1):

        widget = QtWidgets.QWidget()
        if not text:
            text = 'item {0}'.format(self.count() + 1)
        btn = QtWidgets.QPushButton(text)

        layout = QtWidgets.QVBoxLayout(widget)
        layout.setContentsMargins(2, 2, 2, 2)
        layout.setSpacing(2)
        layout.addWidget(btn)

        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(widget.sizeHint())

        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, widget)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        self.add_btn(index=drop_index)
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

    def onClick(self):
        print self.sender().text()


class Test(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(Test, self).__init__(*args, **kwargs)

        self.list1 = QtWidgets.QListWidget()
        self.list1.setDragDropMode(
            QtWidgets.QAbstractItemView.DragDrop
        )
        self.list1.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.list1.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.list1.setMaximumHeight(60)
        self.list2 = ReorderList()
        self.list2.setMaximumHeight(120)
        for name in ('item 1', 'item 2', 'item 3'):
            item = QtWidgets.QListWidgetItem(name)
            self.list1.addItem(item)
            self.list2.add_btn(text=name)
        self.btn = DragButton(text='Add list button')
        self.btn.clicked.connect(self.list2.add_btn)

        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.layout = QtWidgets.QVBoxLayout(self.widget)
        for item in (self.list1, self.list2, self.btn):
            self.layout.addWidget(item)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Test()
    win.show()
    sys.exit(app.exec_())

我希望在 QListWidget 中插入一个小部件时能够有一个水平条,并且让已经药物的按钮返回到它们的默认颜色。

我可以找到一些类似的链接来帮助我达到这一点:

【问题讨论】:

  • 在 SO 中,我们要求它同时是最小和完整的。您的代码很少但不完整,完整意味着无需添加任何其他内容即可执行它,但您的代码远非如此。不添加的想法是,可能在那部分缺少错误,所以我们看不到它,我们无法告诉您问题出在哪里,请花时间阅读它是 minimal reproducible example 并提供所请求的代码。
  • 太好了,我也会这样做。通过您的修改,您的问题非常清楚:-)

标签: python drag-and-drop qlistwidget pyside2


【解决方案1】:

由于拖放是自定义的,因此您必须进行绘制,为此您必须使用方法 dragMoveEvent 中的 QModelIndex 检测项目的矩形。然后进行绘画,但是由于小部件的大小,它不会被看到所以我看了一下,我创建了一个修改编辑器几何形状的委托。

class Delegate(QtWidgets.QStyledItemDelegate):
    def updateEditorGeometry(self, editor, option, index):
        super(Delegate, self).updateEditorGeometry(editor, option, index)
        geo = editor.geometry().adjusted(0, 4, 0, 0)
        editor.setGeometry(geo)

class ReorderList(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)
        delegate = Delegate(self)
        self.setItemDelegate(delegate)
        self.dropIndicatorRect = QtCore.QRect()
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )

    def add_btn(self, text='', index=-1):
        widget = QtWidgets.QWidget()
        if not text:
            text = 'item {0}'.format(self.count() + 1)
        btn = QtWidgets.QPushButton(text)

        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(btn.sizeHint())

        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, btn)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        self.add_btn(index=drop_index)
        self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragLeaveEvent(self, event):
        self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dragLeaveEvent(event)

    def dragMoveEvent(self, event):
        event.accept()

    def onClick(self):
        print(self.sender().text())

    def dragMoveEvent(self, event):
        index = self.indexAt(event.pos())
        if index.isValid():
            rect = self.visualRect(index)
            if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.OnItem:
                self.dropIndicatorRect = rect
            else:
                self.dropIndicatorRect = QtCore.QRect()
        else:
            self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dragMoveEvent(event)

    def paintEvent(self, event):
        super(ReorderList, self).paintEvent(event)
        if not self.dropIndicatorRect.isNull() and self.showDropIndicator():
            painter = QtGui.QPainter(self.viewport())
            p = QtGui.QPen(painter.pen())
            p.setWidthF(1.5)
            painter.setPen(p)
            r = self.dropIndicatorRect
            painter.drawLine(r.topLeft(), r.topRight())

【讨论】:

  • 啊,谢谢!我花了一些时间研究自定义代表,但并没有深入了解它们。这似乎在光标下的每个项目周围画了一个矩形,这至少给了我某种视觉指示。也许我必须想出一个自定义小部件来插入小部件之间,以获得分隔线?按钮释放后它似乎也离开了盒子,我想我必须用 dropEvent 删除它。
  • @user3161430 实际上它是正常的 QListWidget 也可以,但是油漆会删除它,我想我可以改进它。另一方面,我不明白您评论的最后一部分,请尝试我的更新并告诉我您遇到了什么错误
  • 哦,最后一部分是关于获得一个指示器,比如小部件之间带有深蓝色三角形的蓝色条,红色箭头指向它。在那之后,我只是说光标下的前一个小部件周围的矩形在拖动释放后保持在那里,而我想让它消失,一旦一个新的小部件被放下,所以它们都没有拖放后显示的矩形。
  • @user3161430 我做了一个新的更新,把矩形改了一行,告诉我它是否工作正常或者你有问题。
  • @user3161430 尝试:def mousePressEvent(self, event): QtCore.QTimer.singleShot(100, lambda: self.setDown(False)) super(DragButton, self).mousePressEvent(event)
【解决方案2】:

我不太确定我是否正确理解了您,但请尝试以下示例:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class DragButton(QtWidgets.QPushButton):
    def __init__(self, *args, **kwargs):
        super(DragButton, self).__init__(*args, **kwargs)

    def mouseMoveEvent(self, event):
        if event.buttons() != QtCore.Qt.LeftButton:
            super(DragButton, self).mouseMoveEvent(event)
            return

#        btn_img = QtGui.QPixmap.grabWidget(self)                   # ---
        btn_img = self.grab()                                       # +++

        painter = QtGui.QPainter(btn_img)
        painter.setCompositionMode(
            painter.CompositionMode_DestinationIn
        )
        painter.fillRect(btn_img.rect(), QtGui.QColor(0, 0, 0, 127))
        painter.end()
        data = QtCore.QMimeData()
        data.setText('ReorderListAdd()')
        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.setPixmap(btn_img)
        drag.setHotSpot(event.pos())
        drag.exec_(QtCore.Qt.CopyAction)
        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        data = QtCore.QMimeData()
        data.setText('my text')
        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.exec_()
        super(DragButton, self).mouseReleaseEvent(event)


class ReorderList(QtWidgets.QListWidget):

    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)

        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection
        )

    def add_widget(self, widget=None, index=-1):
        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(widget.sizeHint())
        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, widget)

    def add_btn(self, txt='', index=-1):
        if not txt or not isinstance(txt, basestring):
            btn_name = 'Button Item {:02d}'.format(self.count() + 1)
        else:
            btn_name = txt

#        widget = ReorderListAdd(btn_name)                            # ---
        widget = QtWidgets.QPushButton(btn_name)                      # +++
        widget.setIcon(QtGui.QIcon("Ok.png"))                         # +++
        widget.clicked.connect(self.onClick)                          # +++

        if index < 0:
            self.add_widget(widget)
        else:
            self.add_widget(widget, index)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        if event.mimeData().hasText():
            if event.mimeData().text() == 'ReorderListAdd()':
                self.add_btn(index=drop_index)
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

# +++      
    def onClick(self):                                                 # +++
        print(self.sender().text())


class MainView(QtWidgets.QWidget):               
    def __init__(self):
        super().__init__()

        self.listWidget = ReorderList()
        self.listWidget.addItems(["Item 1 1 1 ", "Item 22 22", "Item 3 33 333", ])

        self.button = DragButton()
        self.button.setText("Button")
        self.button.setIcon(QtGui.QIcon("Ok.png"))

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.listWidget)
        layout.addWidget(self.button)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = MainView()
    w.show()
    sys.exit(app.exec_())          

【讨论】:

  • 谢谢!我已将您的建议添加到我的原始帖子中,并试图澄清我的问题和示例代码。 :)
  • @user3161430 我很高兴我的例子是解决你问题的动力:)
猜你喜欢
  • 2017-05-26
  • 2011-10-15
  • 2014-07-20
  • 2018-04-05
  • 1970-01-01
  • 2013-12-25
  • 2010-10-31
  • 1970-01-01
  • 2018-12-26
相关资源
最近更新 更多