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. Strange or Possible Bug with Drag Behaviour in PySide6 but not PyQT5
Forum Updated to NodeBB v4.3 + New Features

Strange or Possible Bug with Drag Behaviour in PySide6 but not PyQT5

Scheduled Pinned Locked Moved Unsolved Qt for Python
qt for pythonpysidepython
1 Posts 1 Posters 891 Views
  • 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.
  • O Offline
    O Offline
    os_ss
    wrote on last edited by
    #1

    After spending weeks on googling/reading books/watching tutors and giving up on finding a nice solution that could implement a custom-widget drag and drop method using Model/View/delegate.
    I found following link the closest sample on what I need in-spite there is no delegate implementation in the code.

    https://github.com/Apress/modern-pyqt/blob/main/ch02_project_management/drag_drop_widget.py

    In fact would like to have a custom widget Having a Text+Image to be Draggable+Droppable into columns (Move action)
    Without missing Image, painting issues, ....

    so I tweaked the PyQT5 code to work with PySide6 and I have checked against PyQt5 implementation.

    I have realised my adoption of the code causes PySide6 to behave strangely
    In fact with the PySide6 implementation if there is multiple tasks in one column
    only hovering over a single task cause the Task to move take over other tasks and mess up with
    the tasks in that column and destroy the arrangement of the tasks.

    I have not seen same behaviour with the PyQT5 implementation.

    Is there a bug?

    In addition I do not know how to re-order tasks inside a column or make dropping a task to be inserted at the location that I release the mouse button.

    The rearrangement of the tasks should be possible between two columns (inserting in the middle of other column) and inside same coulmn.

    Any hint that can shed a light on this issues is really appreciated.
    I am new to pyside and kindly explain your solution that I can understand and implement

    # project_manager.py
    # Import necessary modules
    import sys
    
    from PySide6.QtCore import Qt, QMimeData, QSize
    from PySide6.QtGui import QDrag, QIcon, QPixmap, QPainter, QTextCursor
    from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QFrame, QPushButton,
                                   QLineEdit, QTextEdit, QHBoxLayout, QVBoxLayout, QDialog)
    
    from ProjectManagerStyleSheet import style_sheet
    
    
    class TaskWidget(QFrame):
        def __init__(self, title):
            super().__init__()
            print("TaskWidget::ctor")
            self.setMinimumHeight(32)
            self.setObjectName("Task")
            self.title = title
            self.task_description = ""
    
            task_label = QLabel(title)
            task_label.setObjectName("TaskLabel")
            edit_task_button = QPushButton(QIcon("images/three_dots.png"), None)
            edit_task_button.setIconSize(QSize(28, 28))
            edit_task_button.setMaximumSize(30, 30)
            edit_task_button.clicked.connect(self.specifyTaskInfo)
    
            task_h_box = QHBoxLayout()
            task_h_box.addWidget(task_label)
            task_h_box.addWidget(edit_task_button)
            self.setLayout(task_h_box)
    
        def specifyTaskInfo(self):
            print("TaskWidget::specifyTaskInfo")
            """Create the dialog box where the user can write
            more information about their currently selected task."""
            self.task_info_dialog = QDialog(self)
    
            task_header = QLabel(self.title)
            task_header.setObjectName("TaskHeader")
            description_label = QLabel("Description")
            description_label.setObjectName("DescriptionLabel")
    
            self.enter_task_desc_text = QTextEdit()
            self.enter_task_desc_text.setText(self.task_description)
    
            # The cursor will appear at the end of the text edit input field
            self.enter_task_desc_text.moveCursor(QTextCursor.End)
    
            save_button = QPushButton("Save")
            save_button.clicked.connect(self.confirmTaskDescription)
            cancel_button = QPushButton("Cancel")
            cancel_button.clicked.connect(self.task_info_dialog.reject)
    
            # Create layout for the dialog's buttons
            button_h_box = QHBoxLayout()
            button_h_box.addWidget(save_button)
            button_h_box.addSpacing(15)
            button_h_box.addWidget(cancel_button)
    
            # Create layout and add widgets for the dialog box
            dialog_v_box = QVBoxLayout()
            dialog_v_box.addWidget(task_header)
            dialog_v_box.addWidget(description_label, Qt.AlignLeft)
            dialog_v_box.addWidget(self.enter_task_desc_text)
            dialog_v_box.addItem(button_h_box)
    
            self.task_info_dialog.setLayout(dialog_v_box)
            self.task_info_dialog.show()  # Display dialog box
    
        def confirmTaskDescription(self):
            print("TaskWidget::confirmTaskDescription")
            """When a user selects Save, save the info written in the text edit
            widget to the task_description variable."""
            text = self.enter_task_desc_text.toPlainText()
            if text == "":
                pass
            elif text != "":
                self.task_description = text
            self.task_info_dialog.close()  # Close dialog box
    
        def mousePressEvent(self, event):
            print("TaskWidget::mousePressEvent")
            """Reimplement what happens when the user clicks on the widget."""
            if event.button() == Qt.LeftButton:
                # deprecated self.drag_start_position = event.pos()
                self.drag_start_position = event.pos()
    
        def mouseMoveEvent(self, event):
            print("TaskWidget::mouseMoveEvent")
            """Reimplement how to handle the widget being dragged.
            Change the mouse icon when the user begins dragging the object."""
            drag = QDrag(self)
            # When the user begins dragging the object, change the cursor's icon and set the drop action
            drag.setDragCursor(QPixmap("images/drag.png"), Qt.MoveAction)
            mime_data = QMimeData()
            drag.setMimeData(mime_data)
    
            # Create the QPainter object that will draw the widget being dragged
            pixmap = QPixmap(self.size())  # Get the size of the object
            painter = QPainter(pixmap)  # Set the painter's pixmap
            # Draw the pixmap; grab() renders the widget into a pixmap specified by rect()
            painter.drawPixmap(self.rect(), self.grab())
            painter.end()
            drag.setPixmap(pixmap)  # Set the pixmap to represent the drag action
            drag.setHotSpot(event.pos())
            drag.exec(Qt.MoveAction)
    
    
    class TaskContainer(QWidget):
        def __init__(self, title, bg_color):
            super().__init__()
            self.setAcceptDrops(True)
            self.setObjectName("ContainerWidget")
    
            container_label = QLabel(title)  # Container's title
            # Set the background color of the container's label
            container_label.setStyleSheet("background-color: {}".format(bg_color))
            container_frame = QFrame()  # Main container to hold all TaskWidget objects
            container_frame.setObjectName("ContainerFrame")
    
            self.new_task_button = QPushButton("+ Add a new task")
            self.new_task_button.clicked.connect(self.createNewTask)
    
            self.tasks_v_box = QVBoxLayout()
            self.tasks_v_box.insertWidget(-1, self.new_task_button)
            container_frame.setLayout(self.tasks_v_box)
    
            # Main layout for container class
            container_v_box = QVBoxLayout()
            container_v_box.setSpacing(0)  # No space between widgets
            container_v_box.setAlignment(Qt.AlignTop)
            container_v_box.addWidget(container_label)
            container_v_box.addWidget(container_frame)
            container_v_box.setContentsMargins(0, 0, 0, 0)
    
            self.setLayout(container_v_box)
    
        def createNewTask(self):
            """Set up the dialog box that allows the user to create a new task."""
            self.new_task_dialog = QDialog(self)
            self.new_task_dialog.setWindowTitle("Create New Task")
            self.new_task_dialog.setModal(True)  # Create a modal dialog
    
            self.enter_task_line = QLineEdit()
            self.enter_task_line.setPlaceholderText("Enter a title for this task...")
    
            self.add_task_button = QPushButton("Add Task")
            self.add_task_button.clicked.connect(self.confirmTask)
            cancel_button = QPushButton("Cancel")
            cancel_button.clicked.connect(self.new_task_dialog.reject)
    
            # Create layout for the dialog's buttons
            button_h_box = QHBoxLayout()
            button_h_box.addWidget(self.add_task_button)
            button_h_box.addSpacing(15)
            button_h_box.addWidget(cancel_button)
    
            # Create layout and add widgets for the dialog box
            dialog_v_box = QVBoxLayout()
            dialog_v_box.addWidget(self.enter_task_line)
            dialog_v_box.addItem(button_h_box)
    
            self.new_task_dialog.setLayout(dialog_v_box)
            self.new_task_dialog.show()
    
        def confirmTask(self):
            """If a user clicks Add Task in the dialog box, create a new TaskWidget
            object and insert it into the container's layout."""
            if self.enter_task_line.text() != "":
                new_task = TaskWidget(self.enter_task_line.text())
                self.tasks_v_box.insertWidget(0, new_task, 0)
            self.new_task_dialog.close()
    
        def dragEnterEvent(self, event):
            print("TaskContainer::dragEnterEvent")
            """Accept the dragging event onto the widget."""
            event.setAccepted(True)
    
        def dropEvent(self, event):
            print("TaskContainer::dropEvent")
            """Check the source of the mouse event.
            If the source does not already exist in the target
            widget then the drop is allowed."""
            event.setDropAction(Qt.MoveAction)
            source = event.source()
            if source not in self.children():
                event.setAccepted(True)
                self.tasks_v_box.addWidget(source)
            else:
                event.setAccepted(False)
    
            # Whenever a widget is dropped, ensure new_task_button stays at the bottom of the container
            self.tasks_v_box.insertWidget(-1, self.new_task_button)
    
    
    class ProjectManager(QWidget):
        def __init__(self):
            super().__init__()
            self.initializeUI()
    
        def initializeUI(self):
            """Initialize the window and display its contents to the screen. """
            self.setMinimumSize(800, 400)
            #self.showMaximized()
            self.setWindowTitle('2.1 - Project Manager')
    
            self.setupWidgets()
            self.show()
    
        def setupWidgets(self):
            """Set up the containers and main layout for the window."""
            possible_container = TaskContainer("Possible Projects", "#0AC2E4")
            # Blue
            progress_container = TaskContainer("In Progress", "#F88A20")
            # Orange
            review_container = TaskContainer("Under Review", "#E7CA5F")
            # Yellow
            completed_container = TaskContainer("Completed Projects", "#10C94E")
            # Green
            main_h_box = QHBoxLayout()
            main_h_box.addWidget(possible_container)
            main_h_box.addWidget(progress_container)
            main_h_box.addWidget(review_container)
            main_h_box.addWidget(completed_container)
            self.setLayout(main_h_box)
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        app.setStyleSheet(style_sheet)
        window = ProjectManager()
        sys.exit(app.exec())
    
    

    The Styling Part

    # ProjectManagerStyleSheet.py
    # Style sheet for the Project Manager GUI
    style_sheet = """
            QWidget{ /* Main window's background color */ 
                      background-color: #ACADAD
            }
            QFrame#ContainerFrame{ /* Style border for TaskContainer class */ 
                                    background-color: #8B8E96;
                                    border-bottom-left-radius: 4px;
                                    border-bottom-right-radius: 4px
            }
            QFrame:hover#Task{ /* Indicate that the object is interactive and can be dragged when the user hovers over it*/ 
                                border: 3px solid #2B2B2B
            }
            QLabel#TaskHeader{ /* Style header for dialog box */
                                background-color: #8B8E96;
                                qproperty-alignment: AlignLeft;
                                padding: 0px 0px;
            }
            QLabel#TaskLabel{ /* Set alignment for QLabel in TaskWidget class */
                                qproperty-alignment: AlignLeft;
            }
            QLabel#DescriptionLabel{ /* Style for label in dialog box */
                                        background-color: #8B8E96;
                                        qproperty-alignment: AlignLeft;
                                        padding: 0px 0px; 
                                        font: 13px
            }
            QLabel{ /* Style for QLabel objects for TaskContainer's title */
                    color: #EFEFEF;
                    qproperty-alignment: AlignCenter;
                    border-top-left-radius: 4px;
                    border-top-right-radius: 4px;
                    padding: 10px 0px;
                    font: bold 15px
            }
            QPushButton{
                        color: #4E4C4C;
                        font: 14px 'Helvetica' 
            }
            QPushButton#Task{ 
                            color: #EFEFEF
            }
            QDialog{ 
                    background-color: #8B8E96
            }
            QLineEdit{ 
                background-color: #FFFFFF 
            }
            QTextEdit{
                background-color: #FFFFFF
            }"""
    
    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