Chapter 4. Events and Signals

In the chapters that we have seen so far, we tried out various implementations of readily available functions are extended by the Qt objects. In this chapter, we will look into some of the internal implementation working concepts of those functions. Being an event-driven toolkit, events and event delivery play an important role in the Qt architecture. We will start this chapter by discussing events and signals, their implementation, and will go on to discuss handling drag-and-drop events, and drawing functionalities.

Event management

An event in Qt is an object inherited from the abstract QEvent class which is a notification of something significant that has happened. Events become more useful in creating custom widgets on our own. An event can happen either within an application or as a result of an outside activity that the application needs to know about. When an event occurs, Qt creates an event object and notifies to the instance of an QObject class or one of its subclasses through their event() function. Events can be generated from both inside and outside the application. For instance, the QKeyEvent and QMouseEvent object represent some kind of keyboard and mouse interaction and they come from the window manager; the QTimerEvent objects are sent to QObject when one of its timers fires, and they usually come from the operating system; the QChildEvent objects are sent to QObject when a child is added or removed and they come from inside of your Qt application.

The users of PySide usually get confused with events and signals. Events and signals are two parallel mechanisms used to accomplish the same thing. As a general difference, signals are useful when using a widget, whereas events are useful when implementing the widget. For example, when we are using a widget like QPushButton, we are more interested in its clicked() signal than in the low-level mouse press or key press events that caused the signal to be emitted. But if we are implementing the QPushButton class, we are more interested in the implementation of code for mouse and key events. Also, we usually handle events but get notified by signal emissions.

Event loop

All the events in Qt will go through an event loop. One main key concept to be noted here is that the events are not delivered as soon as they are generated; instead they're queued up in an event queue and processed later one-by-one. The event dispatcher will loop through this queue and dispatch these events to the target QObject and hence it is called an event loop. Qt's main event loop dispatcher, QCoreApplication.exec() will fetch the native window system events from the event queue and will process them, convert them into the QEvent objects, and send it to their respective target QObject.

A simple event loop can be explained as described in the following pseudocode:

while(application_is_active)
{
  while(event_exists_in_event_queue)
    process_next_event();

  wait_for_more_events();
}

The Qt's main event loop starts with the QCoreApplication::exec() call and this gets blocked until QCoreApplication::exit() or QCoreApplication::quit() is called to terminate the loop. The wait_for_more_events() function blocks until some event is generated. This blocking is not a busy wait blocking and will not burn the CPU resources. Generally the event loop can be awaken by a window manager activity, socket activity, timers, or event posted by other threads. All these activities require a running event loop. It is more important not to block the event loop because when it is struck, widgets will not update themselves, timers won't fire, networking communications will slow down and stop. In short, your application will not respond to any external or internal events and hence it is advised to quickly react to events and return to the event loop as soon as possible.

Event processing

Qt offers five methods to do event processing. They are:

  • By re-implementing a specific event handler like keyPressEvent(), paintEvent()
  • By re-implementing the QObject::event() class
  • Installing an event filter on a single QObject
  • Installing an event filter on the QApplication object
  • Subclassing QApplication and re-implementing notify()

Generally, this can be broadly divided into re-implementing event handlers and installing event filters. We will see each of them in little detail.

Reimplementing event handlers

We can implement the task at hand or control a widget by reimplementing the virtual event handling functions. The following example will explain how to reimplement a few most commonly used events, a key press event, a mouse double-click event, and a window resize event. We will have a look at the code first and defer the explanation after the code:

# Import necessary modules
import sys
from PySide.QtGui import *
from PySide.QtCore import *

# Our main widget class
class MyWidget(QWidget):
  # Constructor function
  def __init__(self):
    QWidget.__init__(self)
    self.setWindowTitle("Reimplementing Events")
    self.setGeometry(300, 250, 300, 100)

    self.myLayout = QVBoxLayout()
    self.myLabel = QLabel("Press 'Esc' to close this App")
    self.infoLabel = QLabel()
    self.myLabel.setAlignment(Qt.AlignCenter)
    self.infoLabel.setAlignment(Qt.AlignCenter)
    self.myLayout.addWidget(self.myLabel)
    self.myLayout.addWidget(self.infoLabel)
    self.setLayout(self.myLayout)

  # Function reimplementing Key Press, Mouse Click and Resize Events
  def keyPressEvent(self, event):
    if event.key() == Qt.Key_Escape:
      self.close()

  def mouseDoubleClickEvent(self, event):
    self.close()

  def resizeEvent(self, event):
    self.infoLabel.setText("Window Resized to QSize(%d, %d)" % (event.size().width(), event.size().height()))
    
if __name__ =='__main__':
  # Exception Handling
  try:
    myApp = QApplication(sys.argv)
    myWidget = MyWidget()
    myWidget.show()
    myApp.exec_()
    sys.exit(0)
  except NameError:
    print("Name Error:", sys.exc_info()[1])
  except SystemExit:
    print("Closing Window...")
  except Exception:
    print(sys.exc_info()[1])

In the preceding code, the keyPressEvent() function reimplements the event generated as a result of pressing a key. We have implemented in such a way that the application closes when the Esc key is pressed. On running this code, we would get a output similar to the one shown in the following screenshot:

Reimplementing event handlers

The application will be closed if you press the Esc key. The same functionality is implemented on a mouse double-click event. The third event is a resize event. This event gets triggered when you try to resize the widget. The second line of text in the window will show the size of the window in (width, height) format. You could witness the same on resizing the window.

Similar to keyPressEvent(), we could also implement keyReleaseEvent() that would be triggered on release of the key. Normally, we are not very interested in the key release events except for the keys where it is important. The specific keys where the release event holds importance are the modifier keys such as Ctrl, Shift, and Alt. These keys are called modifier keys and can be accessed using QKeyEvent::modifiers. For example, the key press of a Ctrl key can be checked using Qt.ControlModifier. The other modifiers are Qt.ShiftModifier and Qt.AltModifier. For instance, if we want to check the press event of combination of Ctrl + PageDown key, we could have the check as:

  if event.key() == Qt.Key_PageDown and
  event.modifiers() == Qt.ControlModifier:
    print("Ctrl+PgDn Key is pressed")

Before any particular key press or mouse click event handler function, say, for example, keyPressEvent() is called, the widget's event() function is called first. The event() method may handle the event itself or may delegate the work to a specific event handler like resizeEvent() or keyPressEvent(). The implementation of the event() function is very helpful in some special cases like the Tab key press event. In most cases, the widget with the keyboard focuses the event() method will call setFocus() on the next widget in the tab order and will not pass the event to any of the specific handlers. So we might have to re-implement any specific functionality for the Tab key press event in the event() function. This behavior of propagating the key press events is the outcome of Qt's Parent-Child hierarchy. The event gets propagated to its parent or its grand-parent and so on if it is not handled at any particular level. If the top-level widget also doesn't handle the event it is safely ignored. The following code shows an example for reimplementing the event() function:

class MyWidget(QWidget):
  # Constructor function
  def __init__(self):
    QWidget.__init__(self)
    self.setWindowTitle("Reimplementing Events")
    self.setGeometry(300, 250, 300, 100)
    self.myLayout = QVBoxLayout()
    self.myLabel1 = QLabel("Text 1")
    self.myLineEdit1 = QLineEdit()
    self.myLabel2 = QLabel("Text 2")
    self.myLineEdit2 = QLineEdit()
    self.myLabel3 = QLabel("Text 3")
    self.myLineEdit3 = QLineEdit()
    self.myLayout.addWidget(self.myLabel1)
    self.myLayout.addWidget(self.myLineEdit1)
    self.myLayout.addWidget(self.myLabel2)
    self.myLayout.addWidget(self.myLineEdit2)
    self.myLayout.addWidget(self.myLabel3)
    self.myLayout.addWidget(self.myLineEdit3)
    self.setLayout(self.myLayout)

  # Function reimplementing event() function
  def event(self, event):
    if event.type()== QEvent.KeyRelease and event.key()== Qt.Key_Tab:
      self.myLineEdit3.setFocus()
      return True
    return QWidget.event(self,event)

In the preceding example, we try to mask the default behavior of the Tab key. If you haven't implemented the event() function, pressing the Tab key would have set focus to the next available input widget. You will not be able to detect the Tab key press in the keyPress() function as described in the previous examples, since the key press is never passed to them. Instead, we have to implement it in the event() function. If you execute the preceding code, you would see that every time you press the Tab key the focus will be set into the third QLineEdit widget of the application. Inside the event() function, it is more important to return the value from the function. If we have processed the required operation, True is returned to indicate that the event is handled successfully, else, we pass the event handling to the parent class's event() function.

Installing event filters

One of the interesting and notable features of Qt's event model is to allow a QObject instance to monitor the events of another QObject instance before the latter object is even notified of it. This feature is very useful in constructing custom widgets comprising of various widgets altogether. Consider that you have a requirement to implement a feature in an internal application for a customer such that pressing the Enter key must have to shift the focus to next input widget. One way to approach the problem is to reimplement the keyPressEvent() function for all the widgets present in the custom widget. Instead, this can be achieved by reimplementing the eventFilter() function for the custom widget. If we implement this, the events will first be passed on to the custom widget's eventFilter() function before being passed on to the target widget. An example is implemented as follows:

def eventFilter(self, receiver, event):
    if(event.type() == QEvent.MouseButtonPress):
      QMessageBox.information(None,"Filtered Mouse Press Event!!",'Mouse Press Detected')
      return True
    return super(MyWidget,self).eventFilter(receiver, event)

Remember to return the result of event handling, or pass it on to the parent's eventFilter() function. To invoke eventFilter(), it has to be registered as follows in the constructor function:

self.installEventFilter(self)

The event filters can also be implemented for the QApplication as a whole. This is left as an exercise for you to discover.

Reimplementing the notify() function

The final way of handling events is to reimplement the notify() function of the QApplication class. This is the only way to get all the events before any of the event filters discussed previously are notified. The event gets notified to this function first before it gets passed on to the event filters and specific event functions. The use of notify() and other event filters are generally discouraged unless it is absolutely necessary to implement them because handling them at top level might introduce unwanted results, and we might end up in handling the events that we don't want to. Instead, use the specific event functions to handle events. The following code excerpt shows an example of re-implementing the notify() function:

class MyApplication(QApplication):
  def __init__(self, args):
  super(MyApplication, self).__init__(args)

  def notify(self, receiver, event):
    if (event.type() == QEvent.KeyPress):
      QMessageBox.information(None, "Received Key Release EVent", "You Pressed: "+ event.text())
    return super(MyApplication, self).notify(receiver, event)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.54.245