【Qt】QTabWidget のタブ削除シグナル tabRemoved の使い方と注意点

2025-05-27

void QTabWidget::tabRemoved(int index) は、Qt の QTabWidget クラスで定義されているシグナルの一つです。

役割

このシグナルは、QTabWidget からタブが削除された後に発行(emit)されます。

引数

このシグナルは一つの引数を受け取ります。それは int index です。この index は、削除されたタブの元のインデックスを示します。つまり、タブが削除される前に QTabWidget 内で何番目に位置していたかを表す数値です(0から始まる)。

利用場面

このシグナルは、タブが削除された際に何らかの処理を行いたい場合に便利です。例えば、以下のような状況で利用できます。

  • タブの削除操作を取り消す機能を提供する(ただし、tabRemoved シグナル自体には取り消し機能はありません。別途実装が必要です)。
  • UI の他の部分を更新する(例えば、タブの総数を表示するラベルを更新するなど)。
  • タブの削除履歴を記録する。
  • 削除されたタブに関連付けられていたデータを解放する。

接続方法

このシグナルを利用するには、QObject::connect() 関数を使って、このシグナルとあなたが作成したスロット(シグナルが発行されたときに呼び出される関数)を接続する必要があります。


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

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new QTabWidget;
        QLabel *label1 = new QLabel("タブ 1 の内容");
        QLabel *label2 = new QLabel("タブ 2 の内容");
        tabWidget->addTab(label1, "タブ 1");
        tabWidget->addTab(label2, "タブ 2");

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

        connect(tabWidget, &QTabWidget::tabRemoved, this, &MyWidget::tabWasRemoved);
    }

private slots:
    void tabWasRemoved(int index) {
        qDebug() << "タブが削除されました。削除されたタブのインデックス:" << index;
        // ここで、削除されたタブに関連する処理を行うことができます。
    }

private:
    QTabWidget *tabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

この例では、MyWidget クラスのコンストラクタで tabWidgettabRemoved シグナルと MyWidget クラスの tabWasRemoved スロットを接続しています。タブが削除されると、tabWasRemoved スロットが呼び出され、削除されたタブのインデックスがデバッグ出力に表示されます。



スロットが呼び出されない

  • connect() の戻り値を確認する
    connect() 関数は接続が成功したかどうかを bool 型で返します。戻り値が false の場合は、接続に失敗しています。エラーメッセージ(あれば)を確認してください。
  • オブジェクトの生存期間
    シグナルを送信するオブジェクト (QTabWidget) またはスロットを受け取るオブジェクトが、シグナルが発行される前に破棄されている可能性があります。オブジェクトのライフサイクルを適切に管理してください。
  • シグナルとスロットが正しく接続されていない
    QObject::connect() 関数の引数が間違っている可能性があります。
    • シグナルの構文 (&QTabWidget::tabRemoved) が正しいか確認してください。
    • スロットの構文 (&YourClass::yourSlot) が正しいか確認してください。
    • sender オブジェクト(ここでは QTabWidget のインスタンス)と receiver オブジェクト(あなたのクラスのインスタンス)が正しいか確認してください。

スロット内で不正なインデックスを使用する

  • 存在しないインデックスへのアクセス
    スロット内で、削除されたインデックスを使って QTabWidget の他のメソッド(例えば widget(index)tabText(index)) を呼び出すと、不正なアクセスが発生する可能性があります。削除されたタブの情報にアクセスしたい場合は、シグナルが発行された時点の index を利用するか、削除前に必要な情報を保存しておく必要があります。
  • 削除されたインデックスの誤解
    tabRemoved(int index) で渡される index は、削除された時点でのタブのインデックスです。スロットが呼び出される時点では、他のタブのインデックスが変わっている可能性があります。

スロット内での処理による問題

  • スロット内での例外
    スロット内で例外が発生した場合、プログラムがクラッシュする可能性があります。例外処理 (try-catch) を適切に記述してください。
  • UI スレッドのブロッキング
    スロット内で時間のかかる処理を行うと、UI がフリーズして応答しなくなる可能性があります。時間のかかる処理は、別のスレッドで行うことを検討してください。
  • 再帰的なタブ操作
    スロット内で QTabWidget に対してさらにタブの追加や削除を行うと、予期しない動作や無限ループを引き起こす可能性があります。特に、同じシグナルを発生させるような操作は避けるべきです。

マルチスレッド環境での問題

  • 異なるスレッドからの UI 操作
    tabRemoved シグナルは通常、UI スレッドから発行されます。スロット内で別のスレッドから UI を直接操作すると、スレッドセーフではないため、問題が発生する可能性があります。UI の操作は、Qt::QueuedConnection などのメカニズムを使って UI スレッドで行うようにしてください。
  • ブレークポイントを使用する
    デバッガを使用して、シグナルが発行されるタイミングやスロット内の処理をステップ実行することで、問題の箇所を特定できます。
  • Qt のドキュメントを参照する
    QTabWidget クラスや QObject::connect() 関数のドキュメントを再度確認し、理解を深めることが重要です。
  • シンプルなテストケースを作成する
    問題が複雑な場合に、最小限のコードで問題を再現できるテストケースを作成することで、原因の特定が容易になります。
  • qDebug() を活用する
    シグナルが発行されたタイミング、渡されたインデックス、スロットが呼び出されたかどうかなどを qDebug() で出力して確認すると、問題の原因を特定しやすくなります。


例1: 削除されたタブのインデックスをコンソールに出力する

これは、最も基本的な例です。タブが削除されたときに、そのインデックスをデバッグ出力に表示します。

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

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new QTabWidget(this);
        tabWidget->addTab(new QLabel("タブ 1"), "タブ 1");
        tabWidget->addTab(new QLabel("タブ 2"), "タブ 2");
        tabWidget->addTab(new QLabel("タブ 3"), "タブ 3");

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

        connect(tabWidget, &QTabWidget::tabRemoved, this, &MyWidget::tabRemovedSlot);
    }

private slots:
    void tabRemovedSlot(int index) {
        qDebug() << "タブが削除されました。削除されたインデックス:" << index;
    }

private:
    QTabWidget *tabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

このコードでは、MyWidget クラス内に QTabWidget を作成し、3つのタブを追加しています。connect() 関数で tabWidgettabRemoved シグナルを MyWidget クラスの tabRemovedSlot スロットに接続しています。タブが削除されると、tabRemovedSlot が呼び出され、削除されたタブのインデックスが qDebug() で出力されます。

例2: 削除されたタブのテキストを表示する

削除されたタブのインデックスを使って、削除される前に保存しておいたタブのテキストを取得し、表示する例です。

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

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new QTabWidget(this);
        tabTexts << "タブ A" << "タブ B" << "タブ C";
        tabWidget->addTab(new QLabel("内容 A"), tabTexts[0]);
        tabWidget->addTab(new QLabel("内容 B"), tabTexts[1]);
        tabWidget->addTab(new QLabel("内容 C"), tabTexts[2]);

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

        connect(tabWidget, &QTabWidget::tabRemoved, this, &MyWidget::tabRemovedSlot);
    }

private slots:
    void tabRemovedSlot(int index) {
        if (index >= 0 && index < tabTexts.size()) {
            qDebug() << "削除されたタブのテキスト:" << tabTexts[index];
            // 必要であれば、tabTexts から削除することもできます
            // tabTexts.removeAt(index);
        } else {
            qDebug() << "無効なインデックス:" << index;
        }
    }

private:
    QTabWidget *tabWidget;
    QStringList tabTexts;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

この例では、タブのテキストを QStringList に保存しておき、tabRemovedSlot で削除されたインデックスに対応するテキストを取得して表示しています。

例3: タブが削除されたときに別のウィジェットを更新する

タブが削除されたときに、メインウィンドウのステータスバーにメッセージを表示する例です。

#include <QApplication>
#include <QMainWindow>
#include <QTabWidget>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QStatusBar>

class MainWindow : public QMainWindow {
public:
    MainWindow() {
        tabWidget = new QTabWidget(this);
        tabWidget->addTab(new QLabel("アイテム 1"), "タブ 1");
        tabWidget->addTab(new QLabel("アイテム 2"), "タブ 2");

        setCentralWidget(tabWidget);
        statusBar()->showMessage("準備完了");

        connect(tabWidget, &QTabWidget::tabRemoved, this, &MainWindow::tabRemovedSlot);
    }

private slots:
    void tabRemovedSlot(int index) {
        statusBar()->showMessage(QString("タブ %1 が削除されました").arg(index + 1));
    }

private:
    QTabWidget *tabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

この例では、MainWindowQTabWidget を含み、ステータスバーを持っています。tabRemovedSlot が呼び出されると、ステータスバーにどのタブが削除されたかを示すメッセージが表示されます。

例4: 削除されたタブに関連するデータを解放する

タブにカスタムのデータが関連付けられている場合に、タブが削除されたときにそのデータを解放する例です。ここでは、簡単な例として、タブのウィジェットを削除する処理を示します。

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

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

protected:
    void removeTab(int index) override {
        QWidget *widgetToRemove = this->widget(index);
        if (widgetToRemove) {
            qDebug() << "タブのウィジェットを削除します:" << widgetToRemove;
            widgetToRemove->deleteLater(); // 安全な削除のために deleteLater() を使用
        }
        QTabWidget::removeTab(index);
    }
};

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new MyTabWidget(this);
        tabWidget->addTab(new QLabel("データ 1"), "タブ 1");
        tabWidget->addTab(new QLabel("データ 2"), "タブ 2");

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

        connect(tabWidget, &MyTabWidget::tabRemoved, this, &MyWidget::tabRemovedSlot);
    }

private slots:
    void tabRemovedSlot(int index) {
        qDebug() << "タブが削除されました (シグナル経由):" << index;
        // ここで、タブに関連付けられた他のデータを解放する処理を追加できます
    }

private:
    MyTabWidget *tabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

この例では、QTabWidget を継承した MyTabWidget クラスで removeTab() 関数をオーバーライドし、タブが削除される前にそのウィジェットに対して deleteLater() を呼び出すようにしています。これにより、メモリリークを防ぐことができます。tabRemoved シグナルは、タブが実際に削除された後に発行されるため、このシグナルを受け取るスロットでは、削除後の処理(例えば、関連するデータの解放など)を行うことができます。



QTabWidget::currentChanged(int index) シグナルを利用する (間接的な検出)

タブが削除されると、通常はカレントタブが変更されます。この currentChanged シグナルを監視することで、タブの削除を間接的に検出できます。ただし、どのタブが削除されたかを直接知ることはできません。

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

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new QTabWidget(this);
        tabWidget->addTab(new QLabel("タブ 1"), "タブ 1");
        tabWidget->addTab(new QLabel("タブ 2"), "タブ 2");
        tabWidget->addTab(new QLabel("タブ 3"), "タブ 3");

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

        connect(tabWidget, &QTabWidget::currentChanged, this, &MyWidget::currentTabChanged);
    }

private slots:
    void currentTabChanged(int index) {
        qDebug() << "カレントタブが変更されました。現在のインデックス:" << index;
        // ここで、タブの数が減ったかどうかなどを確認して、削除を推測する
        // ただし、どのタブが削除されたかは不明
    }

private:
    QTabWidget *tabWidget;
    int previousTabCount = 3; // 初期タブ数
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

利点
カレントタブが変更された際の処理と統合しやすい。 欠点: どのタブが削除されたかを直接知ることができないため、追加の管理が必要になる場合がある。

タブを削除する処理を自身で管理する

もし、アプリケーション内でタブの削除処理を明示的に行う場所が一箇所にまとまっている場合(例えば、特定のボタンのクリック時など)、その削除処理の前後で必要な処理を直接記述することができます。

#include <QApplication>
#include <QTabWidget>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>
#include <QHBoxLayout>
#include <QDebug>

class MyWidget : public QWidget {
public:
    MyWidget() {
        tabWidget = new QTabWidget(this);
        tabWidget->addTab(new QLabel("タブ 1"), "タブ 1");
        tabWidget->addTab(new QLabel("タブ 2"), "タブ 2");

        removeButton = new QPushButton("タブ 1 を削除", this);

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

        connect(removeButton, &QPushButton::clicked, this, &MyWidget::removeFirstTab);
    }

private slots:
    void removeFirstTab() {
        if (tabWidget->count() > 0) {
            int removedIndex = 0;
            qDebug() << "タブを削除します。インデックス:" << removedIndex;
            // 削除前の処理
            QWidget *removedWidget = tabWidget->widget(removedIndex);
            if (removedWidget) {
                // 必要に応じて removedWidget を操作
            }
            tabWidget->removeTab(removedIndex);
            // 削除後の処理
            qDebug() << "タブが削除されました。";
        }
    }

private:
    QTabWidget *tabWidget;
    QPushButton *removeButton;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

利点
削除のタイミングと削除されるタブを正確に把握できる。 欠点: アプリケーション全体でタブの削除処理が分散している場合は、管理が煩雑になる可能性がある。

QTabWidget を継承して removeTab() をオーバーライドする

QTabWidget を継承したカスタムクラスを作成し、その中で removeTab() 関数をオーバーライドすることで、タブが削除される前後に独自の処理を追加できます。そして、オーバーライドした removeTab() 関数内で tabRemoved シグナルを明示的に発行することも可能です(ただし、QTabWidget のデフォルト実装でも tabRemoved シグナルは発行されます)。

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

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

protected:
    void removeTab(int index) override {
        qDebug() << "removeTab が呼び出されました。インデックス:" << index;
        QWidget *widgetToRemove = widget(index);
        if (widgetToRemove) {
            qDebug() << "削除されるウィジェット:" << widgetToRemove;
            // 必要に応じて削除前の処理
        }
        QTabWidget::removeTab(index);
        // QTabWidget のデフォルト実装で tabRemoved シグナルが発行される
        qDebug() << "removeTab の処理が完了しました。";
    }

signals:
    void customTabRemoved(int index, QWidget *removedWidget);

public slots:
    void onTabRemoved(int index) {
        QWidget *removedWidget = widget(index);
        emit customTabRemoved(index, removedWidget);
    }
};

class MyWidget : public QWidget {
public:
    MyWidget() {
        customTabWidget = new CustomTabWidget(this);
        customTabWidget->addTab(new QLabel("カスタムタブ 1"), "カスタムタブ 1");
        customTabWidget->addTab(new QLabel("カスタムタブ 2"), "カスタムタブ 2");

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(customTabWidget);

        connect(customTabWidget, &CustomTabWidget::tabRemoved, this, &MyWidget::tabRemovedSlot);
        connect(customTabWidget, &CustomTabWidget::customTabRemoved, this, &MyWidget::customTabRemovedSlot);
    }

private slots:
    void tabRemovedSlot(int index) {
        qDebug() << "標準の tabRemoved シグナルを受信。インデックス:" << index;
    }

    void customTabRemovedSlot(int index, QWidget *removedWidget) {
        qDebug() << "カスタムの customTabRemoved シグナルを受信。インデックス:" << index << ", ウィジェット:" << removedWidget;
        // ここで、削除されたウィジェットに対する処理などを行う
    }

private:
    CustomTabWidget *customTabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
    return a.exec();
}

#include "main.moc" // moc ファイルをインクルード

利点
タブの削除処理をカスタマイズできる。削除前後の処理を removeTab() 内に集約できる。 欠点: QTabWidget の動作を深く理解する必要がある。

イベントフィルターを使用する

QTabWidget にイベントフィルターをインストールし、タブが削除される際に発生する可能性のあるイベント(例えば、子ウィジェットの削除イベントなど)を監視することで、タブの削除を間接的に検出できます。ただし、どのタブが削除されたかを正確に特定するのは難しい場合があります。

利点
他のオブジェクトの動作を監視できる汎用性がある。 欠点: タブの削除を直接的に知ることは難しく、イベントの種類によっては信頼性が低い場合がある。