【Qt入門】QTabWidget::event()でイベントをカスタマイズする方法

2025-05-27

QTabWidget::event() とは

QTabWidget::event(QEvent *event) は、Qtのイベントシステムにおける重要な仮想関数です。すべてのQObjectから派生したクラス(そしてQTabWidgetもその一つ)は、このevent()関数を持っています。

基本的な役割は以下の通りです。

  1. イベントのディスパッチ: Qtアプリケーション内で発生する様々なイベント(マウスイベント、キーボードイベント、ペイントイベント、サイズ変更イベントなど)は、最終的に適切なウィジェットのevent()関数に送られます。
  2. イベントの処理: event()関数は、受け取ったイベントの種類に応じて、そのウィジェットに特化したイベントハンドラ(例: mousePressEvent(), keyPressEvent(), paintEvent()など)を呼び出します。
  3. イベントの伝播: イベントを処理した場合、trueを返してイベントが処理済みであることを示します。イベントを処理しなかった場合(または、さらに親ウィジェットにイベントを渡したい場合)、falseを返して、イベントが親ウィジェットのevent()関数に伝播されるようにします。

QTabWidgetにおけるevent()の役割

QTabWidgetは、複数のタブページを持つウィジェットであり、タブの切り替えや表示に関連する様々なイベントを処理します。QTabWidgetevent()関数は、具体的には以下のようなイベントを内部的に処理しています。

  • ドラッグ&ドロップイベント: setMovable(true)が設定されている場合、タブの移動に関するドラッグ&ドロップイベントを処理します。
  • サイズ変更イベント: QTabWidgetのサイズが変更された場合、内部のタブバーやページウィジェットのレイアウトを調整するための処理を行います。
  • タブのクローズ要求: setTabsClosable(true)が設定されている場合、タブのクローズボタンがクリックされたときに、tabCloseRequested()シグナルを発行するためのイベント処理を行います。
  • タブのクリック/ダブルクリック: ユーザーがタブをクリックしたりダブルクリックしたりすると、それに対応するイベントがQTabWidget::event()に送られ、内部でcurrentChanged()シグナルを発行したり、適切な処理を行います。

event()をオーバーライドする理由

通常、特定のイベント(例えばマウスのクリックやキー入力など)を処理したい場合は、mousePressEvent()keyPressEvent()などの専用のイベントハンドラ関数をオーバーライドすることが推奨されます。これらはevent()関数内部から呼び出されるため、より特定の問題に焦点を当てることができます。

しかし、以下のような場合にはevent()関数をオーバーライドすることを検討します。

  • イベントの伝播を制御したい場合: イベントを親ウィジェットに伝播させるか、それともこのウィジェットで処理を終えるかを明示的に制御したい場合。
  • イベントが特定のイベントハンドラにディスパッチされる前に処理したい場合: event()は、より下位レベルでイベントを捕捉できるため、専用のイベントハンドラが呼び出される前にイベントをフィルターしたり、消費したりすることができます。
  • 専用のイベントハンドラが存在しないイベントを処理したい場合: Qtには、すべてのイベントに対応する専用のイベントハンドラ関数があるわけではありません。
  • 複数の種類のイベントを一箇所で処理したい場合: 例えば、ある特定の条件に基づいてマウスイベントとキーボードイベントの両方に異なる処理を行いたい場合など。

もしQTabWidgetのタブがクリックされたときに、通常とは異なる特殊な処理をしたい場合、event()をオーバーライドして、受け取ったイベントがQEvent::MouseButtonPressであるかを確認し、さらにそのクリックがタブバー上であったかなどをチェックする、といったことが考えられます。

#include <QTabWidget>
#include <QEvent>
#include <QMouseEvent>
#include <QDebug> // デバッグ出力用

class MyTabWidget : public QTabWidget
{
    Q_OBJECT
public:
    explicit MyTabWidget(QWidget *parent = nullptr) : QTabWidget(parent) {}

protected:
    bool event(QEvent *event) override {
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            qDebug() << "MyTabWidget: Mouse press event received at" << mouseEvent->pos();
            // ここで追加のカスタム処理を行うことができます
            // 例: 特定の座標がタブバー上であれば何かをする
            if (tabBar()->geometry().contains(mouseEvent->pos())) {
                qDebug() << "MyTabWidget: Clicked on tab bar!";
                // イベントを処理し、これ以上伝播させない
                // return true; // この行を有効にすると、QTabWidget本来のクリック処理がされなくなる
            }
        }
        // 親クラスのevent()関数を呼び出すことで、通常通りイベントが処理されるようにする
        return QTabWidget::event(event);
    }
};

この例では、MyTabWidgetというQTabWidgetの派生クラスを作成し、event()関数をオーバーライドしています。マウスボタンが押されたイベントが発生した場合に、そのイベントの位置をデバッグ出力し、もしタブバーの領域内でクリックされた場合は追加のメッセージを表示します。



基底クラスのevent()を呼び出し忘れる

エラーの症状: QTabWidget::event()をオーバーライドしたにもかかわらず、QTabWidget本来の機能(タブの切り替え、タブのクローズ、ドラッグ&ドロップなど)が機能しなくなる。

原因: event()関数は、イベントの種類に応じて適切なイベントハンドラ(例: mousePressEvent()keyPressEvent()paintEvent()など)を呼び出す役割も担っています。もしオーバーライドしたevent()内で基底クラスのevent()を呼び出さないと、Qtが提供する基本的なイベント処理がスキップされてしまい、QTabWidgetの多くの機能が動作しなくなります。

トラブルシューティング: オーバーライドしたevent()関数の最後に、必ず基底クラスのevent()を呼び出すようにしてください。

bool MyTabWidget::event(QEvent *event) {
    // カスタム処理
    if (event->type() == QEvent::KeyPress) {
        // ...
    }

    // ★重要: 基底クラスのevent()を呼び出す
    return QTabWidget::event(event);
}

イベントを完全に「消費」して、それ以上伝播させたくない場合にのみtrueを返すことを検討しますが、通常はQTabWidget::event(event)の戻り値をそのまま返すべきです。

イベントフィルタと混同する

エラーの症状: 特定のウィジェットの子孫に対してイベントをフィルターしたいのに、event()をオーバーライドしようとする。

原因: event()は、そのウィジェット自身に送られてきたイベントを処理するためのものです。子ウィジェットや他のウィジェットのイベントを監視・フィルターしたい場合は、eventFilter()関数をオーバーライドし、installEventFilter()を使って対象のウィジェットにフィルタをインストールする必要があります。

トラブルシューティング: 他のウィジェットのイベントを処理したい場合は、QObject::eventFilter()を適切に使用してください。

// イベントフィルタとして機能するクラス
class MyEventFilter : public QObject
{
    Q_OBJECT
protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (obj == myTargetWidget && event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
            qDebug() << "Filtered key press on target widget:" << keyEvent->key();
            return true; // イベントを消費
        }
        return QObject::eventFilter(obj, event);
    }
};

// 使用例
MyEventFilter *filter = new MyEventFilter(this);
myTargetWidget->installEventFilter(filter);

event()内でUIの無限ループに陥る

エラーの症状: event()内でUIの更新や再描画をトリガーするような操作を行うと、無限ループに陥りアプリケーションがフリーズしたりクラッシュしたりする。例えば、paintEventに関連するイベント処理中にupdate()repaint()を呼び出す、など。

原因: event()は頻繁に呼び出される可能性のある関数です。その中でUIの更新(update()repaint())を直接トリガーするような処理を行うと、その更新が再びevent()(特にQEvent::Paintなどのイベント)を呼び出し、再帰的な呼び出しの連鎖が起こる可能性があります。

トラブルシューティング:

  • デバッガを使ってスタックトレースを確認し、無限ループの発生源を特定する。
  • 特にQEvent::PaintQEvent::Resizeなどのイベントを処理している場合は、無限ループにならないように注意深くコードを記述する。
  • event()内でのupdate()repaint()の直接的な呼び出しは避ける。必要な場合は、イベントループに任せるか、QTimer::singleShotなどで遅延実行を検討する。

イベントタイプの誤判定または型キャストの失敗

エラーの症状: 特定のイベントを処理しようとした際に、期待通りのイベントが発生しない、またはアプリケーションがクラッシュする。

原因:

  • static_castなどでのイベントオブジェクトの型キャストが、実際のイベントタイプと一致せず、不正なメモリアクセスを引き起こす。例えば、QEvent::MouseButtonPressなのにQKeyEventにキャストしようとするなど。
  • event->type()でイベントタイプを誤って判断している。

トラブルシューティング:

  • static_castを行う前に、必ずevent->type()で適切なイベントタイプであるかを確認する。または、qobject_castを使用して安全なダウンキャストを行う。
  • イベントタイプを比較する際は、QEvent::Type enumを正確に使用する。

<!-- end list -->

bool MyTabWidget::event(QEvent *event) {
    if (event->type() == QEvent::MouseButtonPress) {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); // 安全なキャスト
        // ...
    } else if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); // 安全なキャスト
        // ...
    }
    return QTabWidget::event(event);
}

QTabWidgetの子ウィジェットがイベントを消費してしまう

エラーの症状: QTabWidgetevent()をオーバーライドしたが、特定のイベントがQTabWidgetに到達せず、子ウィジェットによって先に処理されてしまう。例えば、タブページ内のQLineEditがキーイベントを消費してしまうなど。

原因: Qtのイベント伝播は、基本的に子から親へと流れます。もし子ウィジェットがイベントを処理し(trueを返すなどして)、それ以上伝播させない場合、親であるQTabWidgetevent()にはそのイベントが到達しません。

トラブルシューティング:

  • setFocusPolicy()の確認: 子ウィジェットのフォーカス設定が適切であるかを確認する。キーイベントなどはフォーカスを持つウィジェットに送られます。
  • 子ウィジェットのevent()をオーバーライド: 子ウィジェットのevent()または適切なイベントハンドラをオーバーライドし、必要に応じてイベントをignore()したり、基底クラスのイベントハンドラを呼び出したりして、イベントが親に伝播するようにする。
  • イベントフィルタの使用: 子ウィジェットにイベントフィルタをインストールし、親が処理したいイベントを子ウィジェットが受け取る前にフィルタリング・処理する。

QTabWidget::event()に関連する問題をデバッグする際には、以下の方法が役立ちます。

  • Qtのドキュメント参照: QEventの各タイプや、QObjectのイベント処理に関する詳細なドキュメントを参照し、イベントシステムがどのように機能するかを理解します。
  • デバッガの利用: ブレークポイントを設定し、ステップ実行することで、イベントの伝播パスや変数(eventポインタ、event->type()など)の値を確認します。
  • qDebug()の利用: event()関数の内部にqDebug()文を挿入し、どのイベントタイプがいつ受信されているか、イベントオブジェクトのプロパティ(例: QMouseEvent::pos()QKeyEvent::key()など)がどうなっているかを確認します。


例1: マウスイベントを捕捉してデバッグ情報を出力する

この例では、QTabWidget のタブ領域でマウスがクリックされたときに、そのイベントを捕捉し、デバッグ情報を出力します。

MyTabWidget.h

#ifndef MYTABWIDGET_H
#define MYTABWIDGET_H

#include <QTabWidget>
#include <QEvent>
#include <QMouseEvent>
#include <QDebug> // デバッグ出力用

class MyTabWidget : public QTabWidget
{
    Q_OBJECT
public:
    explicit MyTabWidget(QWidget *parent = nullptr);

protected:
    // QTabWidget::event() をオーバーライド
    bool event(QEvent *event) override;
};

#endif // MYTABWIDGET_H

MyTabWidget.cpp

#include "MyTabWidget.h"
#include <QTabBar> // QTabWidget::tabBar() の戻り値の型

MyTabWidget::MyTabWidget(QWidget *parent) : QTabWidget(parent)
{
    // 初期設定などがあればここに記述
}

bool MyTabWidget::event(QEvent *event)
{
    // マウスプレスイベントの場合
    if (event->type() == QEvent::MouseButtonPress) {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);

        // タブバーの領域内でのクリックかどうかをチェック
        if (tabBar()->geometry().contains(mouseEvent->pos())) {
            qDebug() << "タブがクリックされました!";
            qDebug() << "クリック位置 (QTabWidget座標):" << mouseEvent->pos();
            qDebug() << "ボタン:" << mouseEvent->button();
            qDebug() << "タブのインデックス:" << tabBar()->tabAt(mouseEvent->pos());
            
            // ここで追加のカスタム処理を行うことができます。
            // 例えば、特定のタブでのみ特殊な動作をさせるなど。
            if (tabBar()->tabAt(mouseEvent->pos()) == 0) { // 最初のタブの場合
                qDebug() << "最初のタブがクリックされました!";
            }
        }
    }
    // ここで他の種類のイベントを処理することも可能です。
    // 例: キープレスイベント
    else if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
        qDebug() << "キーが押されました:" << keyEvent->text();
    }

    // ★重要: 基底クラスのevent()関数を呼び出すことで、QTabWidget本来のイベント処理(タブの切り替えなど)が実行されるようにする
    return QTabWidget::event(event);
}

main.cpp (アプリケーションの起動コード)

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include "MyTabWidget.h"

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

    MyTabWidget tabWidget;

    // タブ1
    QWidget *page1 = new QWidget();
    QVBoxLayout *layout1 = new QVBoxLayout(page1);
    layout1->addWidget(new QLabel("これはタブ1のコンテンツです。"));
    layout1->addWidget(new QPushButton("ボタン1"));
    tabWidget.addTab(page1, "タブ 1");

    // タブ2
    QWidget *page2 = new QWidget();
    QVBoxLayout *layout2 = new QVBoxLayout(page2);
    layout2->addWidget(new QLabel("これはタブ2のコンテンツです。"));
    layout2->addWidget(new QPushButton("ボタン2"));
    tabWidget.addTab(page2, "タブ 2");

    // タブを閉じられるように設定
    tabWidget.setTabsClosable(true);

    // タブクローズ要求シグナルに接続(QTabWidget::event() とは直接関係ないが、タブの動作を示すため)
    QObject::connect(&tabWidget, &QTabWidget::tabCloseRequested, &tabWidget, [&](int index){
        qDebug() << "タブのクローズが要求されました。インデックス:" << index;
        tabWidget.removeTab(index);
    });

    tabWidget.setWindowTitle("QTabWidget::event() の例");
    tabWidget.resize(400, 300);
    tabWidget.show();

    return a.exec();
}

この例のポイント:

  • 最も重要な点として、return QTabWidget::event(event); を呼び出して、基底クラスのイベント処理を継続させています。 これを忘れると、タブが切り替わらなかったり、閉じられなくなったりといった問題が発生します。
  • tabBar()->geometry().contains(mouseEvent->pos()) を使って、クリックがタブバーの領域内で行われたかどうかを判定しています。これにより、タブ内のウィジェットに対するクリックとタブ自体に対するクリックを区別できます。
  • 適切な型に static_cast でキャストし、イベント固有の情報を取得しています(例: QMouseEvent::pos())。
  • event->type() を使ってイベントの種類(QEvent::MouseButtonPressQEvent::KeyPress など)を判定しています。
  • protected メンバー関数である event(QEvent *event)override キーワードを使ってオーバーライドしています。
  • MyTabWidget クラスを QTabWidget から派生させています。

この例では、ユーザーが特定のタブに切り替えようとしたときに、何らかの条件(例: ユーザーの確認、データの保存状況など)に基づいてその切り替えを阻止する方法を示します。

MyTabWidget.h

#ifndef MYTABWIDGET_CONDITIONALEVENT_H
#define MYTABWIDGET_CONDITIONALEVENT_H

#include <QTabWidget>
#include <QEvent>
#include <QKeyEvent> // 必要に応じて
#include <QDebug>
#include <QMessageBox> // 確認ダイアログ用

class MyConditionalTabWidget : public QTabWidget
{
    Q_OBJECT
public:
    explicit MyConditionalTabWidget(QWidget *parent = nullptr);

protected:
    bool event(QEvent *event) override;

private:
    int previousIndex; // 以前のタブのインデックスを保存
};

#endif // MYTABWIDGET_CONDITIONALEVENT_H

MyTabWidget.cpp

#include "MyTabWidget_ConditionalEvent.h"

MyConditionalTabWidget::MyConditionalTabWidget(QWidget *parent)
    : QTabWidget(parent), previousIndex(0) // 初期タブは0番と仮定
{
    // currentChanged シグナルを捕捉して previousIndex を更新
    connect(this, &QTabWidget::currentChanged, this, [&](int index){
        if (index != -1) { // タブが削除された場合は -1 になるため除外
            previousIndex = index;
            qDebug() << "現在のタブが変更されました。新しいインデックス:" << index;
        }
    });
}

bool MyConditionalTabWidget::event(QEvent *event)
{
    // TabBarClicked イベントを捕捉して、タブ切り替えを試みたかどうかを判定
    // Note: currentChanged シグナルはイベント処理後に発火するため、
    // event() でタブ変更を阻止するには、QEvent::Type で判断するのが適切です。
    // 特に、キーボードナビゲーション (Ctrl+Tab) やマウスによるタブクリックの両方を考慮する必要があります。
    // QTabWidgetは内部的にQTabBarとQStackedWidgetを使い、様々なイベントからタブ切り替えを行います。
    // ここでは、タブバーへのマウスプレスイベントで切り替えを試みた場合を例とします。
    // より包括的な処理には、QTabBar::event() をオーバーライドするか、
    // QTabWidget::currentChanged シグナルの発行を一時的にブロックするなどの工夫が必要です。

    // 一般的なイベント処理のフローとしては、ユーザーがタブをクリックする
    // -> QMouseEvent が QTabWidget::event() に送られる
    // -> QTabWidget 内部で QTabBar::tabAt() などを使ってクリックされたタブを特定し、
    //    QStackedWidget::setCurrentIndex() を呼び出す
    // -> これにより currentChanged シグナルが発火する
    // したがって、event() で阻止する場合、QEvent::MouseButtonPress や QEvent::KeyPress などで
    // ユーザーの意図を直接捉える必要があります。
    // または、QTabWidget::currentChanged のハンドラで oldIndex を使って戻す方法もありますが、
    // この場合一度切り替わってから戻るため、UXが劣る場合があります。

    if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::KeyPress) {
        // マウスイベントの場合
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            int clickedTabIndex = tabBar()->tabAt(mouseEvent->pos());
            if (clickedTabIndex == 1) { // 2番目のタブ(インデックス1)への切り替えを阻止したい場合
                qDebug() << "2番目のタブがクリックされました。切り替えを試みます。";
                QMessageBox::StandardButton reply;
                reply = QMessageBox::question(this, "タブ切り替えの確認",
                                              "このタブに切り替えますか?未保存のデータがあるかもしれません。",
                                              QMessageBox::Yes|QMessageBox::No);
                if (reply == QMessageBox::No) {
                    qDebug() << "タブ切り替えがキャンセルされました。";
                    // イベントを処理済みとしてマークし、それ以上伝播させない
                    // これにより、QTabWidget本来の切り替え処理が停止します。
                    return true;
                }
            }
        }
        // キーボードイベントの場合 (例: Ctrl+Tab での切り替え)
        else if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
            // これはより複雑になるため、ここでは省略しますが、
            // QTabWidgetの内部実装を理解して、キーイベントからどのタブへの変更が意図されているかを判断する必要があります。
            // 通常、QTabWidgetのキーボードナビゲーションは内部で処理されるため、
            // event() をオーバーライドして阻止するのは少し難しい場合があります。
            // eventFilter を QTabBar にインストールする方が効果的な場合もあります。
        }
    }

    // ★重要: 基底クラスのevent()関数を呼び出す
    return QTabWidget::event(event);
}

main.cpp (アプリケーションの起動コード)

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include "MyTabWidget_ConditionalEvent.h"

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

    MyConditionalTabWidget tabWidget;

    // タブ1
    QWidget *page1 = new QWidget();
    QVBoxLayout *layout1 = new QVBoxLayout(page1);
    layout1->addWidget(new QLabel("これはタブ1です。自由に移動できます。"));
    tabWidget.addTab(page1, "タブ A");

    // タブ2 (切り替えを阻止する対象)
    QWidget *page2 = new QWidget();
    QVBoxLayout *layout2 = new QVBoxLayout(page2);
    layout2->addWidget(new QLabel("これはタブ2です。切り替える際に確認があります。"));
    tabWidget.addTab(page2, "タブ B (確認あり)");

    // タブ3
    QWidget *page3 = new QWidget();
    QVBoxLayout *layout3 = new QVBoxLayout(page3);
    layout3->addWidget(new QLabel("これはタブ3です。自由に移動できます。"));
    tabWidget.addTab(page3, "タブ C");

    tabWidget.setWindowTitle("条件付きタブ切り替えの例");
    tabWidget.resize(400, 300);
    tabWidget.show();

    return a.exec();
}

この例のポイント:

  • キーボード操作によるタブ切り替えの阻止は、より複雑になる可能性があることを示しています。なぜなら、QTabWidget の内部的なキーボードナビゲーションは、単一のキープレスイベントとして明確に現れない場合があるためです。このような場合は、QTabBar 自体にeventFilterをインストールする方が効果的な場合があります。
  • QMessageBox::No が選択された場合、return true; を呼び出してイベントを消費し、それ以上 QTabWidget のデフォルトのイベント処理が進行しないようにしています。これにより、タブの切り替えが阻止されます。


シグナルとスロットの使用

最もQtらしいアプローチであり、多くの場合 event() をオーバーライドするよりも推奨されます。QTabWidget は、タブの操作に関連する便利なシグナルを提供しています。

  • tabBarDoubleClicked(int index): タブバー上の特定のタブがダブルクリックされたときに発せられます。
  • tabBarClicked(int index): タブバー上の特定のタブがクリックされたときに発せられます。
  • tabCloseRequested(int index): setTabsClosable(true) が設定されている場合に、ユーザーがタブのクローズボタンをクリックしたときに発せられます。
  • currentChanged(int index): 現在のタブが変更されたときに発せられます。これは、タブの切り替えを検知する最も一般的な方法です。

メリット:

  • Qt の推奨する設計パターンに沿っているため、将来のメンテナンスが容易。
  • モジュール性が高く、イベント処理のロジックを分離できる。
  • コードが読みやすく、意図が明確になる。

使用例: タブ切り替え時の処理

#include <QApplication>
#include <QTabWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QDebug>

class MyWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        QTabWidget *tabWidget = new QTabWidget(this);

        QWidget *page1 = new QWidget();
        QVBoxLayout *layout1 = new QVBoxLayout(page1);
        layout1->addWidget(new QLabel("タブ1のコンテンツ"));
        tabWidget->addTab(page1, "タブ1");

        QWidget *page2 = new QiaWidget();
        QVBoxLayout *layout2 = new QVBoxLayout(page2);
        layout2->addWidget(new QLabel("タブ2のコンテンツ"));
        tabWidget->addTab(page2, "タブ2");

        // currentChanged シグナルにスロットを接続
        connect(tabWidget, &QTabWidget::currentChanged, this, &MyWidget::onTabChanged);

        // タブを閉じられるように設定し、tabCloseRequested シグナルに接続
        tabWidget->setTabsClosable(true);
        connect(tabWidget, &QTabWidget::tabCloseRequested, this, &MyWidget::onTabCloseRequested);

        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(tabWidget);
    }

private slots:
    void onTabChanged(int index)
    {
        qDebug() << "タブが変更されました。新しいインデックス:" << index;
        // ここでタブ切り替え時のカスタムロジックを記述
    }

    void onTabCloseRequested(int index)
    {
        qDebug() << "タブクローズが要求されました。インデックス:" << index;
        // ここでタブを閉じる前の確認などを記述し、条件に応じて removeTab を呼び出す
        if (index != -1) {
            static_cast<QTabWidget*>(sender())->removeTab(index); // sender() はシグナルを発したオブジェクト
        }
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyWidget w;
    w.resize(400, 300);
    w.show();
    return a.exec();
}
#include "main.moc" // mocファイルをインクルード (Visual Studioなどでのみ必要)

イベントフィルタの使用 (eventFilter)

event() をオーバーライドする方法よりも柔軟で、オブジェクトのイベント処理を監視・変更できます。特に、自身の子ウィジェットや他のオブジェクトのイベントを処理したい場合に非常に有効です。

仕組み:

  1. QObject から派生したクラスで eventFilter(QObject *watched, QEvent *event) 仮想関数をオーバーライドします。
  2. フィルターしたいオブジェクトに installEventFilter(this) を呼び出して、このフィルターをインストールします。
  3. eventFilter() 内で、watched オブジェクトがフィルターしたいオブジェクトであるか、event のタイプが処理したいタイプであるかをチェックします。
  4. イベントを処理した場合は true を返し、それ以上イベントが伝播しないようにします。イベントを処理しない場合は QObject::eventFilter(watched, event) を呼び出して、デフォルトの動作を継続させます。

メリット:

  • 既存のクラスを継承せずにイベントを監視できるため、柔軟性が高い。
  • 複数のオブジェクトに対して同じイベントフィルターを適用できる。
  • イベント処理のロジックを別のクラスに分離できるため、コードの責務が明確になる。

使用例: タブバーへのクリックをフィルタリングして特定のタブ切り替えを阻止

QTabWidgetevent() をオーバーライドする代わりに、QTabWidget::tabBar() が返す QTabBar オブジェクトにイベントフィルターをインストールします。

#include <QApplication>
#include <QTabWidget>
#include <QTabBar>
#include <QLabel>
#include <QVBoxLayout>
#include <QDebug>
#include <QMouseEvent>
#include <QMessageBox>

class TabChangeBlocker : public QObject
{
    Q_OBJECT
public:
    explicit TabChangeBlocker(QObject *parent = nullptr) : QObject(parent) {}

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        // 監視対象が QTabBar であり、マウスプレスイベントである場合
        if (watched->isWidgetType() && event->type() == QEvent::MouseButtonPress) {
            QTabBar *tabBar = qobject_cast<QTabBar*>(watched);
            if (tabBar) {
                QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
                int clickedTabIndex = tabBar->tabAt(mouseEvent->pos());

                // 例: インデックス1のタブへの切り替えを阻止
                if (clickedTabIndex == 1) {
                    qDebug() << "タブバーでインデックス1のタブがクリックされました。";
                    QMessageBox::StandardButton reply;
                    reply = QMessageBox::question(nullptr, "タブ切り替えの確認",
                                                  "このタブに切り替えますか? (イベントフィルタ)",
                                                  QMessageBox::Yes | QMessageBox::No);
                    if (reply == QMessageBox::No) {
                        qDebug() << "タブ切り替えをブロックしました。";
                        return true; // イベントを消費し、QTabBar のデフォルト処理を停止
                    }
                }
            }
        }
        // その他のイベントや、処理しない場合は基底クラスの eventFilter を呼び出す
        return QObject::eventFilter(watched, event);
    }
};

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

    QTabWidget *tabWidget = new QTabWidget();

    // タブ1
    QWidget *page1 = new QWidget();
    QVBoxLayout *layout1 = new QVBoxLayout(page1);
    layout1->addWidget(new QLabel("タブ A のコンテンツ"));
    tabWidget->addTab(page1, "タブ A");

    // タブ2 (イベントフィルタで阻止する対象)
    QWidget *page2 = new QWidget();
    QVBoxLayout *layout2 = new QVBoxLayout(page2);
    layout2->addWidget(new QLabel("タブ B (確認あり) のコンテンツ"));
    tabWidget->addTab(page2, "タブ B (確認あり)");

    // タブ3
    QWidget *page3 = new QWidget();
    QVBoxLayout *layout3 = new QVBoxLayout(page3);
    layout3->addWidget(new QLabel("タブ C のコンテンツ"));
    tabWidget->addTab(page3, "タブ C");

    // タブバーにイベントフィルタをインストール
    TabChangeBlocker *blocker = new TabChangeBlocker(tabWidget); // 親を tabWidget に設定
    tabWidget->tabBar()->installEventFilter(blocker);

    tabWidget->setWindowTitle("イベントフィルタの例");
    tabWidget->resize(400, 300);
    tabWidget->show();

    return a.exec();
}
#include "main.moc"

専用のイベントハンドラ関数をオーバーライドする

QTabWidget::event() は、Qt のイベントシステムにおける一般的な入り口です。しかし、ほとんどの主要なイベントタイプ(マウスプレス、キープレス、ペイント、サイズ変更など)には、それぞれ専用の仮想関数が用意されています。

  • など。
  • void resizeEvent(QResizeEvent *event) override;
  • void paintEvent(QPaintEvent *event) override;
  • void keyPressEvent(QKeyEvent *event) override;
  • void mousePressEvent(QMouseEvent *event) override;

メリット:

  • event() をオーバーライドするよりも、Qt のフレームワークに沿った自然な方法。
  • イベントのタイプを自分で判定する必要がない。
  • 特定の種類のイベントに特化できるため、コードがより整理される。
#include <QApplication>
#include <QTabWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QDebug>
#include <QResizeEvent> // QResizeEvent を使うために必要

class MyResizingTabWidget : public QTabWidget
{
    Q_OBJECT
public:
    explicit MyResizingTabWidget(QWidget *parent = nullptr) : QTabWidget(parent)
    {
        addTab(new QLabel("タブ1"), "Tab 1");
        addTab(new QLabel("タブ2"), "Tab 2");
    }

protected:
    // resizeEvent をオーバーライド
    void resizeEvent(QResizeEvent *event) override
    {
        qDebug() << "MyResizingTabWidget のサイズが変更されました!";
        qDebug() << "新しいサイズ:" << event->size();
        qDebug() << "以前のサイズ:" << event->oldSize();

        // ★重要: 基底クラスの resizeEvent() を呼び出すことで、QTabWidget本来のサイズ調整処理が実行される
        QTabWidget::resizeEvent(event);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyResizingTabWidget tabWidget;
    tabWidget.setWindowTitle("resizeEvent の例");
    tabWidget.resize(400, 300);
    tabWidget.show();
    return a.exec();
}
#include "main.moc"
  • event() のオーバーライド」: 上記のどの方法でも目的が達成できない、あるいは複数の異なる種類のイベントを非常に低レベルで一元的に処理する必要がある場合にのみ検討します。これは最も強力ですが、誤用すると Qt のイベントシステムを壊す可能性があるため、慎重に使用する必要があります。
  • 「イベントフィルタ」: 他のオブジェクト(子ウィジェット、他のQtオブジェクト)のイベントを監視・変更したい場合、または自身のクラスを継承せずにイベント処理を分離したい場合に非常に強力です。
  • 「専用のイベントハンドラ」: 特定のイベントタイプ(マウス、キー、ペイント、サイズ変更など)に対してウィジェット自身の挙動をカスタマイズしたいが、event() をオーバーライドするほど低レベルな制御は不要な場合に適しています。
  • 最も推奨されるのは「シグナルとスロット」: 特定の操作(タブ変更、クローズ要求など)に対応したい場合は、まずシグナルが提供されているかを確認し、あればそれを使うのが最もシンプルで Qt らしい方法です。