Read the free tutorial below or

Get the Video

PyQt5 Signals, Slots & Events
Triggering actions in response to user behaviors and GUI events

Getting started with PyQt5 course

So far we've created a window and added a simple push button widget to it, but the button doesn't do anything. That's not very useful at all -- when you create GUI applications you typically want them to do something! What we need is a way to connect the action of pressing the button to making something happen. In Qt, this is provided by signals and slots or events.

Signals & Slots

Signals are notifications emitted by widgets when something happens. That something can be any number of things, from pressing a button, to the text of an input box changing, to the text of the window changing. Many signals are initiated by user action, but this is not a rule.

In addition to notifying about something happening, signals can also send data to provide additional context about what happened.

You can also create your own custom signals, which we'll explore later.

Slots is the name Qt uses for the receivers of signals. In Python any function (or method) in your application can be used as a slot -- simply by connecting the signal to it. If the signal sends data, then the receiving function will receive that data too. Many Qt widgets also have their own built-in slots, meaning you can hook Qt widgets together directly.

Let's take a look at the basics of Qt signals and how you can use them to hook widgets up to make things happen in your apps.

Save the following app outline to a file named app.py.

python
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

QPushButton Signals

Our simple application currently has a QMainWindow with a QPushButton set as the central widget. Let's start by hooking up this button to a custom Python method. Here we create a simple custom slot named the_button_was_clicked which accepts the clicked signal from the QPushButton.

python
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)

        # Set the central widget of the Window.
        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Run it! If you click the button you'll see the text "Clicked!" on the console.

python
Clicked!
Clicked!
Clicked!
Clicked!

Receiving data

That's a good start! We've heard already that signals can also send data to provide more information about what has just happened. The .clicked signal is no exception, also providing a checked (or toggled) state for the button. For normal buttons this is always False, so our first slot ignored this data. However, we can make our button checkable and see the effect.

In the following example, we add a second slot which outputs the checkstate.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)
        button.clicked.connect(self.the_button_was_toggled)

        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")

    def the_button_was_toggled(self, checked):
        print("Checked?", checked)

Run it! If you press the button you'll see it highlighted as checked. Press it again to release it. Look for the check state in the console.

python
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True

You can connect as many slots to a signal as you like and can respond to different versions of signals at the same time on your slots.

Storing data

Often it is useful to store the current state of a widget in a Python variable. This allows you to work with the values like any other Python variable and without accessing the original widget. You can either store these values as individual variables or use a dictionary if you prefer. In the next example we store the checked value of our button in a variable called button_is_checked on self.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_toggled)
        button.setChecked(self.button_is_checked)

        self.setCentralWidget(button)

    def the_button_was_toggled(self, checked):
        self.button_is_checked = checked

        print(self.button_is_checked)

First we set the default value for our variable (to True), then use the default value to set the initial state of the widget. When the widget state changes, we receive the signal and update the variable to match.

You can use this same pattern with any PyQt widgets. If a widget does not provide a signal that sends the current state, you will need to retrieve the value from the widget directly in your handler. For example, here we're checking the checked state in a pressed handler.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.setCheckable(True)
        self.button.released.connect(self.the_button_was_released)
        self.button.setChecked(self.button_is_checked)

        self.setCentralWidget(self.button)

    def the_button_was_released(self):
        self.button_is_checked = self.button.isChecked()

        print(self.button_is_checked)

We need to keep a reference to the button on self so we can access it in our slot.

The released signal fires when the button is released, but does not send the check state, so instead we use .isChecked() to get the check state from the button in our handler.

Changing the interface

So far we've seen how to accept signals and print output to the console. But how about making something happen in the interface when we click the button? Let's update our slot method to modify the button, changing the text and disabling the button so it is no longer clickable. We'll also turn off the checkable state for now.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        self.button.setText("You already clicked me.")
        self.button.setEnabled(False)

        # Also change the window title.
        self.setWindowTitle("My Oneshot App")

Again, because we need to be able to access the button in our the_button_was_clicked method, we keep a reference to it on self. The text of the button is changed by passing a str to .setText(). To disable a button call .setEnabled() with False.

Run it! If you click the button the text will change and the button will become unclickable.

You're not restricted to changing the button that triggers the signal, you can do anything you want in your slot methods. For example, try adding the following line to the_button_was_clicked method to also change the window title.

python
self.setWindowTitle("A new window title")

Most widgets have their own signals -- and the QMainWindow we're using for our window is no exception. In the following more complex example, we connect the .windowTitleChanged signal on the QMainWindow to a custom slot method.

In the following example we connect the .windowTitleChanged signal on the QMainWindow to a method slot the_window_title_changed. This slot also receives the new window title.

python
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton

import sys
from random import choice

window_titles = [
    'My App',
    'My App',
    'Still My App',
    'Still My App',
    'What on earth',
    'What on earth',
    'This is surprising',
    'This is surprising',
    'Something went wrong'
]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.n_times_clicked = 0

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.windowTitleChanged.connect(self.the_window_title_changed)

        # Set the central widget of the Window.
        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        print("Clicked.")
        new_window_title = choice(window_titles)
        print("Setting title:  %s" % new_window_title)
        self.setWindowTitle(new_window_title)

    def the_window_title_changed(self, window_title):
        print("Window title changed: %s" % window_title)

        if window_title == 'Something went wrong':
            self.button.setDisabled(True)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

First we set up a list of window titles -- we'll select one at random from this list using Python's built-in random.choice(). We hook our custom slot method the_window_title_changed to the window's .windowTitleChanged signal.

When we click the button the window title will change at random. If the new window title equals "Something went wrong" the button will be disabled.

Run it! Click the button repeatedly until the title changes to "Something went wrong" and the button will become disabled.

There are a few things to notice in this example.

Firstly, the windowTitleChanged signal is not always emitted when setting the window title. The signal only fires if the new title is changed from the previous one. If you set the same title multiple times, the signal will only be fired the first time. It is important to double-check the conditions under which signals fire, to avoid being surprised when using them in your app.

Secondly, notice how we are able to chain things together using signals. One thing happening -- a button press -- can trigger multiple other things to happen in turn. These subsequent effects do not need to know what caused them, but simply follow as a consequence of simple rules. This decoupling of effects from their triggers is one of the key concepts to understand when building GUI applications. We'll keep coming back to this throughout the book!

In this section we've covered signals and slots. We've demonstrated some simple signals and how to use them to pass data and state around your application. Next we'll look at the widgets which Qt provides for use in your applications -- together with the signals they provide.

Connecting widgets together directly

So far we've seen examples of connecting widget signals to Python methods. When a signal is fired from the widget, our Python method is called and receives the data from the signal. But you don't always need to use a Python function to handle signals -- you can also connect Qt widgets directly to one another.

In the following example, we add a QLineEdit widget and a QLabel to the window. In the \\__init__ for the window we connect our line edit .textChanged signal to the .setText method on the QLabel. Now any time the text changes in the QLineEdit the QLabel will receive that text to it's .setText method.

python
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget

import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.label = QLabel()

        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)

        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)

        container = QWidget()
        container.setLayout(layout)

        # Set the central widget of the Window.
        self.setCentralWidget(container)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Notice that in order to connect the input to the label, the input and label must both be defined. This code adds the two widgets to a layout, and sets that on the window. We'll cover layouts in detail later, you can ignore it for now.

Run it! Type some text in the upper box, and you'll see it appear immediately on the label.

Any text typed in the input immediately appears on the label Any text typed in the input immediately appears on the label.

Most Qt widgets have slots available, to which you can connect any signal that emits the same type that it accepts. The widget documentation has the slots for each widget listed under "Public Slots". For example, see https://doc.qt.io/qt-5/qlabel.html#public-slots[QLabel].

Create GUI Applications with Python & Qt6
The easy way to create desktop applications

My complete guide, updated for 2021 & PyQt6. Everything you need build real apps.

Downloadable ebook (PDF, ePub) & Complete Source code

To support developers in [[ countryRegion ]] I give a [[ localizedDiscount[couponCode] ]]% discount with the code [[ couponCode ]] — Enjoy!

For [[ activeDiscount.description ]] I'm giving a [[ activeDiscount.discount ]]% discount with the code [[ couponCode ]] — Enjoy!

Events

Every interaction the user has with a Qt application is an event. There are many types of event, each representing a different type of interaction. Qt represents these events using event objects which package up information about what happened. These events are passed to specific event handlers on the widget where the interaction occurred.

By defining custom, or extended event handlers you can alter the way your widgets respond to these events. Event handlers are defined just like any other method, but the name is specific for the type of event they handle.

One of the main events which widgets receive is the QMouseEvent. QMouseEvent events are created for each and every mouse movement and button click on a widget. The following event handlers are available for handling mouse events --

Event handler Event type moved
mouseMoveEvent Mouse moved
mousePressEvent Mouse button pressed
mouseReleaseEvent Mouse button released
mouseDoubleClickEvent Double click detected

For example, clicking on a widget will cause a QMouseEvent to be sent to the .mousePressEvent event handler on that widget. This handler can use the event object to find out information about what happened, such as what triggered the event and where specifically it occurred.

You can intercept events by sub-classing and overriding the handler method on the class. You can choose to filter, modify, or ignore events, passing them up to the normal handler for the event by calling the parent class function with super(). These could be added to your main window class as follows. In each case e will receive the incoming event.

python
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QLabel("Click in this window")
        self.setCentralWidget(self.label)

    def mouseMoveEvent(self, e):
        self.label.setText("mouseMoveEvent")

    def mousePressEvent(self, e):
        self.label.setText("mousePressEvent")

    def mouseReleaseEvent(self, e):
        self.label.setText("mouseReleaseEvent")

    def mouseDoubleClickEvent(self, e):
        self.label.setText("mouseDoubleClickEvent")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Run it! Try moving and clicking (and double-clicking) in the window and watch the events appear.

You'll notice that mouse move events are only registered when you have the button pressed down. You can change this by calling self.setMouseTracking(True) on the window. You may also notice that the press (click) and double-click events both fire when the button is pressed down. Only the release event fires when the button is released. Typically to register a click from a user you should watch for both the mouse down and the release.

Inside the event handlers you have access to an event object. This object contains information about the event and can be used to respond differently depending on what exactly has occurred. We'll look at the mouse event objects next.

Mouse events

All mouse events in Qt are tracked with the QMouseEvent object, with information about the event being readable from the following event methods.

Method Returns
.button() Specific button that triggered this event
.buttons() State of all mouse buttons (OR'ed flags)
.globalPos() Application-global position as a QPoint
.globalX() Application-global horizontal X position
.globalY() Application-global vertical Y position
.pos() Widget-relative position as a QPoint integer
.posF() Widget-relative position as a QPointF float

You can use these methods within an event handler to respond to different events differently, or ignore them completely. The positional methods provide both global and local (widget-relative) position information as QPoint objects, while buttons are reported using the mouse button types from the Qt namespace.

For example, the following allows us to respond differently to a left, right or middle click on the window.

python
    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            # handle the left-button press in here
            self.label.setText("mousePressEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            # handle the middle-button press in here.
            self.label.setText("mousePressEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            # handle the right-button press in here.
            self.label.setText("mousePressEvent RIGHT")

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseReleaseEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseReleaseEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseReleaseEvent RIGHT")

    def mouseDoubleClickEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseDoubleClickEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseDoubleClickEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseDoubleClickEvent RIGHT")

The button identifiers are defined in the Qt namespace, as follows --

Identifier Value (binary) Represents
Qt.NoButton 0 (000) No button pressed, or the event is not related to button press.
Qt.LeftButton 1 (001) The left button is pressed
Qt.RightButton 2 (010) The right button is pressed.
Qt.MiddleButton 4 (100) The middle button is pressed.

On right-handed mice the left and right button positions are reversed, i.e. pressing the right-most button will return Qt.LeftButton. This means you don't need to account for the mouse orientation in your code.

Context menus

Context menus are small context-sensitive menus which typically appear when right clicking on a window. Qt has support for generating these menus, and widgets have a specific event used to trigger them. In the following example we're going to intercept the .contextMenuEvent a QMainWindow. This event is fired whenever a context menu is about to be shown, and is passed a single value event of type QContextMenuEvent.

To intercept the event, we simply override the object method with our new method of the same name. So in this case we can create a method on our MainWindow subclass with the name contextMenuEvent and it will receive all events of this type.

python
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QApplication, QLabel, QMainWindow, QMenu


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

    def contextMenuEvent(self, e):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(e.globalPos())


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

If you run the above code and right-click within the window, you'll see a context menu appear. You can set up .triggered slots on your menu actions as normal (and re-use actions defined for menus and toolbars).

When passing the initial position to the exec function, this must be relative to the parent passed in while defining. In this case we pass self as the parent, so we can use the global position.

Just for completeness, there is actually a signal-based approach to creating context menus.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.show()

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.on_context_menu)

    def on_context_menu(self, pos):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(self.mapToGlobal(pos))

It's entirely up to you which you choose.

Event hierarchy

In PyQt every widget is part of two distinct hierarchies: the Python object hierarchy, and the Qt layout hierarchy. How you respond or ignore events can affect how your UI behaves.

Python inheritance forwarding

Often you may want to intercept an event, do something with it, yet still trigger the default event handling behavior. If your object is inherited from a standard widget, it will likely have sensible behavior implemented by default. You can trigger this by calling up to the parent implementation using super().

This is the Python parent class, not the PyQt .parent().

python
def mousePressEvent(self, event):
    print("Mouse pressed!")
    super(self, MainWindow).contextMenuEvent(event)

The event will continue to behave as normal, yet you've added some non-interfering behavior.

Layout forwarding

When you add a widget to your application, it also gets another parent from the layout. The parent of a widget can be found by calling .parent(). Sometimes you specify these parents manually, such as for QMenu or QDialog, often it is automatic. When you add a widget to your main window for example, the main window will become the widget's parent.

When events are created for user interaction with the UI, these events are passed to the uppermost widget in the UI. So, if you have a button on a window, and click the button, the button will receive the event first.

If the first widget cannot handle the event, or chooses not to, the event will bubble up to the parent widget, which will be given a turn. This bubbling continues all the way up nested widgets, until the event is handled or it reaches the main window.

In your own event handlers you can choose to mark an event as handled calling .accept() --

python
    class CustomButton(QPushButton)
        def mousePressEvent(self, e):
            e.accept()

Alternatively, you can mark it as unhandled by calling .ignore() on the event object. In this case the event will continue to bubble up the hierarchy.

python
    class CustomButton(QPushButton)
        def event(self, e):
            e.ignore()

If you want your widget to appear transparent to events, you can safely ignore events which you've actually responded to in some way. Similarly, you can choose to accept events you are not responding to in order to silence them.

Complete Mark As Complete Next

Continue reading

Drag & drop widgets with PyQt  PyQt

This week I had an interesting question from a reader of my PyQt6 book, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved. I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag & drop, exchanging the position of buttons, but I want to show the motion of QPushButton, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer. First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question. Drag & drop widgets We'll start with this simple application which creates a window using QWidget and places a series of QPushButton widgets into it. You can substitute QPushButton for any other widget you like, e.g. QLabel python from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton class Window(QWidget): def __init__(self): super().__init__() self.blayout = QHBoxLayout() for l in ['A', 'B', 'C', 'D']: btn = QPushButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) app = QApplication([]) w = Window() w.show() app.exec_() If you run this you should see something like this. The series of QPushButton widgets in a horizontal layout. Here we're creating a window, but the Window widget is subclassed from QWidget, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget. QPushButton objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file. python from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) drag.exec_(Qt.MoveAction) We implement a mouseMoveEvent which accepts the single e parameter of the event. We check to see if the left mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a QDrag object, passing in self to give us access later to the widget that was dragged. We also must pass in mime data. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty. Finally, we initiate a drag by calling drag.exec_(Qt.MoveAction). As with dialogs exec_() starts a new event loop, blocking the main loop until the drag is complete. The parameter Qt.MoveAction tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user. You can update the main window code to use our new DragButton class as follows. python class Window(QWidget): def __init__(self): super().__init__() self.blayout = QHBoxLayout() for l in ['A', 'B', 'C', 'D']: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) If you run the code now, you can drag the buttons, but you'll notice the drag is forbidden. Dragging of the widget starts but is forbidden. What's happening? The mouse movement is being detected by our DragButton object and the drag started, but the main window does not accept drag & drop. To fix this we need to enable drops on the window and implement dragEnterEvent to actually accept them. python class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.blayout = QHBoxLayout() for l in ['A', 'B', 'C', 'D']: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling drag.exec_(). Dragging of the widget starts and is accepted, showing a move icon. Releasing the mouse button during a drag drop operation triggers a dropEvent on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our dropEvent method. The drop event contains the position the mouse was at when the button was released & the drop triggered. We can use this to determine where to move the widget to. python def dropEvent(self, e): pos = e.pos() widget = e.source() for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x(): # We didn't drag past this widget. # insert to the left of it. self.blayout.insertWidget(n-1, widget) break e.accept() To determine where to place the widget, we iterate over all the widgets in the layout, until we find one who's x position is greater than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop. The effect of this is that if you drag 1 pixel past the start of another widget, which might be a bit confusing. You adjust this the cut off to use the middle of the widget using if pos.x() < w.x() + w.size().width() // 2: -- that is x + half of the width. python def dropEvent(self, e): pos = e.pos() widget = e.source() for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x() + w.size().width() // 2: # We didn't drag past this widget. # insert to the left of it. self.blayout.insertWidget(n-1, widget) break e.accept() The complete working drag-drop code is shown below. python from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) drag.exec_(Qt.MoveAction) class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.blayout = QHBoxLayout() for l in ['A', 'B', 'C', 'D']: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): pos = e.pos() widget = e.source() for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x() + w.size().width() // 2: # We didn't drag past this widget. # insert to the left of it. self.blayout.insertWidget(n-1, widget) break e.accept() app = QApplication([]) w = Window() w.show() app.exec_() Visual drag & drop So now we have our working drag & drop implementation we can move on to showing the drag visually. What we want to achieve here is showing the button being dragged next to the mouse point as it is dragged. Qt's QDrag handler natively provides a mechanism for showing dragged objects which we can use. We can update our DragButton class to pass a pixmap image to QDrag and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a QPixmap of the widget we're dragging. python from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag, QPixmap class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec_(Qt.MoveAction) To create the pixmap we create a QPixmap object passing in the size of the widget this event is fired on with self.size(). This creates an empty pixmap which we can then pass into self.render to render -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the drag object. If you run the code with this modification you'll see something like the following -- Dragging of the widget showing the dragged widget. Generic drag & drop container We can take this a step further and implement a generic drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget DragWidget which can be added to any window. You can add items -- instances of DragItem -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal orderChanged. python from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QLabel, QMainWindow, QVBoxLayout from PyQt5.QtCore import Qt, QMimeData, pyqtSignal from PyQt5.QtGui import QDrag, QPixmap class DragItem(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setContentsMargins(25, 5, 25, 5) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setStyleSheet("border: 1px solid black;") # Store data separately from display label, but use label for default. self.data = self.text() def set_data(self, data): self.data = data def mouseMoveEvent(self, e): if e.buttons() == Qt.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec_(Qt.MoveAction) class DragWidget(QWidget): """ Generic list sorting handler. """ orderChanged = pyqtSignal(list) def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs): super().__init__() self.setAcceptDrops(True) # Store the orientation for drag checks later. self.orientation = orientation if self.orientation == Qt.Orientation.Vertical: self.blayout = QVBoxLayout() else: self.blayout = QHBoxLayout() self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): pos = e.pos() widget = e.source() for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if self.orientation == Qt.Orientation.Vertical: # Drag drop vertically. drop_here = pos.y() < w.y() + w.size().height() // 2 else: # Drag drop horizontally. drop_here = pos.x() < w.x() + w.size().width() // 2 if drop_here: # We didn't drag past this widget. # insert to the left of it. self.blayout.insertWidget(n-1, widget) self.orderChanged.emit(self.get_item_data()) break e.accept() def add_item(self, item): self.blayout.addWidget(item) def get_item_data(self): data = [] for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() data.append(w.data) return data class MainWindow(QMainWindow): def __init__(self): super().__init__() self.drag = DragWidget(orientation=Qt.Orientation.Vertical) for n, l in enumerate(['A', 'B', 'C', 'D']): item = DragItem(l) item.set_data(n) # Store the data. self.drag.add_item(item) # Print out the changed order. self.drag.orderChanged.connect(print) container = QWidget() layout = QVBoxLayout() layout.addStretch(1) layout.addWidget(self.drag) layout.addStretch(1) container.setLayout(layout) self.setCentralWidget(container) app = QApplication([]) w = MainWindow() w.show() app.exec_() Generic drag-drop sorting in horizontal orientation. You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal QLabel which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call get_item_data yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort anything not just strings. In the example above we're passing in the enumerated index as the data, so dragging will output (via the print connected to orderChanged) something like: python [1, 0, 2, 3] [1, 2, 0, 3] [1, 0, 2, 3] [1, 2, 0, 3] If you remove the item.set_data(n) you'll see the labels emitted on changes. python ['B', 'A', 'C', 'D'] ['B', 'C', 'A', 'D'] We've also implemented orientation onto the DragWidget using the Qt built in flags Qt.Orientation.Vertical or Qt.Orientation.Horizontal. This setting this allows you sort items either vertically or horizontally -- the calculations are handled for both directions. Generic drag-drop sorting in vertical orientation. More