17.14 EVENT PROCESSING IN Qt


17.14 EVENT PROCESSING IN Qt

Event processing in C++-based Qt is carried out in two different ways, depending on whether an event is high-level or low-level. The distinction between the two is the same here as it was for Java. But since Qt uses two different modes of processing for these two different types of events, we will revisit this distinction.

When a user clicks the mouse on a GUI button, we have a high-level event, in the sense that the GUI button stands for some action that is supposed to be initiated by the mouse click. Similarly, selecting a menu item gives rise to a high-level event. Entering text in a text window would also constitute a high-level event. In fact, as was the case with Java, most widgets in Qt when subject to human interaction produce high-level events in the form of signals. High-level events are handled by the signal-slot mechanism of Qt.

On the other hand, clicking a mouse or dragging a mouse inside a widget for the purpose of, say, free-form drawing gives rise to a low-level event; low-level in the sense that all we want to know is the position of the mouse pointer on the screen and, in some cases, the identity of the mouse button pressed. Low-level events in Qt are handled through the mechanism of virtual functions. Certain virtual functions defined for a widget are automatically invoked upon the occurrence of low-level events. Since the functions are virtual, it's the user-defined override definitions for the functions that would actually be invoked. In this manner, a given application can exhibit a desired behavior in response to low-level events.

This section will only deal with the signal-slot mechanism for the handling of high-level events, since that is the dominant mode for dealing with events in Qt. The virtual function mechanism for handling low-level events will be presented in Section 17.20.

Section 17.6 showed a Qt program in which the signal emitted by a GUI button was used to terminate the event processing loop and the program. To further illustrate high-level event processing in Qt, in this section we will discuss two programs. In the first, we will illustrate how a signal emitted by one widget can be used to affect the behavior of another widget. Next, in a more elaborate program, we will show how the user can create his/her own widget class with its own signals and slots. That example will also take us to the topic of meta object compilation in Qt. However, before getting into these two examples, we will explain in greater detail the signal-slot mechanism of Qt.

As explained in the previous two sections, every widget available for human interaction emits a signal in response to that interaction. In Qt, if an object wishes to be notified of a signal emitted by a widget, the class corresponding to the object must provide a slot for the signal and you must explicitly connect the signal with the slot. The minimal syntax for connecting a signal with a slot is

      QObject::connect(pointerToWidgetEmittingSignal,                       SIGNAL( signalName( paramTypeOnly) ),                       pointerToObjectReceivingSignal,                       SLOT(slotName( paramTypeOnly) ) ); 

where the first argument is a pointer to the object emitting the signal; the second, enveloped by the keyword SIGNAL, is the name of the signal together with its parameter type(s); the third is a pointer to the object that wants to be notified of the signal; and the fourth, enveloped by the keyword SLOT, is the name of the slot function in the destination object. So, as we did in the example in Section 17.6, if we want our top-level QApplication object to include a pushbutton for closing the top-level application, we'd need to connect the clicked () signal of the pushbutton with the quit() slot of the QApplication object:

      QApplication myapp( argc, argv);           //argc, argv from main header      QPushButton* myButton = new QPushButton ("Quit");      QObject::connect (myButton,                        SIGNAL (clicked() ),                        &myapp,                        SLOT (quit() ) ); 

For another example,

      QObject::connect (myMenu,                     SIGNAL (activated (int) ),                     pointerToObjectReceivingSignal,                     SLOT (slotDoMenuFunction (int) ) ); 

In the example that follows, we use predefined classes with signals and slots and therefore we do not need to invoke the meta object compiler. This example, which is only a slight variation of an example provided by Dalheimer [14], consists of connecting a signal from the QSlider widget with a slot of the QLCDNumber widget. The program constructs a QSlider object in line (A) and a QLCDNumber object in line (B). The statement in line (D) connects the valueChanged (int) signal of the QSlider object with the slot display (int) of the QLCDNumber object. Note how, in line (C), the number displayed initially by the QLCDNumber object is set by a direct invocation of the slot function display (int).

 
//SignalSlotLCD.cc //Based on a program by Dalheimer with //inconsequential made by the author #include <qapplication.h> #include <qslider.h> #include <qlcdnumber.h> int main (int argc, char **argv) { QApplication myApp (argc, argv); QWidget* myWidget= new QWidget(); myWidget->setGeometry (400, 300, 170, 110); QSlider* myslider = new QSlider (0, // minimum value //(A) 9, // maximum value 1, // step 1, // initial value QSlider::Horizontal, // orient. myWidget); // parent myslider->setGeometry (10, 10, 150, 30); //first arg below is the number of digits to display: QLCDNumber* mylcdnum = new QLCDNumber (1, myWidget); //(B) mylcdnum->setGeometry (60, 50, 50, 50); //manual invocation of slot: mylcdnum->display (1); //(C) // connect slider and number display QObject::connect (myslider, //(D) SIGNAL (valueChanged (int) ), mylcdnum, SLOT (display (int) ) ); myApp.setMainWidget (myWidget); myWidget->show(); // starts event loop return myApp.exec(); }

This program can be compiled with a simple command line call to the compiler, as in

      g++ -o SignalSlotLCD SignalSlotLCD.cc \                       -I$QTDIR/include -L$QTDIR/lib -lqt 

The program produces a GUI that looks like what is shown in Figure 17.26.

click to expand
Figure 17.26

17.14.1 A Qt Example that requires Meta Object Compilation

In this subsection we will consider a more elaborate example consisting of a class for which we define our own signals and slots. The syntax of a class for which you want to define your own signals or slots looks like

      class MyClass : public QObject {           Q_OBJECT           // ..           signals:                void userDidSomething()                // other signals ..           public slots:                void doSomethingReactToSignal();                // other public slots ..           private slots:                void doSomethingInternally();                // other private slots                // rest of code      }; 

where Q_OBJECT is a macro that must be declared inside the private section of the class. Qt has a special procedure for compiling such classes. Before regular compilation, such classes must first be run through a meta object compiler. The meta object compiler, moc, generates meta object code, also referred to as "glue code," that is needed for the signal/slot mechanism to work. This meta object code is in the form of another source file that is subsequently subject to regular compilation, as explained further in the following list of compilation steps for a Qt class with signals and/or slots:

  1. Separate the class declaration from its implementation. So if MyWidget has signals and/or slots, create the following three files:

          MyWidget.h      MyWidget.cc      main.cc 
  2. Run the declaration file, MyWidget.h, through the meta object compiler, moc. The moc compiler will output a source file containing meta object code. The name of the output file will be moc_MyWidget.cc.

  3. Now compile the meta object file moc_MyWidget.cc to create the binary file moc_MyWidget.o.

  4. Compile the implementation source file MyWidget.cc to create the binary file MyWidget.o.

  5. Compile the source file main.cc to create a binary file main.o.

  6. Link the binary files moc_MyWidget.o, MyWidget.o and main.o to create the executable.

In the example that follows, we will now present a Qt version of the Java Crazy-Window program of Section 17.13.1. As with the Java program, the Qt program creates two side-by-side panels, one for a user to enter text in and the other for drawing shapes. As the reader will recall from our Java discussion, the idea behind the example is that the user enters some sort of a story in the left panel-let's say a story involving the names of various colors. Each time the user mentions the name of a color, a square block of that color pops up at a random location in the right panel.

The top-level header file is CrazyWindow.h and its implementation CrazyWindow.cc. Following the declaration in line (A) of the header file, the implementation in CrazyWindow.cc first declares a layout manager for the top-level window in line (C) and then creates an instance of MyTextPanel for the left panel and an instance of MyDrawPanel for the right panel in lines (D) and (E), respectively.

As shown in line (F), another important task of the top-level CrazyWindow class is to establish a connection between the signal userTypedKeyword(char*) of the MyTextPanel object and the slot function drawColoredSquare(char*) of the MyDrawPanel object. The overall "flow" of signals in the program is as shown in Figure 17.27.

click to expand
Figure 17.27

 
//////////////////////// file: CrazyWindow.h /////////////////////// #ifndef CRAZYWINDOW_H #define CRAZYWINDOW_H #include <qwidget.h> class CrazyWindow: public QWidget { //(A) public: CrazyWindow (QWidget *parent=0, const char* name= 0); }; #endif /////////////////////// file: CrazyWindow.cc /////////////////////// #include "CrazyWindow.h" #include <qpainter.h> #include <qlayout.h> #include "MyTextPanel.h" #include "MyDrawPanel.h" CrazyWindow::CrazyWindow (QWidget* parent, const char* name) //(B) : QWidget(parent, name) { QGridLayout* grid = new QGridLayout(this, 0, 1); //(C) MyTextPanel* textPanel = new MyTextPanel(this, "for text only"); //(D) MyDrawPanel* drawPanel = new MyDrawPanel(this, "for graphics only"); //(E) grid->addWidget(textPanel, 0, 0); grid->addWidget(drawPanel, 0, 1); QObject::connect(textPanel, //(F) SIGNAL(userTypedKeyword(char*) ), drawPanel SLOT(drawColoredSquare(char*) ) ); }

It is the job of the MyTextPanel object to receive the keystrokes from the user, to trap the signal textChanged() via the slot function textChangedDoSomething(), test the words entered by the user for the color names, and to emit the signal userTypedKeyword(char*) when the user has entered a color name. The class must therefore define its own signal userTypedKeyword (char*), as done in line (G) below, and its own slot function textChangedDoSomething(), as declared in line (H) and implemented in line (J). The logic in textChangedDoSomething is the same as for insertUpdate for the Java example.

 
//////////////////////// file: MyTextPanel.h /////////////////////// #ifndef MYTEXTPANEL_H #define MYTEXTPANEL_H #include <qmultilineedit.h> class MyTextPanel: public QMultiLineEdit { Q_OBJECT public: MyTextPanel (QWidget *parent=0, const char* name= 0); signals: //This signal is connected in class CrazyWindow //with slot function drawColoredSquare of class //MyDrawPanel in line (F) void userTypedKeyword(char*); //(G) public slots: //The signal textChanged() emitted by an object //of type "this" is connected to this slot function //in this class in line (I) void doSomethingTextChanged(); private: QString word; }; #endif /////////////////////// file: MyTextPanel.cc /////////////////////// #include "MyTextPanel.h" #include <qtextstream.h> #include <stdlib.h> // for malloc() MyTextpanel::MyTextPanel(QWidget* parent, const char* name) :QMultiLineEdit(parent, name) { word = QString(""); setPalette(QPalette(QColor(250, 250, 200) ) ); //MyTextPanel inherits the signal textChanged() //from its superclass QMultiLineEdit QObject::connect(this, //(I) SIGNAL(textChanged() ), this, SLOT(doSomethingTextChanged() ) ); } void MyTextPanel::doSomethingTextChanged() { //(J) QString qstr = text(); QChar c = qstr[ (int) qstr.length() - 1]; if (c == ' ' || c == '\n') { if (word == "red" || word == "blue" || word == "orange"|| word == "green") { char* keyword = (char*) malloc(word.length() + 1); strcpy(keyword, word); emit(userTypedKeyword(keyword) ); } word = QString(""); } else word += c; }

Finally, we will present the MyDrawPanel class which corresponds to the right panel in the top-level display. The class has defined for it one slot function, drawColoredSquare(char*), whose job is to trap the signal userTypedKeyword (char*) emitted by a MyTextPanel object. The rest of the class is self-explanatory, save for the function sizePolicy(). The implementation code provided in MyDrawPanel.cc includes invocation of the following QSizePolicy constructor in line (K) in the code for sizePolicy():

      QSizePolicy MyDrawPanel::sizePolicy() const {         return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);      } 

The QSizePolicy object returned by sizePolicy() function says that the MyDrawPanel object is to occupy as much space as possible both horizontally and vertically. The prototype of the constructor is

      QSizePolicy(SizeType hor, SizeType ver, bool hvw = FALSE) 

where SizeType is the following enum

      enum SizeType {           Fixed = 0,           Minimum = MayGrow,           Maximum = MayShrink,           Preferred = MayGrow|MayShrink,           MinimumExpanding = Minimum|ExpMask,           Expanding = MinimumExpanding | MayShrink      }; 

where the identifiers used on the right of the assignment operators are defined by the following enum:

     enum { HSize = 6.          HMask = Ox3f,          VMask = HMask << HSize,          MayGrow = 1          ExpMask = 2          MayShrink = 4     }; 

Both these enumerations are defined in the header file qsizepolicy.h. Assigning Expanding to the first two arguments in the QSizePolicy constructor causes the MyDrawWindow to occupy all the space that can be assigned by the layout manager. Without setting the sizing policy in the manner shown, it would be possible for the first widget, a MyTextPanel object, to occupy all the visible space in the top-level object.

 
//////////////////////// file: MyDrawPanel.h /////////////////////// #ifndef MYDRAWPANEL_H #define MYDRAWPANEL_H #include <qwidget.h> class MyDrawPanel: public QWidget { Q_OBJECT public: MyDrawPanel(QWidget *parent=0, const char* name= 0); QSizePolicy sizePolicy() const; void paintEvent(QPaintEvent*); public slots: void drawColoredSquare(char*); }; #endif /////////////////////// file: MyDrawPanel.cc /////////////////////// #include "MyDrawPanel.h" #include <string.h> #include <qpainter.h> #include <qwidget.h> #include <stdlib.h> // for rand() #include <time.h> // for time(NULL) to seed rand() MyDrawPanel::MyDrawPanel(QWidget* parent, const char* name) : QWidget(parent, name) { setPalette(QPalette(QColor(250, 250, 200) ) ); srand( (unsigned) time(NULL) ); } void MyDrawPanel::paintEvent(QpaintEvent*) { QPainter p(this); } void MyDrawPanel::drawColoredSquare (char* key) { QPainter p(this); p.setBrush(QString(key) ); p.setPen(NoPen); int x = rand() % 250 + 1; int y = rand () % 300 + 1; p.drawRect(QRect(x, y, 30, 30) ); } QSizePolicy MyDrawPanel::sizePolicy() const { //(K) return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); }

We also need a main for the entire program to work:

 
//////////////////// file: main_CrazyWindow.cc ///////////////////// #include <qapplication.h> #include "CrazyWindow.h" int main(int argc, char ** argv) { QApplication::setColorSpec(QApplication::CustomColor); QApplication a(argc, argv); CrazyWindow w; w.setGeometry(0, 0, 700, 500); a.setMainWidget(&w); w.show() a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()) ); return a.exec(); }

We will now show the makefile that can be used with this program. If you name the makefile_Qt_CrazyWindow, you can build the executable by

    make -f Makefile_Qt_CrazyWindow 

 
#Makefile_Qt_CrazyWindow CC=g++ LDLIBS=-L$(QTDIR)/lib -lqt CFLAGS=-g -I$(QTDIR)/include CrazyWindow: moc_CrazyWindow.o moc_MyDrawPanel.o moc_MyTextPanel.o \ CrazyWindow.o MyDrawPanel.o MyTextPanel.o \ main_CrazyWindow.o Makefile_Qt_CrazyWindow $(CC) $(LDLIBS) -o CrazyWindow moc_CrazyWindow.o \ moc_MyDrawPanel.o moc_MyTextPanel.o CrazyWindow.o \ MyDrawPanel.o MyTextPanel.o main_CrazyWindow.o moc_CrazyWindow.cc: CrazyWindow.h moc -o moc_CrazyWindow.cc CrazyWindow.h moc_MyDrawPanel.cc: MyDrawPanel.h moc -o moc_MyDrawPanel.cc MyDrawPanel.h moc_MyTextPanel.cc: MyTextPanel.h moc -o moc_MyTextPanel.cc MyTextPanel.h moc_CrazyWindow.o: moc_CrazyWindow.cc $(CC) -c $(CFLAGS) -02 moc_CrazyWindow.cc moc_MyDrawPanel.o: moc_MyDrawPanel.cc $(CC) -c $(CFLAGS) -02 moc_MyDrawPanel.cc moc_MyTextPanel.o: moc_MyTextPanel.cc $(CC) -c $(CFLAGS) -02 moc_MyTextPanel.cc CrazyWindow.o: CrazyWindow.cc $(CC) -c $(CFLAGS) -02 CrazyWindow.cc MyDrawPanel.o: MyDrawPanel.cc $(CC) -c $(CFLAGS) -02 MyDrawPanel.cc MyTextPanel.o: MyTextPanel.cc $(CC) -c $(CFLAGS) -02 MyTextPanel.cc main_CrazyWindow.o: main_CrazyWindow.cc $(CC) -c $(CFLAGS) -02 main_CrazyWindow.cc clean: rm -f CrazyWindow rm -f *.o rm -f moc*.*

The GUI produced by this program is shown in Figure 17.28.

click to expand
Figure 17.28

17.14.2 Summary of Facts about Signals and Slots

Here is a list of important facts about Qt's signals and slots. This list is reproduced from [14]:

  • You never have to implement signals directly. You just declare them. In other words, there is no implementation code associated with the signals.

  • Slots are declared and implemented just like any other C++ member function. They can also be called and used like any other member function.

  • A slot will generally be public, but you can make it protected. It does not make much sense for a slot to be private.

  • A slot function can be virtual.

  • A slot function cannot be static.

  • You can connect any number of slots to a signal, and you can connect any number of signals to a slot.

  • If more than one slot is connected to a signal, the order in which the slots are called is not guaranteed.

  • In order to be connected to a signal, a slot must have the same parameter types as the signal.

  • Signals and slots are only available within a C++ class; you cannot make a stand-alone function a slot.

  • Every class that wants to define its own signals or slots must be derived, directly or indirectly, from QObject.

  • Every class that wants to define its own signals or slots must contain the macro Q_OBJECT somewhere.

  • Do not put a semicolon after the Q_OBJECT macro.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net