Qt/C++ による GUI ソフト 5:イベント処理

ここで使っている Qt3DExtras::Qt3DWindow は非推奨なので,代替法が分かり次第改訂します

このページでは,まず QWidget の継承クラスを作成して QWidget のイベントハンドラをいくつかオーバーライドしてみます.
数えてみたら,QWidget のイベントハンドラは 26 個ありました.

次に 3D レンダリング用ウィンドウである Qt3DExtras::Qt3DWindow の継承クラスを作成して Qt3DWindow のイベントハンドラをいくつかオーバーライドしてみます.Qt3DWindow のイベントハンドラは基底クラス QWindow で定義されています.
QWindow のイベントハンドラは 19 個ありました.QWidget と同じハンドラには同じ関数名がついています.

さらに,Qt3dWindow を埋め込んだ QWidget をインスタンス化し,Qt3dWindow のイベントハンドラが機能することを確認します.これは,Qt3DWIndow がレイアウト管理できることを意味します.

Before : 4:3D レンダリングウィジェットの作成 Next : 6:レンダリング その1

インフォメーション

イベントシステムについて

イベント全般については,The Event System に解説があります.

イベントは抽象クラス QEvent から派生したオブジェクトです,種々のイベント型を表現する派生クラスがあります,その一覧は Event Classes で見ることができます.イベントの例として,"The Event System" では,QResizeEvent,QPaintEvent,QMouseEvent,QKeyEvent,QCloseEvent が挙げられています.
複数のイベントに対応したイベントクラスもあります.例えば,QMouseEvent は,マウスボタンの押し下げ,ダブルクリック,移動その他をサポートしています.

イベントは,各種ウィジェットなどの,QObject 継承したクラスのインスタンスで受信および処理できます.
イベントが発生すると,そのイベントを表すイベントオブジェクトが作成されます.そのイベントオブジェクトが,QObject 継承クラスの event() 関数を呼び出します.ただし,event() 関数はイベントそのものを処理するのではありません.配信さされたイベント型に基づいて,特定のイベントハンドラを呼び出すだけです.

例えば,QWidget のクラスリファレンスを見ると.種々のイベントハンドラが定義されています.
virtual void xxxEvent(QxxxEvent *event)
というのがそれです.イベントハンドラの引数は,配信されたイベントです.イベントハンドラの処理がイベント配信の反応ということになります.

インストールした Debian パッケージ

このページは Debian 13 trixie で作成しています.

パッケージ名(バージョン)
g++(14.2.0)GNU C++ コンパイラ
make(4.4.1)コンパイルを制御するユーティリティ
qt6-base-dev(6.8.2)Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル
qt6-3d-dev(6.8.2)Qt 3D ライブラリを使用した Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル

目次(ページ内リンク)


QWidget のイベントハンドラを実装してみる

ここでは,QWidget を継承したクラスを作成し,そのなかでいくつかのイベントハンドラをオーバーライドします.
継承クラスを作成するという点以外は,1:メインウィンドウで QWidget をインスタンス化した手順と同じです.

ソースコード

my_widget.h

QWidget を継承したクラス My_Widget を宣言します.
QWidget クラス に列挙されているイベントハンドラ virtual void xxxEvent(QxxxEvent *event) のなかから 3 個をピックアップしました.


#include <QWidget>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QKeyEvent>

class My_Widget : public QWidget {
 protected:
  void resizeEvent(QResizeEvent *event);  //ウィジェットの大きさを変更したとき.QResizeEvent というイベントを受信して処理するイベントハンドラ
  void mousePressEvent(QMouseEvent *event);  //カウスボタンを押し下げたとき
  void keyPressEvent(QKeyEvent *event);  //キーボードを操作したとき
};

my_widget.cc

My_Widget のイベントハンドラを実装します.
各イベントクラスのドキュメントページを見ると,"List of all members, including inherited members" と記されたリンクあります.そのページを見れば,利用できるイベント情報が判ります.
イベントから得られる情報は,クラスオブジェクトであったり列挙体であったりするのでリンクを張ってみました.


#include "my_widget.h"
#include <iostream>

void My_Widget::resizeEvent(QResizeEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 //List of All Members for QResizeEvent からいくつか利用
 std::cout << "type ; " << event->type() << std::endl;  //type() の戻り値は enum QEvent::Type

 QSize size = event->size();
 std::cout << "height ; " << size.rheight() << ", width : " << size.rwidth() << std::endl;
}

void My_Widget::mousePressEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 //List of All Members for QMouseEvent からいくつか利用
 std::cout << "type ; " << event->type() << std::endl;

 Qt::MouseButton button = event->button();  //button() の戻り値は enum Qt::MouseButton
 std::cout << "button ; " << button << std::endl;

 QPointF point = event->position();
 std::cout << "point ; " << point.rx() << ' ' << point.ry() << std::endl;
}

void My_Widget::keyPressEvent(QKeyEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 //List of All Members for QKeyEvent からいくつか利用
 std::cout << "type : " << event->type() << std::endl;

 std::cout << "key : " << event->key() << std::endl;  //key() の戻り値は enum Qt::Key
}

main.cc


#include <QApplication>
#include "my_widget.h"

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

 My_Widget widget;
 widget.resize(320, 240);
 widget.show();

 return app.exec();
}

tmp.pro


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
SOURCES += main.cc my_widget.cc
HEADER += my_widget.h
QT += widgets

ビルドと実行

My_Window の画面

1:メインウィンドウ以来同じ手順です.ソースファイルが tmp というディテクトリにあるとします.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &

ウィジェットが表示されると同時に,端末に

virtual void My_Widget::resizeEvent(QResizeEvent*)
type ; 14
height ; 240, width : 320

と出力されました.起動時にresizeEvent() が呼び出されるということが判りました.
マウスでウィジェットの大きさを変更すると,同様に出力されます.
ウィジェットを左クリックしてみると,

virtual void My_Widget::mousePressEvent(QMouseEvent*)
type ; 2
button ; 1
point ; 125 149

と出力されました.enum Qt::MouseButton で確認してみると,左ボタンは Qt::LeftButton 0x00000001 と書いてありました.
キーボードで q キーを押してみたところ,次の等に出力されました.

virtual void My_Widget::keyPressEvent(QKeyEvent*)
type : 6
key : 81

Qt3DWindow のイベントハンドラを実装してみる

Qt3DWindow の基底クラス QWindow Class にも QWidget 同様のイベントハンドラが定義されています.
virtual void xxxEvent(QxxxEvent *ev)
というのがそれで,QWindow クラスの継承クラスである Qt3DWindow をさらに継承したクラスを作成してもオーバーライドできます.
上のセクションで作成したコードを流用して Qt3DWindow のイベントハンドラをコーディングしてみます.
継承クラスを作成するという点以外は,4:3D レンダリングウィジェットの作成で QWindow をインスタンス化した手順と同じです.あるいは,QWidget 継承クラスと Qt3DWindow 継承クラスを使うという点を除けば,上のセクションと同じです.

ソースコード

上のセクションで作成したコードからの変更点のみ赤字で記します.

my_window.h


#include <Qt3DWindow>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QKeyEvent>

class My_Window : public Qt3DExtras::Qt3DWindow {
 protected:
  void resizeEvent(QResizeEvent *event);
  void mousePressEvent(QMouseEvent *event);
  void keyPressEvent(QKeyEvent *event);
};

my_window.cc


#include "my_window.h"
#include <iostream>

void My_Window::resizeEvent(QResizeEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 std::cout << "type ; " << event->type() << std::endl;

 QSize size = event->size();
 std::cout << "height ; " << size.rheight() << ", width : " << size.rwidth() << std::endl;
}

void My_Window::mousePressEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 std::cout << "type ; " << event->type() << std::endl;

 Qt::MouseButton button = event->button();
 std::cout << "button ; " << button << std::endl;

 QPointF point = event->position();
 std::cout << "point ; " << point.rx() << ' ' << point.ry() << std::endl;
}

void My_Window::keyPressEvent(QKeyEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 std::cout << "type : " << event->type() << std::endl;

 std::cout << "key : " << event->key() << std::endl;
}

main.cc


#include <QApplication>
#include "my_window.h"

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

 My_Window window;
 window.resize(320, 240);
 window.show();

 return app.exec();
}

tmp.pro


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
SOURCES += main.cc my_window.cc
HEADER += my_window.h
QT += widgets
QT += 3dextras

ビルドと実行

Qt3DExtras::Qt3DWindow インスタンス

~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &

画像に示すウィンドウが表示されました.
実装したイベントハンドラは,上のセクションで練習した QWidget 派生クラスの場合と同じ動作を示しました.

Qt3DWindow を QWidget に埋め込んでみる

このセクションでは Qt3DWindow の派生クラス My_Window を定義してイベントハンドラを実装しています.
では,My_Window を埋め込んだ QWidget では,実装したイベントハンドラが機能するでしょうか.
うまく機能すれば ,ウィンドウアプリケーションでレイアウト管理が可能になります.

main.cc

createWindowContainer() 関数を使えば,Qt3DWindow の派生クラスから QWidget が作成できます.これは,4:3D レンダリングウィジェットの作成で練習しました.


#include <QApplication>
#include <QWidget>
#include "my_window.h"

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

 My_Window *view = new My_Window;
 QWidget *widget = QWidget::createWindowContainer(view);
 widget->resize(320, 240);
 widget->show();

 return app.exec();
}

このコードでは,実行時に My_Window を埋め込んだ QWidget 表示されます.イベントハンドラは My_Window で実装しています.
やってみたら,イベントが補足できました.
QT3dWindow 継承クラスを埋め込んだ QWidget を利用すれば,Qt3DWindow 継承クラスをレイアウト管理できそうです.


ウィンドウアプリケーションの雛形

上で練習した成果を 4:3D レンダリングウィジェットの作成で作成した雛形に取り込みます.
すなわち,メインウィンドウ内で,DrawWindow から QWidget を作成し,レイアウト管理しています.
ここでは,Qt3DExtras::Qt3DWindow を継承した Draw_Window というクラスにイベントハンドラを void mousePressEvent(QMouseEvent *event) を実装します.

ソースコード

draw_window.h


#ifndef ___DRAW_WINDOW
#define ___DRAW_WINDOW

#include <Qt3DExtras>

class Draw_Window : public Qt3DExtras::Qt3DWindow {
 protected:
  void mousePressEvent(QMouseEvent *event);

 public:
  Draw_Window() {}
};

#endif

draw_window.cc


#include "draw_window.h"
#include <iostream>

void Draw_Window::mousePressEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;

 std::cout << "type ; " << event->type() << std::endl;

 Qt::MouseButton button = event->button();
 std::cout << "button ; " << button << std::endl;

 QPointF point = event->position();
 std::cout << "point ; " << point.rx() << ' ' << point.ry() << std::endl;
}

main_window.h


#include "draw_window.h"
#include <QMainWindow>
#include <QMenuBar>

class Main_Window : public QMainWindow {
 private:
  QMenuBar *menu_bar;
  QMenu *file_menu;
  QMenu *view_menu;  QAction *action_quit;

  Draw_Window *DrawWindow;
  QWidget *DrawWidget;

 public:
  Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};

main_window.cc


#include "main_window.h"
#include "draw_window.h"
#include <QHBoxLayout>

Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
 : QMainWindow(parent, f),
   menu_bar(menuBar())
{
 resize(320, 240);

 file_menu = new QMenu("ファイル(&F)", this);
 menu_bar->addMenu(file_menu);

 action_quit = new QAction("終了(&Q)", this);
 file_menu->addAction(action_quit);

 connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));

 QWidget *central_widget = new QWidget(this);
 setCentralWidget(central_widget);
 QHBoxLayout *central_layout = new QHBoxLayout(central_widget); 

 DrawWindow = new Draw_Window;
 DrawWidget = QWidget::createWindowContainer(DrawWindow);
 central_layout->addWidget(DrawWidget); 
}

main.cc


#include "main_window.h"
#include <QApplication>

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

 Main_Window window(nullptr, Qt::Window);
 window.show();

 return app.exec();
}

tmp.pro


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
QT += widgets
QT += 3dextras
SOURCES += main.cc main_window.cc draw_window.cc
HEADERS += main_window.h draw_window.h

ビルドと実行

My_Window インスタンス

手順は上と同じです.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &
ウィンドウ上の白い部分をクリックしたら,イベントハンドラが呼び出されこることを確認できます.

virtual void Draw_Window::mousePressEvent(QMouseEvent*)
type ; 2
button ; 1
point ; 289 188