Qt - Programmieren in der Sprache des KDE


Die folgenden Betrachtungen beleuchten lediglich einen kleinen Ausschnitt aus dem Qt - Programmierkonzept: den Signal-Slot-Mechanismus.

Ein umfangreiches Beispiel für die Anwendung des Qt-Signal-Slot-Konzepts ist der Noteneditor noteedit

Problem

Wenn einige Klassen einer Klassenbibliothek Widgets darstellen, kommt es häufig zu einem Problem, welches anhand nebenstehenden Beispielprogramms erläutert werden soll:

Auf einem Framewidget f werden:

  • ein Labelwidget l1 der Klasse myLabel (Subklasse von QLabel)
    und
  • ein blatt der Klasse flaeche (Subklasse von QWidget)

erzeugt. Das ansonsten gelbe Widget blatt zeigt ein schwarzes Rechteck. Klickt der Nutzer in dieses Rechteck hinein, so wird die Aufschrift des Labelwidgets l1 in "getroffen" geändert. Klickt er auf einen Punkt außerhalb des Rechtecks, so ändert sich die Aufschrift des Labelwidgets l1 in "daneben".

ansicht1.gif

Ein Ausschnitt aus dem Qt-Quelltext für die QWidget-Subklasse flaeche könnte so aussehen
(flaeche.h):

             class flaeche : public QWidget {
                      private: 
                          QLabel *haedline;
                      public:
                          flaeche(QLabel *l, QFrame *parent);
                      protected:
                          void paintEvent (QPaintEvent * evt);
                          virtual void mousePressEvent ( QMouseEvent * evt);
             };

(flaeche.cpp):
                     .....
               flaeche::flaeche(QLabel *l, QFrame *parent):
	                                    QWidget(parent) {
	                haedline = l;
                     .....
               }

                     .....
               void flaeche::mousePressEvent ( QMouseEvent * evt) {
	            if (evt->x() >= LEFT && evt->x() <= RIGHT && 
	                   evt->y() >= TOP && evt->y() <= BOT) {
		           haedline->setText("getroffen");
	            }
	            else {
		           haedline->setText("daneben");
	            }
              }

Damit ein Objekt der Klasse flaeche die Aufschrift des Label-Widgets ändern kann, wird ihm das Label-Widget bekanntgemacht. Zu diesem Zwecke wird das Label-Widget im Konstruktor übergeben.
(mainwindow.cpp):

                int main( int argc, char *argv[] )
                {
                     QApplication app( argc, argv );

                     QFrame f;
                     myLabel l1(&f);
                     flaeche blatt(&l1, &f);

                     f.setGeometry( 0, 0, 300, 300 );
                     app.setMainWidget( &f );
                     f.show();
                     return app.exec();
               }

Das gesamte Programm ist hier [qtbsp.tgz] zu finden. Schematisch kann das so dargestellt werden:

schema1.gif

Das Objekt f übergibt das Objekt l1 an das Objekt blatt. Das kann zu Problemen führen, wenn es auf Grund der GUI notwendig ist, umfangreichere Strukturen zu erzeugen:

schema2.gif

In diesem Fall wird das Label-Widget l1 an p1 und p3 übergeben, obwohl diese Widgets der Kenntnis von l1 gar nicht bedürfen.

Qt bietet für diesen Fall als Lösung den Signal-Slot-Mechanismus an
(flaeche.h):

                class flaeche : public QWidget {
                     Q_OBJECT
                     public:
                        flaeche(QFrame *parent);
                        void paintEvent (QPaintEvent * evt);
                     signals: 
                        void getroffen(bool ok);
                     protected:
                         virtual void mousePressEvent ( QMouseEvent * evt);
                };

Im Haeder-File wird eine Signal getroffen definiert ...


(flaeche.cpp):
              ....

               flaeche::flaeche(QFrame *parent):
		      QWidget(parent) {
                      ....
               }
               
                      .....

               void flaeche::mousePressEvent ( QMouseEvent * evt) {
                      bool treffer;
                      treffer = evt->x() >= LEFT && evt-<x() <= RIGHT && 
                                evt->y() >= TOP && evt->y() <= BOT;
                      emit getroffen(treffer);
               }

.. welches vom Objekt emittiert wird. Das Signal kann einen oder mehrerer Parameter transportieren. Ein Objekt, das dieses Signale empfangen will, muß über einen Slot gleicher Parameterliste verfügen
(mylabel.h):

                class myLabel : public QLabel {
                     Q_OBJECT
                     public:
                         myLabel(QFrame *parent);
                     public slots: void show_state(bool is_getroffen);
                };

Der Slot wird wie eine "normale" Methode implementiert
(mylabel.cpp):

               void myLabel::show_state(bool is_getroffen) {
                     if (is_getroffen) {
                           setText("getroffen");
                     }
                     else {
                           setText("daneben");
                     }
               }

Zwischen dem Signal und dem Slot erzeugt das QWidget f einen Kanal
(signale.cpp):

                 int main( int argc, char *argv[] )
                 {
                       QApplication app( argc, argv );

                       QFrame f;
                       myLabel l1(&f);
                       flaeche blatt(&f);

                       f.setGeometry( 0, 0, 300, 300 );

                       QObject::connect(&blatt, SIGNAL(getroffen(bool)),
                                        &l1, SLOT(show_state(bool)));
                       app.setMainWidget( &f );
                       f.show();
                       return app.exec();
                }

Die Übergabe von l1 an blatt kann entfallen, da es nun einen Nachrichtenkanal jenseits der Aggregationsstruktur gibt:

schema3.gif

Das gesamte Beispiel ist hier [qtsignal.tgz] zu finden.

Für kompliziertere Fälle verfügt Qt über einen Namensdienst, welcher jedes Qt-Objekt in die Lage versetzt, sich eine Zugangsmöglichkeit zu einem anderen Objekt zu verschaffen:


                 ...

                flaeche::flaeche(QFrame *parent_, char *name):
		           QWidget(parent_, name) {
                        myLabel *label_ptr;
                        QObjectList *list;
                        list = parent()->queryList( "myLabel" , "Label1" );
                        QObjectListIt it(*list);
                        label_ptr = (myLabel *) it.current();
                        if (label_ptr == 0) {
	                        cerr << "internal error" << endl;
                        }
                        QObject::connect(this, SIGNAL(getroffen(bool)),
                                         label_ptr, SLOT(show_state(bool)));
               }

                 ....

Das gesamte Beispiel ist hier [qtsearch.tgz] zu finden.