Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Drag and drop widgets onto a MS word-like a4 page
Forum Updated to NodeBB v4.3 + New Features

Drag and drop widgets onto a MS word-like a4 page

Scheduled Pinned Locked Moved Solved Qt for Python
12 Posts 2 Posters 1.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M MightyDigits

    Hi,

    I'm developing a plotting app, and for reporting purposes, I would like to have the functionality to drag chart types onto a page that looks like an MS Word-like page.

    I would not want the page to be a text edit widget, but rather some sort of grid so that the drops snap in place at some given resolution. Any text should come from a draggable text edit widget added to the page.

    I know how to implement drag/drop part, the widgets, the rendering, and all.
    But I have no clue about how to make an MS Word-like page with A4 proportions, and how to drop widgets at snapping points. I think all the other rearrangement logic etc. will fall into place once I have the page set up.

    I tried some experiments with a QGridLayout, but obviously, that's the wrong choice because I don't think it's wise to predefine sizes of layouts if it's even possible. I think something with a graphics scene maybe?

    Here's some code to at least get a gist of the way I would like to drag widgets in another field:

    from PyQt6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QGridLayout, QScrollArea
    from PyQt6 import QtGui, QtCore
    from PyQt6.QtCore import Qt, QMimeData
    from PyQt6.QtGui import QDrag, QPixmap
    
    
    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:    
                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)     
    
    
    class Window(QWidget):
    
        def __init__(self):
            super().__init__()
            self.setAcceptDrops(True)
    
            self.main_layout = QVBoxLayout()
            self.button_layout = QHBoxLayout()
    
            # We want something like a ScrollArea??
            self.scrollArea = QScrollArea(self)
            self.scrollArea.setWidgetResizable(True)
            self.scrollAreaWidgetContents = QWidget()
    
            self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
            self.scrollArea.setWidget(self.scrollAreaWidgetContents)
    
            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}")
    
            event.setDropAction(Qt.DropAction.MoveAction)
    
            button = QPushButton("xyz")
    
            self.gridLayout.addWidget(button, self.column, self.row)
            self.column += 1
            # self.row += 1
    
            event.accept()
    
    
    app = QApplication([])
    w = Window()
    w.show()
    
    app.exec()
    

    Can anyone help me along? Just dropping clues would be welcome as well, the Qt Framework is a bit overwhelming to me.

    Regards,

    Roddy

    M Offline
    M Offline
    MightyDigits
    wrote on last edited by
    #2

    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()
    
    SGaistS 1 Reply Last reply
    0
    • M MightyDigits

      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()
      
      SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by SGaist
      #3

      Hi and welcome to devnet,

      You need to implement the dragMoveEvent as well.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      M 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi and welcome to devnet,

        You need to implement the dragMoveEvent as well.

        M Offline
        M Offline
        MightyDigits
        wrote on last edited by
        #4

        @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?

        SGaistS 1 Reply Last reply
        0
        • M MightyDigits

          @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?

          SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #5

          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.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          M 1 Reply Last reply
          2
          • SGaistS SGaist

            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.

            M Offline
            M Offline
            MightyDigits
            wrote on last edited by
            #6

            @SGaist Got it to work. Thanks a lot!

            SGaistS 1 Reply Last reply
            0
            • M MightyDigits

              @SGaist Got it to work. Thanks a lot!

              SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #7

              @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 :-)

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              M 1 Reply Last reply
              0
              • M MightyDigits has marked this topic as solved on
              • SGaistS SGaist

                @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 :-)

                M Offline
                M Offline
                MightyDigits
                wrote on last edited by
                #8

                @SGaist Share the code as well?

                SGaistS 1 Reply Last reply
                0
                • M MightyDigits

                  @SGaist Share the code as well?

                  SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on last edited by
                  #9

                  @MightyDigits that would be nice as well ! It might help other people having the same issue :-)

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  M 1 Reply Last reply
                  0
                  • SGaistS SGaist

                    @MightyDigits that would be nice as well ! It might help other people having the same issue :-)

                    M Offline
                    M Offline
                    MightyDigits
                    wrote on last edited by
                    #10

                    @SGaist

                    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()
                    
                    SGaistS 1 Reply Last reply
                    0
                    • M MightyDigits

                      @SGaist

                      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()
                      
                      SGaistS Offline
                      SGaistS Offline
                      SGaist
                      Lifetime Qt Champion
                      wrote on last edited by
                      #11

                      From the looks of it, you did not mapToScene.

                      Interested in AI ? www.idiap.ch
                      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                      M 1 Reply Last reply
                      1
                      • SGaistS SGaist

                        From the looks of it, you did not mapToScene.

                        M Offline
                        M Offline
                        MightyDigits
                        wrote on last edited by
                        #12

                        @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!

                        1 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved