PyQt - New Signals with PyQtSignal



PyQt automatically defines signals for all Qt's built-in signals, but sometimes custom signals are required to ease the communication between different parts of the application. This is where pyqtSignal helps the developers to define and create new custom signals as class attributes using PyQtSignal factory.

Syntax and Parameters of pyqtSignal

We can define a new signal using pyqtSignal as class attributes as follows −

PyQt6.QtCore.pyqtSignal(types[, name[, revision=0[, arguments=[]]]])

In the above syntax the parameters passed to the pyqtSignals plays different role as follows −

  • types − Defines the types that constitute the C++ signature of the signal. Each type can be a Python type object, a string representing a C++ type, or a sequence of type arguments defining multiple signal overloads.For example, int, float etc.
  • name(optional) − It specifies the name of the signal. If omitted, the name of the class attribute is used.
  • revision(optional) − It specifies the revision of the signal exported to QML. (Qt Modeling Language)
  • arguments(optional) − It specifies the names of the signal's arguments exported to QML.

Defining New Signal

A new signal in PyQt specifies an event or condition that is emitted by an object when some specific action occurs or a certain state changes. When a new signal is emitted the objects whihc connected to these signals can execute code in response which enables effective communication and interaction between different parts of the application.

Example of Signal Definition

from PyQt6.QtCore import QObject, pyqtSignal

class Foo(QObject):
   # Define a signal called 'closed' with no arguments.
   closed = pyqtSignal()

   # Define a signal called 'rangeChanged' with two integer arguments.
   range_changed = pyqtSignal(int, int, name='rangeChanged')

   # Define a signal called 'valueChanged' with two overloads: int and QString.
   valueChanged = pyqtSignal([int], ['QString'])

Guidelines for Defining New Signals

  • New Signals should only be defined in sub-classes of QObject.
  • New Signals must be part of the class definition and cannot be added dynamically after class definition.
  • New Signals defined using pyqtSignal are automatically added to the class's QMetaObject, making them accessible in Qt Designer and through the QMetaObject API.

Caution with Overloaded Signal

When we define overloaded signals i.e the signal contains more then one signature type of the signal, we should take caution when dealing with Python types that does not have corresponding C++ types.It's possible to have overloaded signals with different Python signatures but identical C++ signatures, leading to unexpected behavior.

Example of overloaded signal with unexpected behaviour

class Foo(QObject):

   # This will cause problems because each has the same C++ signature.
   cautiousSignal = pyqtSignal([dict], [list])

PyQt will consider both [dict] and [list] of array type internally leading to unexpected behaviour of signal.

Example of New Simple Signal

In the below example we define a PyQt class Counter which is inherited from QObject. We define a custom new signal that emits an integer when invoked.The increment method increases the internal _value attribute by 1 and emits the valueChanged signal with the updated value. An instance of Counter is created, and a lambda function is connected to its valueChanged signal to print the current value when the increment method is called.

from PyQt6.QtCore import QObject, pyqtSignal

class Counter(QObject):
   valueChanged = pyqtSignal(int)

   def __init__(self):
      super().__init__()
      self._value = 0

   def increment(self):
      self._value += 1
      self.valueChanged.emit(self._value)

counter = Counter()
counter.valueChanged.connect(lambda value: print(f"Counter value: {value}"))

counter.increment()

Output

Counter value: 1

Example of Custom new Signal with Arguments

In this example we defined a Worker class that is inherited from QObject. A new custom signal job_done is defined using pyqtSignal that emits a string.

The do_work method simulates work and emits the job_done signal with a message. An instance of Worker is created, and a lambda function is connected to its job_done signal, printing the worker's status when the task is completed.

from PyQt6.QtCore import QObject, pyqtSignal

class Worker(QObject):
   job_done = pyqtSignal(str)

   def do_work(self):
      # Simulating some work
      result = "Task completed successfully"
      self.job_done.emit(result)

worker = Worker()
worker.job_done.connect(lambda message: print(f"Worker status: {message}"))

worker.do_work()

Output

Worker status: Task completed successfully

Example of Overloaded new Signal

In this example, we're using QVariant as the type argument for the signal, and specifying the argument name as 'data'. When emitting the signal, we pass the dictionary as the argument.

Using QVariant allows us to pass complex data types like dictionaries between PyQt objects via signals.

from PyQt6.QtCore import QObject, pyqtSignal, QVariant

class Loader(QObject):
   data_loaded = pyqtSignal(QVariant, arguments=['data'])

   def load_data(self):
      # Simulating data loading
      data_dict = {"key1": "value1", "key2": "value2"}
      self.data_loaded.emit(data_dict)

loader = Loader()
loader.data_loaded.connect(lambda data: print(f"Data loaded: {data}"))

loader.load_data()

Output

Data loaded: {'key1': 'value1', 'key2': 'value2'}
Advertisements