Understanding Python QT5 Signals & Slots In-depth

Understanding Python QT5 Signals & Slots In-depth

Understanding Python QT5 Signals & Slots

Hy Hashnode! Today we are gonna learn about signals and slots in python QT5, what they really are and how we can implement them in our python GUI software.

It is 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.

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.

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.

Let's look at this simple illustration, I can also liken this to a case whereby someone calls your name, may be a friend (signal) and you respond to this call (slot) by answering to the call. So in the process of answering to this call, you emit data that is your voice and you guys can start conversing.

QPushButton Signals

To demonstrate how to use slots and signals in QT5, we will set up a 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.

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton  1
from PyQt5.QtCore import Qt
import sys
class MainWindow(QMainWindow):
  def __init__(self):
     super().__init__() 2
        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_()

If you run the following code, the output will be,

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.

import sys
from PyQt5.QtCore import Qt
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)
           button.clicked.connect(self.the_button_was_toggled)

          # Set the central widget of the Window.
          self.setCentralWidget(button)
     def the_button_was_clicked(self):
           print("Clicked!")
     def the_button_was_toggled(self, checked):
           print("Checked?", checked)

app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

Console Output

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. Now let's look at something different.

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.

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) ②

          # Set the central widget of the Window.
          self.setCentralWidget(button)
     def the_button_was_toggled(self, checked):
          self.button_is_checked = checked ③
           print(self.button_is_checked)

① Set the default value for our variable.

② Use the default value to set the initial state of the widget.

③ When the widget state changes, update the variable to match.

You can use this same pattern with any PyQt5 widgets. If a widget does notprovide 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.

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)

         # Set the central widget of the Window.
         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.

.isChecked() returns the check state of the button.

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.

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton ①
from PyQt5.QtCore import Qt

import sys

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)

            # Set the central widget of the Window.
            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")

app = QApplication(sys.argv)

window = MainWindow()
window.show()
app.exec_()

① We need to be able to access the button in our the_button_was_clicked method, so we keep a reference to it on self.

② You can change the text of a button by passing a str to .setText().

③ To disable a button call .setEnabled() with False.

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.

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

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.

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5.QtCore import Qt

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_()

① A list of window titles we’ll select from using random.choice().

② Hook up our custom slot method the_window_title_changed to the windows .windowTitleChanged signal.

③ Set the window title to the new title.

④ If the new window title equals "Something went wrong" disable the button.

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 they new title is a change from the previous one. It’s important to know exactly what conditions signals fire under, to avoid being surprised!

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.

In this post 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.

Thank you guys!

Visit my blog and subscribe to my newsletter to get posts right to your mailbox. Megtrix Publications

My Website