拖动内置QgraphicsView中的Drop不起作用(PyQt) [英] Drag n Drop inside QgraphicsView doesn't work (PyQt)

查看:1332
本文介绍了拖动内置QgraphicsView中的Drop不起作用(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 useaddWidget 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆