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按钮可以新增标签,新增的标签同样可以进行拖拽排序。