拖动内置QgraphicsView中的Drop不起作用(PyQt) [英] Drag n Drop inside QgraphicsView doesn't work (PyQt)
问题描述
我为某些按钮创建了一个自定义类。那些是名称所示的可拖动按钮,是可以拖放到其中的按钮(根据是否设置了allowDrag属性),然后进行操作。
这些拖放代码已经在这里发布:
拖动下拉按钮和下拉菜单PyQt / Qt设计器
I created a custom class for some buttons. Those are a "draggable" buttons, which its name indicates, are buttons that you can drag and drop into each other (depending if is allowDrag property is set) and then make an action. The code of those dragbuttons is already posted here: Drag n Drop Button and Drop-down menu PyQt/Qt designer
显然,当它们处于QWidget,但是当它们被添加到一个QGraphicsView的场景中(我也做了一个自定义的类)它的drop事件不起作用。我得到一个 QGraphicsItem :: ungrabMouse:不是鼠标抓取器
警告。
Apparently the buttons work well when they are in a QWidget, but when they are added into a scene of a QGraphicsView (I also made a custom class of it) the drop event doesn't work. I get a QGraphicsItem::ungrabMouse: not a mouse grabber
warning instead.
这是自定义的代码从PyQt4导入QtGui,QtCore
class WiringGraphicsView(QtGui.QGraphicsView)
$ $ b #Initializer方法
def __init __(self,parent = None,scene = None)
QtGui.QGraphicsView .__ init __(self,scene,parent)
#Set Accept Drops属性true
self.setAcceptDrops(True)
#This方法创建两个小部件之间的一行
def paintWire(self,start_widget,end_widget):
#Size和小部件
_start = start_widget.geometry()
_end = end_widget.geometry()
#创建一个具有红色颜色的Brush对象
brush = QtGui.QBrush(QtGui.QColor(255 ,0,0))
#创建具有指定画笔的Pen对象
pen = QtGui.QPen(brush,2)
#创建一个线对象n两个小部件
line = QtGui.QGraphicsLineItem(_start.x()+ _start.width()/ 2,_start.y()+ _start.height()/ 2,_end.x()+ _end.width ()/ 2,_end.y()+ _end.height()/ 2)
#设置笔的行
line.setPen(pen)
#将此行项目添加到现场。
self.scene()。addItem(line)
This is the code for the custom GraphicsView:
按钮和graphicsView是:
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 )
And here the code where the custom buttons and the graphicsView are:
加:如果我想嵌入按钮首先进入水平或垂直布局(让它们顺序排列)然后进入QgraphicsView,是否可能?
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_()
编辑:我已经想到可以添加布局他们的孩子按钮成为任何其他小部件的图形。
我还不知道为什么我的dragbutton类中的拖动实现在QgraphicSecurity / QgraphicsView内部不起作用。
我阅读的大多数文档谈到要实现拖放逻辑,但是在一个QgraphicsItem类中。
根据QGraphicsItem创建一个新的类是一个好主意,但是在这一点上我要做以下几个问题:
Plus: what about if I want to embed buttons into a horizontal or vertical layout first (to have them in order) and then into a QgraphicsView, is that possible?
- 我想如何重新实现按钮行为?点击效果,属性和添加QMenu的可能性?当我使用
addWidget
将QButton或我的自定义DragButton添加到场景中时,这可以工作。 - 布局如何?我不能将QgraphicsItem添加到布局中,然后将布局添加到场景!在场景/视图中有没有办法让这些项目顺序排列?
I already figured out that you can add layouts with their child buttons into a graphicscene as any other widget. I still don't know why my drag n drop implemented in my dragbutton class is not working when is inside of a Qgraphicsscene/QgraphicsView. Most of the documentation that I read talks about to implement drag n drop logic but in a QgraphicsItem class. It would be a good idea to create a new class based on QGraphicsItem, but at this point makes me to do the following questions:
编辑2:我包含了DragButton类发布在我的其他帖子中,因为与此问题有关。
- How I suppose to re-implement the button behaviour? Click effects, properties, and the possibility to add a QMenu? This already works when I use
addWidget
to add a QButton or my custom DragButton into a scene. - What about the layouts? I can't add QgraphicsItem into a layout and then add the layout to the scene! Is there way to have those items in order when they are in a scene/view?
EDIT 2: I included the code of the "DragButton" class posted in my other post, since is relevant to this question.
p $ p>从PyQt4导入QtGui,QtCore
class DragButton(QtGui.QPushButton):
def __init __(self,parent):
super(DragButton,self).__ init __(parent)
self。 allowDrag = True
def setAllowDrag(self,allowDrag):
如果类型(allowDrag)== bool:
self.allowDrag = allowDrag
else:
raise TypeError(你必须设置一个布尔类型)
def mouseMoveEvent(self,e):
如果e.buttons()!= QtCore.Qt.RightButton:
return
如果self.allowDrag == True:
#将相对光标位置写入mime数据
mimeData = QtCore.QMimeData()
#简单字符串与'x,y'
mimeData.setText('%d,%d'%(ex() ,e.y()))
print mimeData.text()
#让我们来看看。我们将显示一个ghost的按钮,我们拖动
#抓住按钮到pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
#below pixmap半透明
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)
#把我们的MimeData
drag.setMimeData(mimeData)
#设置其Pixmap
drag.setPixmap(pixmap)
#移动Pixmap,使其与光标位置一致
drag.setHotSpot(e。 pos())
#启动拖动操作
#exec_将从dropEvent
返回接受的操作,如果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)
如果e .button()== QtCore.Qt.LeftButton:
print'按'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
def dragEnterEvent(self,e):
e.accept()
def dropEvent(self,e):
#从mime数据获取相对位置
mime = e.mimeData()。text()
x,y = map(int,mime.split(','))
#move
#所以移动拖动的按钮event.source())
print e.pos()
#e.source()。move(e.pos() - QtCore.QPoint(x,y))
#set drop action作为LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
#告诉我们接受的QDrag
e.accept()
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()
推荐答案
解决方案似乎要求您的子类 QGraphicsScene
将drop事件显式传递到下拉坐标处的 QGraphicsItem
。此外, QGraphicsProxyWidget
似乎没有将删除事件传递到子窗口小部件。所以再次,你需要子类 QGraphicsProxyWidget
并手动实例化这个类,添加小部件和母鸡手动添加实例到场景使用 scene.addItem(
。
The solution appears to require that you subclass QGraphicsScene
to explicitly pass the drop events to the QGraphicsItem
at the drop coordinates. Further more, QGraphicsProxyWidget
does not appear to pass drop events to the child widget. So again, you need to subclass QGraphicsProxyWidget
and manually instantiate this class, add the widget and hen manually add the instance to the scene using scene.addItem()
.
注意:你可能知道,但是拖放不是开始的,除非你先与widget进行交互例如点击它)。大概这可以通过从场景中通过 mouseMoveEvent
到代理,然后到窗口小部件来修复。
Note: You are probably aware, but you the drag/drop isn't started unless you have first interacted with the widget (e.g. clicked on it). Presumably this could be fixed by also passing through the mouseMoveEvent
from the scene to the proxy and then to the widget.
注2:我不知道为什么要花这么多精力来做这个工作。我觉得我可能会失踪的东西。 文档说:
Note 2: I don't know why it takes so much effort to make this work. I do feel like I may be missing something. The documentation says:
QGraphicsProxyWidget支持QWidget的所有核心功能,包括标签焦点,键盘输入,拖放&删除和弹出窗口
QGraphicsProxyWidget supports all core features of QWidget, including tab focus, keyboard input, Drag & Drop, and popups
但是,如果没有子类化,我无法使其工作。
but I couldn't make it work without subclassing.
相关的子类实现:
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()
修改后的应用程序代码:
Modified application code:
scene = MyScene()
...
my_proxy = MyProxy()
my_proxy.setWidget(button2)
my_proxy.setAcceptDrops(True)
scene.addItem(my_proxy)
...
全工作(好吧,它打印出链接,当拖动成功...这是你以前写过的所有内容)应用程序:
Full working (well, it prints out "linked" when the drag drop succeeds...which is all you had written it to do previously) application:
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_()
这篇关于拖动内置QgraphicsView中的Drop不起作用(PyQt)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!