问题描述
我正在使用pyside2构建非常复杂的GUI,并且必须为QTreeView小部件实现拖放系统(内部和外部移动均应接受)。
目标
能够将项目从QTreeView
小部件(文件浏览器)复制到另一个QTreeView
小部件(“测试”小部件)并在此“测试”小部件中移动项目。应检查每个拖动的文件,以使用户了解他是否可以移动该文件以及应将其放置在何处。
同样,当带有拖动元素的鼠标将其悬停时,“测试”小部件的各项也应突出显示(一个接一个)。 (实际上,我想在项目之间划一条界线,但我暂时无法这样做:欢迎提出任何建议。)
问题
外部移动效果很好,而内部移动则无法正常工作:在拖放操作期间,即使应该接受该移动,也始终显示“停止”图标。由于放置操作成功完成,因此实际上已被接受。
悬停项目的突出显示也不起作用。
我认为问题是由Tests小部件的mouseMoveEvent()
方法的重写所引起的,为了设置QMimeData
对象,我不得不实施该方法。
请注意,出于隐私原因,代码的某些部分已由“ [...]”替换。无论如何,这些部分对于系统的功能并不重要。
class MyStandardItem(QStandardItem):
def __init__(self,text,icon_path='',value='',num=0,font_size=8,set_bold=False):
super().__init__()
self.setDragEnabled(True)
self.setDropEnabled(True)
self.num = num
self.value = value
self.setText(text)
font = QFont('Segoe UI',font_size)
font.setBold(set_bold)
self.setFont(font)
self.setIcon(QIcon(icon_path))
def setCheckState(self,checkState):
super().setCheckState(checkState)
if checkState == Qt.Unchecked:
self.setForeground(QColor(150,150,150))
def get_data(self):
return self.text(),self.value,self.num
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.start_drag_pos = None
self.model = QStandardItemmodel()
self.root = self.model.invisibleRootItem()
self.setModel(self.model)
def mousepressEvent(self,event: QtGui.QMouseEvent):
if event.button() == Qt.LeftButton:
self.start_drag_pos = event.pos()
super().mousepressEvent(event)
def mouseMoveEvent(self,event: QtGui.QMouseEvent):
super().mouseMoveEvent(event)
if not event.buttons() == Qt.LeftButton:
return
if (event.pos() - self.start_drag_pos).manhattanLength() < QApplication.startDragdistance():
return
index = self.indexAt(self.start_drag_pos)
item = self.model.itemFromIndex(index)
if item:
drag = QDrag(self)
mime_data = QMimeData()
mime_data.setText(str(item.get_data()))
drag.setMimeData(mime_data)
drag.exec_(Qt.MoveAction)
def dragenterEvent(self,event: QdragenterEvent):
self.selectionModel().clear()
if event.mimeData().hasText():
mime_text = event.mimeData().text()
if event.source() == self:
mime_tuple = eval(mime_text)
if [...]:
event.acceptProposedAction()
else:
path = Path(mime_text)
accepted_extensions = ['.txt']
if path.suffix in accepted_extensions:
event.acceptProposedAction()
def dragMoveEvent(self,event: QDragMoveEvent):
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if self.hover_item is not item:
self.hover_item = item
if self.hover_item is not None:
self.selectionModel().clear()
self.selectionModel().select(item.index(),QItemSelectionModel.Rows | QItemSelectionModel.Select)
super().dragMoveEvent(event)
if event.source() == self:
item_data = eval(event.mimeData().text())
if item is not None:
if [...]:
event.acceptProposedAction()
else:
if [...]:
event.acceptProposedAction()
else:
path = event.mimeData().text().replace('file:///','')
if item is not None:
if [...]:
if [...] in item.text():
event.acceptProposedAction()
else:
if [...]:
if [...] in item.value:
event.acceptProposedAction()
else:
if [...] in item.value:
event.acceptProposedAction()
def dropEvent(self,event: QDropEvent):
self.hover_item = None
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
over_dropped_item = self.model.itemFromIndex(index)
dropped = event.mimeData().text().replace('file:///','')
print('Moved item: ',dropped)
print('Moved over: ',over_dropped_item.get_data())
[...]
event.acceptProposedAction()
解决方法
考虑了很多之后,我决定不重写mouseMoveEvent()
方法,这解决了问题。
我认为mouseMoveEvent()
和dragMoveEvent()
之间存在冲突。实际上,dragMoveEvent()
仅被调用一次(在拖动运动中它应被多次调用)并且在dropEvent()
之前被调用。我认为,问题是由于QDrag
对象是在鼠标移动过程中连续创建的,因此dragMoveEvent()
直到被放下才被调用,但是为时已晚。
没有自定义QDrop
对象-在mouseMoveEvent()
中创建的先例-我必须找到另一种方法来保存拖动的QStandardItem
,然后再使用它。解决方案非常简单:调用dragEnterEvent()
时,将所选项目存储到class属性中:
def dragEnterEvent(self,event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
此外,由于有了本机的拖放功能,我不再需要选择要悬停的项目,因为该功能是内置的,行之间的黑线也是如此。原生拖放仅适用于内部移动,仅适用于QStandardItem
项,因此我删除了MyStandardItem
类,而使用了QStandardItem
。
代码
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.dragged_item = None
self.dragged_index = None
def dragEnterEvent(self,event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
if [...] not in self.dragged_item.text():
super().dragEnterEvent(event)
else:
[...]
def dragMoveEvent(self,event: QDragMoveEvent):
super().dragMoveEvent(event)
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if event.source() == self:
if item is not None:
if [...] in self.dragged_item.data()[0]:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
[...]
def dropEvent(self,event: QDropEvent):
super().dropEvent(event)
[...]