Chapter 4. Events and Signals

In the chapters that we saw so far, we tried out various implementations of readily available functions to build a base GUI by extending the Qt objects without handling custom events. In this chapter, we will look into some of the internal implementations of working concepts of these functions in the context of events and signals. Qt being an event-driven UI 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 general, is some change in state in discrete time that is induced by external or internal actions. An event action in Qt is an object that is 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 the instance of a QObject class or one of its subclasses through the event() function. Events can be generated from both inside and outside the application. For instance, the QKeyEvent and QMouseEvent objects 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 that are 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, such as 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 to validate on mouse press, we are more interested in the implementation of code for mouse press or key down events. Hence, events are generated by an external entity, such as keyboard, mouse, and so on, and when we want to get notified, signals are used. Don't worry if you don't get it, we will go through this in the next sections.

Event loop

All the events in Qt will go through an event loop. One of the main key concepts 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, QApplication.exec(), will fetch the native window system events from the event queue and will process them, convert them into the QEvent objects, and send them to their respective target QObject.

A simple event loop can be explained as described in the following pseudo-code:

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 QApplication::exec() call, and this gets blocked until QApplication::exit() or QApplication::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 exhaust CPU resources. The event loops take cares of a lot of background activities, such as draw, redraw, click, press, notification; as well as your custom events, such as calling web services for data, and a lot more. Hence, it is very important not to block the event as failing as blocking will make the application freeze. Generally, the event loop can be awoken by a window manager activity, socket activity, timers, or events posted by other threads. All these activities require a running event loop.

Note

It is more important not to block the event loop because when it is struck, widgets will not update themselves, timers won't fire, and networking communications will slow down and stop.

In short, your application will not respond to any external or internal events (because this code takes place behind the scenes of the event loop operation), 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 perform event processing. They are as follows:

  • Reimplementing a specific event handler, such as keyPressEvent(), and paintEvent()
  • Reimplementing the QObject::event() class
  • Installing an event filter on a single QObject
  • Installing an event filter on the QApplication object
  • Subclassing QApplication and reimplementing notify()

Generally, this can be broadly divided into reimplementing event handlers and installing event filters. We will look at each of them in 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 of the most commonly used events, such as 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 for after the code:

        # Import necessary modules
import sys
from PySide.QtGui import  QmainWindow, QApplication

# Our main widget class
class MyWidget(QWidget):
    # Constructor function
    def __init__(self):
        super.(MyWidget,self).__init__()
        self.initGUI()

    def initGUI(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)
        self.show()

    # 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()
        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 that is generated as a result of pressing a key. We implemented it in such a way that the application closes when the Esc key is pressed. On running this code, we will get an output that is 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() which would be triggered on release of the key. Normally, we are not very interested in the key release events except for the keys that are important. The specific keys for which the release event holds importance are the modifier keys, such as Ctrl, Shift, and Alt. These keys are called modifier keys, and they 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 a combination of the Ctrl + PageDown keys, we could perform the check as follows:

  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 it may delegate the work to a specific event handler, such as resizeEvent() or keyPressEvent(). The implementation of the event() function is very helpful in some special cases, such as the Tab key press event. In most cases, the widget with the keyboard that focuses the event() method will call setFocus() on the next widget in the tab order, and it will not pass the event to any of the specific handlers. So, we may have to reimplement 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 grandparent, 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 of how to reimplement the event() function:

# 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):
        super(MyWidget,self).__init__()
        self.initGUI()

    def initGUI(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)
        self.show()

    # 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 will have set the 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, because the key press is never passed to them. Instead, we have to implement this in the event() function. If you execute the preceding code, you will 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; otherwise, we pass the event handling to the parent class's event() function.

As you noticed in Linux, the example allowed you to move the focus to the first text box and then shifted focus back to the third text box; and the event implementation is only for the KeyRelease event and not the KeyPress event. This is because keys, such as tab, arrow, and space, have special functionality and KeyPress is handled by the parent window handler and not passed onto the custom event handler that is defined. To handle these special events and others, we can use the event filter that is discussed next.

Installing event filters

An interesting and notable feature 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 comprised 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 this 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)

In the reimplementation of the event handler session, we discussed that key press events for keys, such as tab, arrow, and space, cannot be captured. The following code snippet will help you understand how eventFilter can be used to capture these key press events:

  def eventFilter(self, receiver, event):
        if event.type()== QEvent.KeyPress and event.key()== Qt.Key_Tab:
            self.myLineEdit3.setFocus()
            return True
        return super(MyWidget,self).eventFilter(receiver, event)
if __name__ =='__main__':
    # Exception Handling
    try:
        myApp = QApplication(sys.argv)
        myWidget = MyWidget()
        myApp.installEventFilter(myWidget)
        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])

There is also a change in the main function, the installEventFilter is moved from MyWidget to the main function, and the event is registered for QApplication level. Now, you can run the program and see how the difference in Tab press is handled.

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 that were 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 is 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 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 reimplementing 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.119.142.232