Drag and drop widgets onto a MS word-like a4 page
-
An update. So I have a graphicsview and graphicsscene.
The peculiar part is that I cannot drop into the scene, only outside of it in the view. It does get added to the scene when dropping.
How to resolve this issue, and basically inverse this behavior (not droppable outside of scene, droppable inside it).?
Code below:
import math from PyQt6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QGridLayout, QScrollArea, QGraphicsGridLayout, QGraphicsView, QGraphicsScene from PyQt6 import QtGui, QtCore from PyQt6.QtCore import Qt, QMimeData from PyQt6.QtGui import QDrag, QPixmap class CustomGraphicsScene(QGraphicsScene): def __init__(self, parent=None): super(QGraphicsScene, self).__init__(parent) # def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None: # event.accept() # # def dropEvent(self, event: QtGui.QDropEvent) -> None: # widget = event.source() # Know which widget is triggering # print(f"Widget:\t{widget}") # # event.setDropAction(Qt.DropAction.MoveAction) # # button = QPushButton("xyz") # # # widget.setParent(self.graphics_view) # # self.addWidget(button) # event.accept() class DragButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None: # Not sure if the conditional here is needed (yet) if event.buttons() == QtCore.Qt.MouseButton.LeftButton: # Must use buttons(), not button() drag = QDrag(self) mime_data = QMimeData() drag.setMimeData(mime_data) drag.setHotSpot(event.pos()) # Code to show what is being dragged pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction) # Event loop blocking the main loop until complete class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.main_layout = QVBoxLayout() self.button_layout = QHBoxLayout() self.graphics_scene = CustomGraphicsScene(parent=self) # Make a graphics view and add it to the main_layout as the 2nd widget self.graphics_view = QGraphicsView() self.graphics_view.setScene(self.graphics_scene) self.graphics_view.setAcceptDrops(True) page_size = 300 self.graphics_view.setFixedSize(page_size, int(page_size*(math.sqrt(2)))) # We want something like a ScrollArea?? self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.graphics_view) for button in ["A", "B", "C", "D"]: _button = DragButton(button) self.button_layout.addWidget(_button) self.main_layout.addLayout(self.button_layout) self.main_layout.addWidget(self.scrollArea) self.setLayout(self.main_layout) # Test self.column = 3 self.row = 3 def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None: event.accept() def dropEvent(self, event: QtGui.QDropEvent) -> None: widget = event.source() # Know which widget is triggering print(f"Widget:\t{widget}") position = event.position() event.setDropAction(Qt.DropAction.MoveAction) button = QPushButton("xyz") self.graphics_scene.addWidget(button) event.accept() app = QApplication([]) w = Window() w.show() app.exec()
Hi and welcome to devnet,
You need to implement the dragMoveEvent as well.
-
@SGaist To me it seems like it's because the scene cannot accept drops.
How would the dragMoveEvent suddenly make me be able to drop into the scene, when without it I can drop anywhere but the scene, so long as I set setAcceptDrops to True?
-
@SGaist To me it seems like it's because the scene cannot accept drops.
How would the dragMoveEvent suddenly make me be able to drop into the scene, when without it I can drop anywhere but the scene, so long as I set setAcceptDrops to True?
If you want custom drag and drop you must do it at the view level. The scene handles scene related drag and drop.
dragEnterEvent enables the handling of DnD, dragMoveEvent, handles where it should happen and then dropEvent for the actual drop.
-
If you want custom drag and drop you must do it at the view level. The scene handles scene related drag and drop.
dragEnterEvent enables the handling of DnD, dragMoveEvent, handles where it should happen and then dropEvent for the actual drop.
@SGaist Got it to work. Thanks a lot!
-
@SGaist Got it to work. Thanks a lot!
@MightyDigits you're welcome !
Since you have it working now, please mark the thread as solved using the "Topic Tools" button or the three doted menu beside the answer you deem correct so other forum members may know a solution has been found :-)
-
-
@MightyDigits you're welcome !
Since you have it working now, please mark the thread as solved using the "Topic Tools" button or the three doted menu beside the answer you deem correct so other forum members may know a solution has been found :-)
@SGaist Share the code as well?
-
@SGaist Share the code as well?
@MightyDigits that would be nice as well ! It might help other people having the same issue :-)
-
@MightyDigits that would be nice as well ! It might help other people having the same issue :-)
Sure, below is the code.
Still having trouble with dropping the buttons at the right place. I have experience mapping ROI's from pyqtgraph into views, but PyQt is different. Tried all mapping options and followed some examples, but nothing is giving me what I want. Some of these little functionalities takes hours and hours without success...
The coordinates of the view/scene and buttons do not align, so how to achieve that?
import math from PyQt6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QGridLayout, QScrollArea, QGraphicsGridLayout, QGraphicsView, QGraphicsScene from PyQt6 import QtGui, QtCore from PyQt6.QtCore import Qt, QMimeData from PyQt6.QtGui import QDrag, QPixmap class CustomGraphicsView(QGraphicsView): def __init__(self, parent=None): super(QGraphicsView, self).__init__(parent) self.setAcceptDrops(True) def dragMoveEvent(self, event: QtGui.QDragMoveEvent) -> None: if event.mimeData().hasImage(): event.acceptProposedAction() def dropEvent(self, event): print("In dropEvent of VIEW") if self.scene(): event.accept() button = DragButton("XYZ") point = button.mapFromGlobal(event.position()) try: button.move(point) self.scene().addWidget(button) print("Button moved") except Exception as e: print(e) event.acceptProposedAction() class CustomGraphicsScene(QGraphicsScene): def __init__(self, parent=None): super(QGraphicsScene, self).__init__(parent) class DragButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None: print("In mousemovevent of dragbutton") drag = QDrag(self) mime_data = QMimeData() mime_data.setText(f"{event.pos().x()}, {event.pos().y()}") drag.setMimeData(mime_data) drag.setHotSpot(event.pos()) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction) # Event loop blocking the main loop until complete class Window(QWidget): def __init__(self): super().__init__() self.setFixedSize(960, 540) self.main_layout = QVBoxLayout() self.button_layout = QHBoxLayout() self.graphics_scene = CustomGraphicsScene(parent=self) self.graphics_view = CustomGraphicsView(self) self.graphics_view.setScene(self.graphics_scene) page_size = 300 self.graphics_view.setFixedSize(page_size, int(page_size*(math.sqrt(2)))) self.scrollArea = QScrollArea(self) self.scrollArea.setAcceptDrops(True) self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.graphics_view) for button in ["A", "B", "C", "D"]: _button = DragButton(button) self.button_layout.addWidget(_button) self.main_layout.addLayout(self.button_layout) self.main_layout.addWidget(self.scrollArea) self.setLayout(self.main_layout) app = QApplication([]) w = Window() w.show() app.exec()
-
Sure, below is the code.
Still having trouble with dropping the buttons at the right place. I have experience mapping ROI's from pyqtgraph into views, but PyQt is different. Tried all mapping options and followed some examples, but nothing is giving me what I want. Some of these little functionalities takes hours and hours without success...
The coordinates of the view/scene and buttons do not align, so how to achieve that?
import math from PyQt6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QGridLayout, QScrollArea, QGraphicsGridLayout, QGraphicsView, QGraphicsScene from PyQt6 import QtGui, QtCore from PyQt6.QtCore import Qt, QMimeData from PyQt6.QtGui import QDrag, QPixmap class CustomGraphicsView(QGraphicsView): def __init__(self, parent=None): super(QGraphicsView, self).__init__(parent) self.setAcceptDrops(True) def dragMoveEvent(self, event: QtGui.QDragMoveEvent) -> None: if event.mimeData().hasImage(): event.acceptProposedAction() def dropEvent(self, event): print("In dropEvent of VIEW") if self.scene(): event.accept() button = DragButton("XYZ") point = button.mapFromGlobal(event.position()) try: button.move(point) self.scene().addWidget(button) print("Button moved") except Exception as e: print(e) event.acceptProposedAction() class CustomGraphicsScene(QGraphicsScene): def __init__(self, parent=None): super(QGraphicsScene, self).__init__(parent) class DragButton(QPushButton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None: print("In mousemovevent of dragbutton") drag = QDrag(self) mime_data = QMimeData() mime_data.setText(f"{event.pos().x()}, {event.pos().y()}") drag.setMimeData(mime_data) drag.setHotSpot(event.pos()) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction) # Event loop blocking the main loop until complete class Window(QWidget): def __init__(self): super().__init__() self.setFixedSize(960, 540) self.main_layout = QVBoxLayout() self.button_layout = QHBoxLayout() self.graphics_scene = CustomGraphicsScene(parent=self) self.graphics_view = CustomGraphicsView(self) self.graphics_view.setScene(self.graphics_scene) page_size = 300 self.graphics_view.setFixedSize(page_size, int(page_size*(math.sqrt(2)))) self.scrollArea = QScrollArea(self) self.scrollArea.setAcceptDrops(True) self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.graphics_view) for button in ["A", "B", "C", "D"]: _button = DragButton(button) self.button_layout.addWidget(_button) self.main_layout.addLayout(self.button_layout) self.main_layout.addWidget(self.scrollArea) self.setLayout(self.main_layout) app = QApplication([]) w = Window() w.show() app.exec()
From the looks of it, you did not mapToScene.
-
From the looks of it, you did not mapToScene.
@SGaist First thing I tried was mapToScene(), but that didn't work.
What worked, is defining the sceneRect for the scene. Without it, there apparently are no native coordinates and they get set on the fly with each button drop, with the first drop being dead center in the scene.
All I had to do is add the following line:
self.graphics_scene = CustomGraphicsScene(parent=self) # ADDED THE FOLLOWING LINE: self.graphics_scene.setSceneRect(0, 0, page_size, int(page_size*math.sqrt(2))) self.graphics_view = CustomGraphicsView(self)
There's still some polishing like scrollarea showing the scrollbars, basically just not slightly showing the full rect. If I increase the size of the scrollarea, it doesn't solve it. But that stuff is for worries later on.
Thanks anyway, you're a great help!