Python 在QgraphicsView中拖放';t工作(PyQt)
我为一些按钮创建了一个自定义类。这些是一个“可拖动”按钮,其名称表明,这些按钮可以相互拖放(取决于是否设置了allowDrag属性),然后执行操作。 这些拖拽按钮的代码已发布在此处: 显然,当按钮在QWidget中时效果很好,但是当它们被添加到QGraphicsView的场景中时(我也制作了一个自定义类),drop事件就不起作用了。我得到了一个Python 在QgraphicsView中拖放';t工作(PyQt),python,qt,drag-and-drop,pyqt,Python,Qt,Drag And Drop,Pyqt,我为一些按钮创建了一个自定义类。这些是一个“可拖动”按钮,其名称表明,这些按钮可以相互拖放(取决于是否设置了allowDrag属性),然后执行操作。 这些拖拽按钮的代码已发布在此处: 显然,当按钮在QWidget中时效果很好,但是当它们被添加到QGraphicsView的场景中时(我也制作了一个自定义类),drop事件就不起作用了。我得到了一个QGraphicsItem::ungrabMouse:而不是一个鼠标抓取器警告 这是自定义图形视图的代码: from PyQt4 import QtGu
QGraphicsItem::ungrabMouse:而不是一个鼠标抓取器
警告
这是自定义图形视图的代码:
from PyQt4 import QtGui, QtCore
class WiringGraphicsView(QtGui.QGraphicsView):
#Initializer method
def __init__(self, parent = None, scene=None):
QtGui.QGraphicsView.__init__(self, scene, parent)
#Set Accept Drops property true
self.setAcceptDrops(True)
#This method creates a line between two widgets
def paintWire(self, start_widget, end_widget):
#Size and Position of both widgets
_start = start_widget.geometry()
_end = end_widget.geometry()
#Creates a Brush object with Red color
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
#Creates Pen object with specified brush
pen = QtGui.QPen(brush, 2)
#Create a Line object between two widgets
line = QtGui.QGraphicsLineItem(_start.x() + _start.width() / 2, _start.y() + _start.height() / 2, _end.x() + _end.width() / 2, _end.y() + _end.height() / 2)
#Set the Pen for the Line
line.setPen(pen)
#Add this line item to the scene.
self.scene().addItem( line )
这里是自定义按钮和图形视图所在的代码:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from dragbutton import DragButton
from wiringgraphicsview import WiringGraphicsView
import icons_rc
app = QApplication([])
scene = QGraphicsScene()
menu = QMenu()
# put a button into the scene and move it
button1 = DragButton('Button 1')
button1.setText("")
button1.setDefault(False)
button1.setAutoDefault(True)
#button1.setMouseTracking(True)
button1.setAllowDrag(True) #Allow Drag n Drop of DragButton
button1.setGeometry(QRect(50, 50, 51, 31)) #Set dimensions of it
#Set icon of button1
icon = QIcon()
icon.addPixmap(QPixmap(":/audio-input-line.png"), QIcon.Normal, QIcon.Off)
button1.setIcon(icon)
button1.setFlat(True)
button1.setMenu(menu)
#Create a QGraphicsProxyWidget adding the widget to scene
scene_button1 = scene.addWidget(button1)
#move the button on the scene
r1 = scene_button1.geometry()
r1.moveTo(-100, -50)
# put another button into the scene
button2 = DragButton('Button 2')
button2.setText("")
#This button shoudn't be dragged, it is just for dropping.
button2.setAllowDrag(False)
button2.setAcceptDrops(True)
icon = QIcon()
icon.addPixmap(QPixmap(":/input_small.png"), QIcon.Normal, QIcon.Off)
button2.setIcon(icon)
#button2.setMouseTracking(True)
#button2.setGeometry(QRect(270, 150, 41, 31))
scene_button2 = scene.addWidget(button2)
scene_button2.setAcceptDrops(True)
r2 = scene_button2.geometry()
# Create the view using the scene
view = WiringGraphicsView(None, scene)
view.resize(300, 200)
view.show()
#and paint a wire between those buttons
view.paintWire(button1, button2)
app.exec_()
另外:如果我想先将按钮嵌入到水平或垂直布局中(使其有序),然后再嵌入到QgraphicsView中,那会怎么样呢
编辑:我已经发现,可以像其他小部件一样,将布局及其子按钮添加到图形场景中。
我仍然不知道为什么我在dragbutton类中实现的drag n drop在QGraphicscene/QgraphicsView中不起作用。
我读过的大多数文档都谈到实现拖放逻辑,但都是在QgraphicsItem类中实现的。
基于QGraphicsItem创建一个新类是一个好主意,但此时我要回答以下问题:
- 我想如何重新实现按钮行为?单击“效果”、“属性”和添加QMenu的可能性?当我使用
将QButton或自定义拖动按钮添加到场景中时,这已经起作用了addWidget
- 布局如何?我无法将QgraphicsItem添加到布局中,然后再将布局添加到场景中!当这些项目位于场景/视图中时,是否有方法将其按顺序排列
from PyQt4 import QtGui, QtCore
class DragButton(QtGui.QPushButton):
def __init__(self, parent):
super(DragButton, self).__init__(parent)
self.allowDrag = True
def setAllowDrag(self, allowDrag):
if type(allowDrag) == bool:
self.allowDrag = allowDrag
else:
raise TypeError("You have to set a boolean type")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
if self.allowDrag == True:
# write the relative cursor position to mime data
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
print mimeData.text()
# let's make it fancy. we'll show a "ghost" of the button as we drag
# grab the button to a pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
# make a QDrag
drag = QtGui.QDrag(self)
# put our MimeData
drag.setMimeData(mimeData)
# set its Pixmap
drag.setPixmap(pixmap)
# shift the Pixmap so that it coincides with the cursor position
drag.setHotSpot(e.pos())
# start the drag operation
# exec_ will return the accepted action from dropEvent
if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
print 'linked'
else:
print 'moved'
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
# get the relative position from the mime data
mime = e.mimeData().text()
x, y = map(int, mime.split(','))
# move
# so move the dragged button (i.e. event.source())
print e.pos()
#e.source().move(e.pos()-QtCore.QPoint(x, y))
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
该解决方案似乎需要子类
qgraphicscene
将放置事件显式传递到放置坐标处的QGraphicsItem
。此外,QGraphicsProxyWidget
似乎不会将放置事件传递给子小部件。同样,您需要子类化QGraphicsProxyWidget
并手动实例化这个类,添加小部件,然后使用scene.addItem()
将实例手动添加到场景中
注意:您可能知道,但是您必须先与小部件交互(例如,单击它),才能开始拖放。大概这可以通过将mouseMoveEvent
从场景传递到代理,然后传递到小部件来解决
注2:我不知道为什么要花这么多的精力才能做到这一点。我确实觉得我可能错过了什么。报告说:
QGraphicsProxyWidget支持QWidget的所有核心功能,包括选项卡焦点、键盘输入、拖放和弹出窗口
但是如果没有子类化,我就无法使它工作
相关子类实现:
class MyScene(QGraphicsScene):
def dragEnterEvent(self, e):
e.acceptProposedAction()
def dropEvent(self, e):
# find item at these coordinates
item = self.itemAt(e.scenePos())
if item.setAcceptDrops == True:
# pass on event to item at the coordinates
try:
item.dropEvent(e)
except RuntimeError:
pass #This will supress a Runtime Error generated when dropping into a widget with no MyProxy
def dragMoveEvent(self, e):
e.acceptProposedAction()
class MyProxy(QGraphicsProxyWidget):
def dragEnterEvent(self, e):
e.acceptProposedAction()
def dropEvent(self, e):
# pass drop event to child widget
return self.widget().dropEvent(e)
def dragMoveEvent(self, e):
e.acceptProposedAction()
修改的应用程序代码:
scene = MyScene()
...
my_proxy = MyProxy()
my_proxy.setWidget(button2)
my_proxy.setAcceptDrops(True)
scene.addItem(my_proxy)
...
完全工作(好吧,当拖放成功时,它会打印出“链接”…这是您之前编写的所有操作)应用程序:
from PyQt4 import QtGui, QtCore
class WiringGraphicsView(QtGui.QGraphicsView):
#Initializer method
def __init__(self, parent = None, scene=None):
QtGui.QGraphicsView.__init__(self, scene, parent)
#Set Accept Drops property true
self.setAcceptDrops(True)
#This method creates a line between two widgets
def paintWire(self, start_widget, end_widget):
#Size and Position of both widgets
_start = start_widget.geometry()
_end = end_widget.geometry()
#Creates a Brush object with Red color
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
#Creates Pen object with specified brush
pen = QtGui.QPen(brush, 2)
#Create a Line object between two widgets
line = QtGui.QGraphicsLineItem(_start.x() + _start.width() / 2, _start.y() + _start.height() / 2, _end.x() + _end.width() / 2, _end.y() + _end.height() / 2)
#Set the Pen for the Line
line.setPen(pen)
#Add this line item to the scene.
self.scene().addItem( line )
class DragButton(QtGui.QPushButton):
def __init__(self, parent):
super(DragButton, self).__init__(parent)
self.allowDrag = True
def setAllowDrag(self, allowDrag):
if type(allowDrag) == bool:
self.allowDrag = allowDrag
else:
raise TypeError("You have to set a boolean type")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return QtGui.QPushButton.mouseMoveEvent(self, e)
if self.allowDrag == True:
# write the relative cursor position to mime data
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
# print mimeData.text()
# let's make it fancy. we'll show a "ghost" of the button as we drag
# grab the button to a pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
# make a QDrag
drag = QtGui.QDrag(self)
# put our MimeData
drag.setMimeData(mimeData)
# set its Pixmap
drag.setPixmap(pixmap)
# shift the Pixmap so that it coincides with the cursor position
drag.setHotSpot(e.pos())
# start the drag operation
# exec_ will return the accepted action from dropEvent
if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
print 'linked'
else:
print 'moved'
return QtGui.QPushButton.mouseMoveEvent(self, e)
def mousePressEvent(self, e):
if e.button() == QtCore.Qt.LeftButton:
print 'press'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
return QtGui.QPushButton.mousePressEvent(self, e)
def dragEnterEvent(self, e):
e.accept()
return QtGui.QPushButton.dragEnterEvent(self, e)
def dropEvent(self, e):
# get the relative position from the mime data
mime = e.mimeData().text()
x, y = map(int, mime.split(','))
# move
# so move the dragged button (i.e. event.source())
print e.pos()
# e.source().move(e.pos()-QtCore.QPoint(x, y))
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
return QtGui.QPushButton.dropEvent(self, QDropEvent(QPoint(e.pos().x(), e.pos().y()), e.possibleActions(), e.mimeData(), e.buttons(), e.modifiers()))
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MyScene(QGraphicsScene):
def dragEnterEvent(self, e):
e.acceptProposedAction()
def dropEvent(self, e):
# find item at these coordinates
item = self.itemAt(e.scenePos())
if item.setAcceptDrops == True:
# pass on event to item at the coordinates
try:
item.dropEvent(e)
except RuntimeError:
pass #This will supress a Runtime Error generated when dropping into a widget with no ProxyWidget
def dragMoveEvent(self, e):
e.acceptProposedAction()
class MyProxy(QGraphicsProxyWidget):
def dragEnterEvent(self, e):
e.acceptProposedAction()
def dropEvent(self, e):
# pass drop event to child widget
return self.widget().dropEvent(e)
def dragMoveEvent(self, e):
e.acceptProposedAction()
app = QApplication([])
scene = MyScene()
menu = QMenu()
# put a button into the scene and move it
button1 = DragButton('Button 1')
button1.setText("aaa")
button1.setDefault(False)
button1.setAutoDefault(True)
#button1.setMouseTracking(True)
button1.setAllowDrag(True) #Allow Drag n Drop of DragButton
button1.setGeometry(QRect(50, 50, 51, 31)) #Set dimensions of it
#Set icon of button1
icon = QIcon()
icon.addPixmap(QPixmap(":/audio-input-line.png"), QIcon.Normal, QIcon.Off)
button1.setIcon(icon)
button1.setFlat(True)
button1.setMenu(menu)
#Create a QGraphicsProxyWidget adding the widget to scene
scene_button1 = scene.addWidget(button1)
#move the button on the scene
r1 = scene_button1.geometry()
r1.moveTo(-100, -50)
# put another button into the scene
button2 = DragButton('Button 2')
button2.setText("bbb")
#This button shoudn't be dragged, it is just for dropping.
button2.setAllowDrag(False)
button2.setAcceptDrops(True)
icon = QIcon()
icon.addPixmap(QPixmap(":/input_small.png"), QIcon.Normal, QIcon.Off)
button2.setIcon(icon)
#button2.setMouseTracking(True)
#button2.setGeometry(QRect(270, 150, 41, 31))
# Instantiate our own proxy which forwars drag/drop events to the child widget
my_proxy = MyProxy()
my_proxy.setWidget(button2)
my_proxy.setAcceptDrops(True)
scene.addItem(my_proxy)
# Create the view using the scene
view = WiringGraphicsView(None, scene)
view.resize(300, 200)
view.show()
#and paint a wire between those buttons
view.paintWire(button1, button2)
app.exec_()
我试图从所有这些代码片段中拼凑出一个示例,但我无法重现您看到的错误。我不能移动按钮,但它会在终端上打印“移动”和坐标。它不会打印您引用的错误。我不太确定你期望发生什么。您当前的代码中没有任何一个对移动QGraphicscene中的按钮有任何作用…@three_Pinepples至少它应该打印“linked”,并且光标应该在即将放下时改变。当它们在场景中时不会发生。你看到上面按钮代码的链接了吗?又来了:@three_Pinepples我为按钮添加了代码。正如我所说,当它们在布局或小部件中时,它们会按预期工作,但一旦它们被添加到GraphicsCenter中,显然就不起作用。您可以发布/链接到一个在qwidget模式下使用拖动按钮而不使用qgraphicsview/场景的可运行示例吗?这样我就能更好地理解它是如何工作的。@three_Pinepples在这里:我还读了一些关于QGraphicsProxyWidget的引文,这就是为什么我如此困惑的原因,因为我认为只要用
addWidget()
将按钮添加到场景中就足以获得所有拖放行为。至少你的解决方案像预期的那样有效,谢谢。是的,我同意。我也会这么想。也许其他人会知道为什么…我在玩你的一些代码,我发现我可以像往常一样使用addWidget()将带有布局和子按钮的QWidget添加到场景中,这很好,因为布局允许我对齐按钮,但这只会将它们带到场景中。正如我们所看到的,drag n drop不起作用,至少我为每个接收drop的按钮实例化了一个代理。所以我做了并且工作了。现在唯一困扰我的是一个异常异常“unhandled RuntimeError”无法访问非Python创建的对象的受保护函数或信号,每次我放入包含按钮的QWidget(但不是放在按钮上)时都会引发该异常。哦,我发现,每当我进入一个没有自己代理的对象时,这个值就会升高。