PyQt中设置拖拽组件并自动重新排序

PyQt中设置拖拽组件并自动重新排序

在开发测试软件的过程中,已经拥有固定layout时,需要允许用户通过拖动组件进行自定义排序,但是不能将一个widget的组件拖动到另一个widget中。

参考代码:

from PyQt5.QtCore import QMimeData, Qt, pyqtSignal
from PyQt5.QtGui import QDrag, QPixmap
from PyQt5.QtWidgets import *

class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setStyleSheet(
            "QLabel { background-color: rgb(235, 212, 82); border: 1px solid black; }"
        )


class ActionLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setAlignment(Qt.AlignmentFlag.AlignLeft)
        self.data = self.text()
        
        self.setObjectName('Action')
        self.setMouseTracking(True)
        self.setStyleSheet("QLabel:hover { background-color: #6EB5FF; }")
        self.setToolTip('Double Click to Edit')

        # create right click menu
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            step = QDrag(self)
            mime = QMimeData()
            step.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            step.setPixmap(pixmap)

            step.exec_(Qt.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.


class Actions_Widget(QWidget):
    """
    通用的列表排序处理程序。
    """

    orderChanged = pyqtSignal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # 存储方向以供以后拖拽检查使用。
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.layout = QVBoxLayout()
        else:
            self.layout = QHBoxLayout()

        # 添加拖拽目标指示器。默认情况下,这是不可见的,
        # 我们在拖动期间显示它并移动它。
        self.drag_target_indicator = DragTargetIndicator()
        self.layout.addWidget(self.drag_target_indicator)
        self.drag_target_indicator.hide()

        self.setLayout(self.layout)

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

    def dragLeaveEvent(self, event):
        self.drag_target_indicator.hide()
        event.accept()
        
        
    def dragMoveEvent(self, event):
        # 找到拖放目标的正确位置,以便我们可以将其移动
        moved_widget = event.source()
        if self.isAncestorOf(moved_widget):
        # if self.rect().contains(event.pos()):  # 检查拖动事件的位置是否在当前区域内
            index = self._find_drop_location(event)
            if index is not None:
                # 插入目标移动项目(如果已经在布局中)。
                self.layout.insertWidget(index, self.drag_target_indicator)
                # 隐藏被拖动的项目。
                event.source().hide()
                # 显示目标。
                self.drag_target_indicator.show()
                event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        moved_widget = event.source()
        # 使用拖放目标位置作为目的地,然后移除它。
        self.drag_target_indicator.hide()
        index = self.layout.indexOf(self.drag_target_indicator)
        if index is not None:
            self.layout.insertWidget(index, moved_widget)
            self.orderChanged.emit(self.get_item_data())
            moved_widget.show()
            self.layout.activate()
        event.accept()

    def _find_drop_location(self, event):
        pos = event.pos()
        spacing = self.layout.spacing() / 2

        for n in range(self.layout.count()):
            # 依次获取每个索引处的小部件。
            w = self.layout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # 垂直拖放。
                drop_here = (
                    pos.y() >= w.y() - spacing
                    and pos.y() <= w.y() + w.size().height() + spacing
                )
            else:
                # 水平拖放。
                drop_here = (
                    pos.x() >= w.x() - spacing
                    and pos.x() <= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # 在此目标上放置。
                break

        return n

    def add_item(self, item):
        self.layout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.layout.count()):
            # 依次获取每个索引处的小部件。
            w = self.layout.itemAt(n).widget()
            if hasattr(w, "data"):
                # 拖放目标没有数据。
                data.append(w.data)
        return data



class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.add_btn = QPushButton('Add')
        
        step_1 = Actions_Widget(orientation=Qt.Orientation.Vertical)
        # step_1.add_item(QLabel('Step 1'))
        # step_1.add_item(QLabel('Step Description'))
        for index, value in enumerate(["A", "B", "C", "D"]):
            item = ActionLabel(value)
            step_1.add_item(item)

        # Print out the changed order.
        step_1.orderChanged.connect(print)
        
        step_2 = Actions_Widget(orientation=Qt.Orientation.Vertical)
        for index, value in enumerate(["JJ", "KK", "LL"]):
            item = ActionLabel(value)
            step_2.add_item(item)

        # Print out the changed order.
        step_2.orderChanged.connect(print)
        
        self.add_btn.clicked.connect(lambda event,  step=step_1: self.add_action(event, step))
        
        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(QLabel('Step 1'))
        layout.addWidget(QLabel('Step Description'))
        layout.addWidget(step_1)
        layout.addWidget(step_2)
        layout.addStretch(1)
        layout.addWidget(self.add_btn)
        container.setLayout(layout)

        self.setCentralWidget(container)

    
    def add_action(self, event, step):
        index = step.layout.count()+1
        item = ActionLabel('E')
        step.add_item(item)
if __name__ == '__main__':
    app = QApplication([])
    w = MainWindow()
    w.show()

    app.exec_()

在这里插入图片描述
设置了两个步骤,步骤中的每个标签都可以在对应的步骤内部进行拖拽排序,其排序类型为在指定位置插入拖动的标签,在插入位置以下的标签顺序下移
点击Add按钮可以新增标签,新增的标签同样可以进行拖拽排序。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值