Events verarbeiten (Event-Handler)

Widget-Klassen reagieren auf sogenannte Events, die in speziellen Methoden der Klasse abgearbeitet werden. Durch Ableiten der Klasse und Überschreiben der Methoden können wir das Verhalten von Widgets selbst festlegen. Die Methoden sind als protected deklariert, wodurch sie auch in abgeleiteten Klassen verwendet werden können und bekommen ein Event-Objekt mit Informationen als Parameter. Event-Objekte liegen - wie allgemein in Qt üblich - immer in einem eigenen Header.
Wir werden an dieser Stelle nur die wichtigsten Events behandeln, weniger gebräuchliche können in der Dokumentation nachgelesen werden. Hier soll vor allem das Prinzip des Überschreibens von Event-Handlern erklärt werden.

Close-Events

Ein typisches Beispiel ist das „Wollen sie das Programm wirklich beenden?“-Popup, wenn man versucht das Programm zu schließen. Um das zu bewirken, müssen wir schlicht die Methode closeEvent() überschreiben und darin eine Messagebox anzeigen.

// main.cpp
#include "TextEdit.h"
#include <QApplication>
 
int main( int argc, char *argv[] )
{
  QApplication app( argc, argv );
  TextEdit widget;
 
  widget.resize( 300, 300 );
  widget.setWindowTitle( "Event-Test" );
  widget.show();
 
  return app.exec();
}
// TextEdit.h
#ifndef TEXTEDIT_H
#define TEXTEDIT_H
 
#include <QTextEdit>
#include <QCloseEvent>
#include <QMessageBox>
 
class TextEdit : public QTextEdit
{
 
  Q_OBJECT
 
  protected:
    void closeEvent( QCloseEvent *event );  // Close-Event von QTextEdit überschreiben
 
};
 
#endif
// TextEdit.cpp
#include "TextEdit.h"
 
void TextEdit::closeEvent( QCloseEvent *event )
{
  // Benutzer Fragen, ob er das Programm wirklich beenden will
  if( QMessageBox::question ( this,             // Parent-Widget
                              "Achtung",        // Fenstertitel
                              "Willst du dieses Programm wirklich beenden?",  // Text
                              QMessageBox::Yes | QMessageBox::No )  // Verfügbare Buttons
                              == QMessageBox::Yes )  // Prüfen, ob "Yes" gedrückt wurde
  {
    // Event wird akzeptiert und muss nicht weiter verarbeitet werden.
    event->accept();
    // Methode der Basisklasse aufrufen, damit das Widget geschlossen wird
    QTextEdit::closeEvent( event );
  }
  else
  {
    // Event wird nicht akzeptiert und weiter verarbeitet.
    event->ignore();
  }
}


Tastatur-Events

Als nächstes wollen wir das vorherige Beispiel dahingehend erweitern, dass es mit der Escape-Taste beendet werden kann. Tastatur-Events werden in der Methode keyPressEvent() behandelt. Um an die gewünschten Informationen zu kommen überschreiben wir also diese Methode. Konstanten zur Identifikation von Tasten finden sich im Qt-Namespace und haben ein Key_-Präfix. Das bedeutet wir fragen in unserem Event-Handler auf Qt::Key_Escape ab und schließen das Widget, falls diese Taste betätigt wurde. Die Konstante kann mit dem Rückgabewert der Methode key() des Event-Objektes verglichen werden.

// TextEdit.h
#ifndef TEXTEDIT_H
#define TEXTEDIT_H
 
#include <QTextEdit>
#include <QCloseEvent>
#include <QKeyEvent>
#include <QMessageBox>
 
class TextEdit : public QTextEdit
{
 
  Q_OBJECT
 
  protected:
    void closeEvent( QCloseEvent *event );  // Close-Event von QTextEdit überschreiben
    void keyPressEvent( QKeyEvent *event ); // Tastatur-Event von QTextEdit überschreiben
 
};
 
#endif
// TextEdit.cpp
#include "TextEdit.h"
 
void TextEdit::closeEvent( QCloseEvent *event )
{
  // Benutzer Fragen, ob er das Programm wirklich beenden will
  if( QMessageBox::question ( this,             // Parent-Widget
                              "Achtung",        // Fenstertitel
                              "Willst du dieses Programm wirklich beenden?",  // Text
                              QMessageBox::Yes | QMessageBox::No )  // Verfügbare Buttons
                              == QMessageBox::Yes )  // Prüfen, ob "Yes" gedrückt wurde
  {
    // Event wird akzeptiert und muss nicht weiter verarbeitet werden.
    event->accept();
    // Methode der Basisklasse aufrufen, damit das Widget geschlossen wird
    QTextEdit::closeEvent( event );
  }
  else
  {
    // Event wird nicht akzeptiert und weiter verarbeitet.
    event->ignore();
  }
}
 
 
void TextEdit::keyPressEvent( QKeyEvent *event )
{
  if( event->key() == Qt::Key_Escape )    // Prüfen ob Escape gedrückt wurde
  {
    event->accept();                      // Event wird akzeptiert
    close();                              // Widget schließen -> closeEvent() wird aufgerufen
  }
  else                                    // Es wurde eine andere Taste als Escape gedrückt
  {
    event->ignore();                      // Event wird nicht akzeptiert
    QTextEdit::keyPressEvent( event );    // Event an die Basisklasse weitergeben
  }
}

Die main-Funktion und die Optik des Programmes ist identisch zum vorherigen Beispiel. Wird nun jedoch im Programm die Escape-Taste gedrückt, wird unsere closeEvent()-Methode aufgerufen und die Messagebox angezeigt. Wichtig ist auch, dass wir in den anderen Fällen die Methode der Basisklasse aufrufen. Tun wir dies nicht, werden alle andern Tastendrücke ignoriert und es kann nichts in das Textfeld eingegeben werden.

Maus-Events

Im Unterschied zu den bisherigen Events gibt es zur Behandlung von Maus-Events mehrere Methoden und Objekte:

  • Mausbewegung: mouseMoveEvent()
  • Maustasten (jeweils für Drücken und Loslassen): mousePressEvent(), mouseReleaseEvent()
  • Doppelklick: mouseDoubleClickEvent()
  • Mausrad: wheelEvent()

Alle Methoden bekommen einen Zeiger auf ein Objekt der Klasse QMouseEvent übergeben. Ausnahme bildet die Methode wheelEvent(), die mit QWheelEvent arbeitet.
Zum Bewegungs-Event ist noch hinzuzufügen, dass die Methode standardmäßig nur aufgerufen, wenn während der Bewegung eine Taste gedrückt wird. Um dieses Verhalten zu ändern muss

QWidget::setMouseTracking( true );

aufgerufen werden.
Der Einfachheit halber geben wir im folgenden Beispiel einfach die Informationen zu den Events aus ohne sie weiter zu verarbeiten:

// main.cpp
#include "MouseInfo.h"
#include <QApplication>
 
int main( int argc, char *argv[] )
{
  QApplication app( argc, argv );
  MouseInfo m;
 
  m.show();
 
  return app.exec();
}
// MouseInfo.h
#ifndef MOUSEINFO_H
#define MOUSEINFO_H
 
#include <QWidget>
#include <QMouseEvent>
#include <QWheelEvent>
#include <iostream>
 
class MouseInfo : public QWidget
{
 
  public:
    MouseInfo();
 
  protected:
    void mouseMoveEvent( QMouseEvent *event );         // Mausbewegungs-Event überschreiben
    void mousePressEvent( QMouseEvent *event );        // Event für gedrückte Maustasten überschreiben
    void mouseReleaseEvent( QMouseEvent *event );      // Event für losgelassene Maustasten überschreiben
    void mouseDoubleClickEvent( QMouseEvent *event );  // Event für Doppelklicks überschreiben
    void wheelEvent( QWheelEvent *event );             // Mausrad-Event überschreiben
 
};
 
#endif // MOUSEINFO_H
// MouseInfo.cpp
#include "MouseInfo.h"
 
MouseInfo::MouseInfo()
{
  resize( 400, 400 );
}
 
 
void MouseInfo::mouseMoveEvent( QMouseEvent *event )
{
  // Koordinaten der Maus ausgeben
  std::cout << "Maus wurde mit gedrueckter Taste bewegt; neue Koordinaten: " << event->x()
            << " / " << event->y() << std::endl;
}
 
 
void MouseInfo::mousePressEvent( QMouseEvent *event )
{
  // Name der Maustaste und Koordinaten ausgeben
  switch( event->button() )
  {
    case Qt::LeftButton:
      std::cout << "Linke ";
      break;
    case Qt::RightButton:
      std::cout << "Rechte ";
      break;
    case Qt::MidButton:
      std::cout << "Mittlere ";
      break;
    default:
      std::cout << "Unbekannte ";
      break;
  }
  std::cout << "Maustaste wurde an den Koordinaten " << event->x()
            << " / " << event->y() << " gedrueckt" << std::endl;
}
 
 
void MouseInfo::mouseReleaseEvent( QMouseEvent *event )
{
  // Name der Maustaste und Koordinaten ausgeben
  switch( event->button() )
  {
    case Qt::LeftButton:
      std::cout << "Linke ";
      break;
    case Qt::RightButton:
      std::cout << "Rechte ";
      break;
    case Qt::MidButton:
      std::cout << "Mittlere ";
      break;
    default:
      std::cout << "Unbekannte ";
      break;
  }
  std::cout << "Maustaste wurde an den Koordinaten " << event->x()
            << " / " << event->y() << " ausgelassen" << std::endl;
}
 
 
void MouseInfo::mouseDoubleClickEvent( QMouseEvent *event )
{
  // Koordinaten des Doppelklicks ausgeben
  std::cout << "Doppelklick an den Koordinaten " << event->x()
            << " / " << event->y() << std::endl;
}
 
 
void MouseInfo::wheelEvent( QWheelEvent *event )
{
  // Gradänderung ausgeben; Der Rückgabewert von 'delta' entspricht der 8-fachen Gradzahl
  std::cout << "Mausrad wurde um " << event->delta() / 8 << "° gedreht" << std::endl;
}

Paint-Events

Qt bietet uns bereits eine Vielzahl an fertigen Widgets, aber wir können natürlich auch selbst auf Widgets zeichnen. Möglich wird dies durch das Überschreiben des Event-Handlers paintEvent(). Dazu müssen wir in dieser Methode eine Instanz von QPainter mit unserem Widget (also this) als Parameter für den Konstruktor erstellen.
QPainter macht es uns recht einfach einfache geometrische Formen und Text auf Widgets zu zeichnen. Aufgrund der Qt-internen Architektur sollte man QPainter-Objekte nur im Haupt-Thread verwenden!

Brush und Pen

Brush und Pen bestimmen die Farbe der Zeichnung, wobei Brush für die Füllfarbe und Pen für Linien und Rahmen verwendet wird. Dabei können wir entweder auf die Klasse QColor zurückgreifen, oder diese implizit durch Verwendung des Enums Qt::GlobalColor erstellen und an die jeweilige set-Methode übergeben:

QPainter painter( this );
painter.setPen( Qt::blue );
painter.setBrush( Qt::white );

Primitive Formen zeichnen

QPainter bietet uns fertige Funktionen zum Zeichnen von primitiven geometrischen Formen wie Linien, Kreisen, Ellipsen und Rechtecken. Als einfaches Beispiel zeichnen wir jetzt die Diagonalen des Widgets blau ein:

// main.cpp
#include "PaintWidget.h"
#include <QApplication>
 
int main( int argc, char *argv[] )
{
  QApplication app( argc, argv );
  PaintWidget widget;
 
  widget.resize( 300, 300 );
  widget.setWindowTitle( "Paint-Test" );
  widget.show();
 
  return app.exec();
}
// PaintWidget.h
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H
 
#include <QWidget>
 
class PaintWidget : public QWidget
{
 
  protected:
    void paintEvent( QPaintEvent *event );
 
};
 
#endif // PAINTWIDGET_H
// PaintWidget.cpp
#include "PaintWidget.h"
#include <QPainter>
 
void PaintWidget::paintEvent( QPaintEvent *event )
{
  QPainter painter( this );
 
  // Hintergrund weiß füllen
  painter.setBrush( Qt::white );
  painter.drawRect( 0, 0, width(), height() );
 
  // Blaue als Farbe für die Linien
  painter.setPen( Qt::blue );
  // Blaue Linie von links oben nach rechts unten
  painter.drawLine( 0, 0, width(), height() );
  // Blaue Linie von rechts oben nach links unten
  painter.drawLine( width(), 0, 0, height() );
}




Text auf Widgets zeichnen

Das manuelle Zeichnen von Text auf Widgets ist ebenfalls möglich und funktioniert analog zu geometrischen Formen. Pen dient dabei als Schriftfarbe, die Schriftart kann über ein QFont-Objekt festgelegt werden.
Im nächste Beispiel wird der Text „proggen.org“ in blauer Monospace-Schrift, fett und mit Schrfitgröße 30 zentriert auf ein Widget gezeichnet:

// main.cpp
#include "PaintWidget.h"
#include <QApplication>
 
int main( int argc, char *argv[] )
{
  QApplication app( argc, argv );
  PaintWidget widget;
 
  widget.resize( 300, 150 );
  widget.setWindowTitle( "Text-Test" );
  widget.show();
 
  return app.exec();
}
// PaintWidget.h
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H
 
#include <QWidget>
 
class PaintWidget : public QWidget
{
 
  protected:
    void paintEvent( QPaintEvent *event );
 
};
 
#endif // PAINTWIDGET_H
// PaintWidget.cpp
#include "PaintWidget.h"
#include <QPainter>
#include <QFont>
 
void PaintWidget::paintEvent( QPaintEvent *event )
{
  QPainter painter( this );
 
  // Blauer Text
  painter.setPen( Qt::blue );
  // Schriftart: Monospace, 30 pt und fett
  painter.setFont( QFont( "Monospace", 30, QFont::Bold ) );
  // Zentriert auf das Widget zeichnen
  painter.drawText( 0, 0, width(), height(),
                    Qt::AlignHCenter | Qt::AlignVCenter, 
                    "proggen.org" );
}




Abschließendes

Dieser Artikel soll nur einen Überblick über das Konzept der Event-Verarbeitung in Qt bieten. Es gibt noch ein paar andere Events und eine Vielzahl von Methoden die hier nicht besprochen wurden. Genauere Informationen finden sich wie immer in der Dokumentation.