Qt/C++ による GUI ソフトウェア作成の練習 4:イベントの取得

Qt では,イベントは抽象 QEvent クラスから派生したオブジェクトです.
イベントが発生すると,適切な QEvent 派生クラスのインスタンスが作成され,その event() 関数を呼び出すことで,イベントを配信します.
ほとんどのイベント・タイプには特別なクラスがあります.QResizeEvent,QPaintEvent,QMouseEvent,QKeyEvent,QCloseEvent が代表例です.

QMouseEvent は,マウスボタンの押下,ダブルクリック,移動,その他の関連操作により発生するイベントです.
ウィジェット内でマウスボタンが押されると,ウィジェットは自動的にマウスを捕捉し,マウスボタンが離されるまでマウスイベントを受け取り続けます.

このページでは,Qt3DExtras::Qt3DWindow を継承したクラスを定義し,そのクラスで QWidget のイベントハンドラをいくつかオーバーライドしてみます.

Before : 3:レイアウト Next : 5:Qt3D による描画

インフォメーション

ドキュメント


 このページは,C++ クラスリファレンスの QWidget を参考にして作成しました.
 マウスイベントについては,QMouseEvent Class に解説があります.
 イベント全般については,Qt Core の The Event System に解説があります.

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

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

QWidget のイベントハンドラ

QWidget のページを見ると,protected メンバ関数の多くがイベントハンドラとなっています.番号を振ってみたら 26 個ありました.
これらのイベントハンドラは protected メンバなので,派生クラスでオーバーライドできます.

  1. virtual void actionEvent(QActionEvent *event)
  2. virtual void changeEvent(QEvent *event)
  3. virtual void closeEvent(QCloseEvent *event)
  4. virtual void contextMenuEvent(QContextMenuEvent *event)
  5. virtual void dragEnterEvent(QDragEnterEvent *event)
  6. virtual void dragLeaveEvent(QDragLeaveEvent *event)
  7. virtual void dragMoveEvent(QDragMoveEvent *event)
  8. virtual void dropEvent(QDropEvent *event)
  9. virtual void enterEvent(QEnterEvent *event)
  10. virtual void focusInEvent(QFocusEvent *event)
  11. virtual void focusOutEvent(QFocusEvent *event)
  12. virtual void hideEvent(QHideEvent *event)
  13. virtual void inputMethodEvent(QInputMethodEvent *event)
  14. virtual void keyPressEvent(QKeyEvent *event)
  15. virtual void keyReleaseEvent(QKeyEvent *event)
  16. virtual void leaveEvent(QEvent *event)
  17. virtual void mouseDoubleClickEvent(QMouseEvent *event)
  18. virtual void mouseMoveEvent(QMouseEvent *event)
  19. virtual void mousePressEvent(QMouseEvent *event)
  20. virtual void mouseReleaseEvent(QMouseEvent *event)
  21. virtual void moveEvent(QMoveEvent *event)
  22. virtual void paintEvent(QPaintEvent *event)
  23. virtual void resizeEvent(QResizeEvent *event)
  24. virtual void showEvent(QShowEvent *event)
  25. virtual void tabletEvent(QTabletEvent *event)
  26. virtual void wheelEvent(QWheelEvent *event)

イベントの取得と出力

レイアウトで作成したコードを変更して雛形としました.
d3_window.h と d3_window.cc は新規作成です.
レイアウトのコードを流用したコードでは,このページで追加した箇所を赤色で示しています.

ソースファイル

d3_window.h

Qt3DExtras::Qt3DWindow を継承したクラスを新規作成します.
このクラスで,基底クラスの QWidget で宣言されている仮想関数をオーバーライドするのが目的です.


#ifndef ___D3_WINDOW
#define ___D3_WINDOW

#include <Qt3DCore>
#include <Qt3DExtras>
#include <Qt3DRender>

class D3_Window : public Qt3DExtras::Qt3DWindow  {
 protected:
  void actionEvent(QActionEvent *event);
  void changeEvent(QEvent *event);
  void closeEvent(QCloseEvent *event);
  void contextMenuEvent(QContextMenuEvent *event);
  void dragEnterEvent(QDragEnterEvent *event);
  void dragLeaveEvent(QDragLeaveEvent *event);
  void dragMoveEvent(QDragMoveEvent *event);
  void dropEvent(QDropEvent *event);
  void enterEvent(QEnterEvent *event);
  void focusInEvent(QFocusEvent *event);
  void focusOutEvent(QFocusEvent *event);
  void hideEvent(QHideEvent *event);
  void inputMethodEvent(QInputMethodEvent *event);
  void keyPressEvent(QKeyEvent *event);
  void keyReleaseEvent(QKeyEvent *event);
  void leaveEvent(QEvent *event);
  void mouseDoubleClickEvent(QMouseEvent *event);
  void mouseMoveEvent(QMouseEvent *event);
  void mousePressEvent(QMouseEvent *event);
  void mouseReleaseEvent(QMouseEvent *event);
  void moveEvent(QMoveEvent *event);
  void paintEvent(QPaintEvent *event);
  void resizeEvent(QResizeEvent *event);
  void showEvent(QShowEvent *event);
  void tabletEvent(QTabletEvent *event);
  void wheelEvent(QWheelEvent *event);
};

#endif

d3_window.cc

新規作成です.
引数のイベントを利用していない関数があるので,コンパイル時にその旨の警告が出ます.

イベントが発生すると,イベントハンドラが呼び出され,その名前が出力されます(どうやって発生させるのか知らないイベントもありますが).
マウスボタンを押した際のイベントハンドラ mousePressEvent には,ボタンに割り当てられた番号とウィンドウ上の位置を出力するコードを記述しました.

また,show_event では,基底クラス Qt3DExtras::Qt3DWindow の show_event() を呼び出しています.
そうしないと,ウィンドウの下(デスクトップとか他のウィンドウとか)が描画されてしまうからです.


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

void D3_Window::actionEvent(QActionEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::changeEvent(QEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::closeEvent(QCloseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::contextMenuEvent(QContextMenuEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::dragEnterEvent(QDragEnterEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::dragLeaveEvent(QDragLeaveEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::dragMoveEvent(QDragMoveEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::dropEvent(QDropEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::enterEvent(QEnterEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::focusInEvent(QFocusEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::focusOutEvent(QFocusEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::hideEvent(QHideEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::inputMethodEvent(QInputMethodEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

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

void D3_Window::keyReleaseEvent(QKeyEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::leaveEvent(QEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::mouseDoubleClickEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::mouseMoveEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::mousePressEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << " ";
 //どのボタンのイベントかを出力
 //button() 関数 は,QMouseEvent クラスの基底クラス QSinglePointEvent クラスで定義されている.戻り値の型は enum Qt::MouseButton という列挙体.
 std::cout << "button " << event->button() << ", ";

 //ボタンが押された位置を出力
 //position() 関数も QSinglePointEvent クラスで定義されている.戻り値は QPointF クラスのオブジェクト
 QPointF point = event->position();
 std::cout << "x " << point.x() << ", ";
 std::cout << "y " << point.y() << std::endl;
}

void D3_Window::mouseReleaseEvent(QMouseEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::moveEvent(QMoveEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::paintEvent(QPaintEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

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

void D3_Window::showEvent(QShowEvent *event) {
 //基底クラスのハンドラを呼び出さないと,ウィンドウの下(デスクトップとか他のウィンドウとか)が描画されてしまう
 Qt3DExtras::Qt3DWindow::showEvent(event);

 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::tabletEvent(QTabletEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void D3_Window::wheelEvent(QWheelEvent *event) {
 std::cout << __PRETTY_FUNCTION__ << std::endl;
}

draw_obj.h

描画用のウィンドウ DrawWindow は,D3_Window クラスのインスタンスに変更しています.


#ifndef ___DRAW_OBJ
#define ___DRAW_OBJ

#include "d3_window.h"

class Draw_Obj : public QObject {
 private:
  D3_Window *DrawWindow;

 public:
  Draw_Obj();
  ~Draw_Obj() {}

  D3_Window *get_draw_window()
        { return DrawWindow; }

  void init();
};

#endif

draw_obj.cc


#include "draw_obj.h"

Draw_Obj::Draw_Obj()
 : DrawWindow(nullptr)
{
  DrawWindow = new D3_Window();
}

void Draw_Obj::init() {
 if(DrawWindow) {
  delete DrawWindow;
  DrawWindow = new D3_Window();
 }
}

main_window.h


#include <QMainWindow>
#include <QMenuBar>
#include <QHBoxLayout>
#include "draw_obj.h"

class Main_Window : public QMainWindow {
 private:
  Q_OBJECT

  QMenuBar *menu_bar;
  QMenu *file_menu;
  QAction *action_quit;

  QHBoxLayout *CentralLayout;
  QWidget *Container;
  Draw_Obj DrawObj;
  D3_Window *DrawWindow;

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

main_window.cc


#include "main_window.h"

Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
 : QMainWindow(parent, f),
   menu_bar(menuBar()),
   CentralLayout(nullptr),
   Container(nullptr),
   DrawWindow(nullptr)
{
 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);
 CentralLayout = new QHBoxLayout(central_widget);

 DrawWindow = DrawObj.get_draw_window();
 Container = createWindowContainer(DrawWindow);
 CentralLayout->addWidget(Container);

 QSize screenSize = screen()->size();
 setMaximumSize(screenSize);

 setMinimumWidth(600);
 setMinimumHeight(600);
}

main.cc


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

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

 Main_Window window(nullptr, Qt::Window);

 window.show();

 return app.exec();
}

tmp.pro

qmake のプロジェクトファイルです.名前を変更しても問題ありません.


TEMPLATE = app
TARGET = window
QT += widgets
INCLUDEPATH += .
HEADERS += main_window.h draw_obj.h d3_window.h
SOURCES += main.cc main_window.cc draw_obj.cc d3_window.cc

QT += 3dcore
QT += 3dextras
#QT += 3drender
#QT += 3dinput

ビルドと実行

Draw_Widget を作成

まず Makefile を作成.
~/tmp$ qmake6

次いでビルド.実行ファイル window が生成します.
~/tmp$ make

ビルドに成功したら実行.
~/tmp$ ./window &
とするとウィンドウが表示されます.
以下に,端末への出力をいくつか記します.

起動

ペイントイベントが 2 回発生しています.

virtual void D3_Window::showEvent(QShowEvent*)
virtual void D3_Window::resizeEvent(QResizeEvent*)
virtual void D3_Window::paintEvent(QPaintEvent*)
virtual void D3_Window::paintEvent(QPaintEvent*)

クリック

左 → 右 → ホイールボタンとクリックしてみました.
クリックにより,mousePressEvent と mouseReleaseEvent() が呼び出されました.
マウスボタンには,それぞれ 1,2,4 という番号が割り当てられていることが判りました.

virtual void D3_Window::mousePressEvent(QMouseEvent*) button 1, x 392, y 353
virtual void D3_Window::mouseReleaseEvent(QMouseEvent*)
virtual void D3_Window::mousePressEvent(QMouseEvent*) button 2, x 392, y 353
virtual void D3_Window::mouseReleaseEvent(QMouseEvent*)
virtual void D3_Window::mousePressEvent(QMouseEvent*) button 4, x 392, y 353
virtual void D3_Window::mouseReleaseEvent(QMouseEvent*)

ダブルクリック

マウスを押して,離して,押した時点で,ダブルクリックかシングルクリックが 2 回かが判断されることが判ります.

virtual void D3_Window::mousePressEvent(QMouseEvent*) button 1, x 512, y 269
virtual void D3_Window::mouseReleaseEvent(QMouseEvent*)
virtual void D3_Window::mousePressEvent(QMouseEvent*) button 1, x 512, y 269
virtual void D3_Window::mouseDoubleClickEvent(QMouseEvent*)
virtual void D3_Window::mouseReleaseEvent(QMouseEvent*)

終了

virtual void D3_Window::hideEvent(QHideEvent*)

参考書の検索