Qt

[C++] Qt QStackedWidgetで実現するウィジェット切り替え基本操作

QStackedWidgetはQtで利用できるウィジェットの一つで、画面に複数の子ウィジェットを積み重ね、必要に応じて一つだけ表示する仕組みです。

各ウィジェットをaddWidgetで追加し、setCurrentWidgetsetCurrentIndexで表示を切り替えるため、ウィザード形式などで快適に利用できるのが特徴です。

基本操作

ウィジェットの追加

addWidgetによる追加

QStackedWidgetにウィジェットを追加する操作は、シンプルな使い方としてよく利用されます。

addWidgetメソッドを用いると、順番にウィジェットをスタックに積む形で管理できます。

例えば、以下のサンプルコードでは、2つのウィジェットをQStackedWidgetへ追加する操作を示しています。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    // メインウィジェットの作成
    QWidget mainWindow;
    mainWindow.setWindowTitle("addWidgetでのウィジェット追加サンプル");
    // QStackedWidgetのインスタンスを作成
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    // 複数ページとなるウィジェットの作成
    QWidget *page1 = new QWidget;
    page1->setStyleSheet("background-color: lightblue;");
    QWidget *page2 = new QWidget;
    page2->setStyleSheet("background-color: lightgreen;");
    // addWidgetによるウィジェットの追加
    stackedWidget->addWidget(page1);
    stackedWidget->addWidget(page2);
    // QVBoxLayoutにウィジェットを組み込む
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
アプリケーションが起動し、青と緑の背景色を持つ2つのページがスタックされているウィンドウが表示されます

このサンプルでは、addWidgetを用いることでウィジェットを簡単に追加できる点を確認できます。

使い方が直感的なため、基本的なウィジェット管理に向いています。

insertWidgetでの位置指定

insertWidgetを利用すると、ウィジェットの挿入位置を指定することができます。

ウィジェットをスタック内の任意の位置に配置したい場合に役立つ機能です。

例えば、2ページ目と3ページ目の間に新しいウィジェットを挿入する場合、以下のようなコードが利用できます。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget mainWindow;
    mainWindow.setWindowTitle("insertWidgetで位置指定サンプル");
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    QWidget *page1 = new QWidget;
    page1->setStyleSheet("background-color: pink;");
    QWidget *page2 = new QWidget;
    page2->setStyleSheet("background-color: orange;");
    QWidget *page3 = new QWidget;
    page3->setStyleSheet("background-color: yellow;");
    // 最初にpage1とpage3を追加
    stackedWidget->addWidget(page1);
    stackedWidget->addWidget(page3);
    // page2をpage1とpage3の間に挿入
    stackedWidget->insertWidget(1, page2);
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
オレンジ、黄色、ピンクの色が割り当てられた各ページが、挿入順序に応じて管理されるウィンドウが表示されます

上記のコードでは、insertWidgetを使ってウィジェットを特定の位置に配置する操作を示しています。

挿入位置を整数で指定すれば、ウィジェットの順序を柔軟に制御できる点が魅力です。

ウィジェットの切り替え

setCurrentIndexを利用した切り替え

setCurrentIndexを使用すると、ウィジェットのスタック内のインデックス番号を設定して切り替える方法が利用できます。

この方法は、ウィジェットの順序が明確に管理されている場合に便利です。

以下のコード例では、インデックスを指定することで表示内容を変更する動作を示しています。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget mainWindow;
    mainWindow.setWindowTitle("setCurrentIndexによるウィジェット切り替えサンプル");
    // QStackedWidgetの作成
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    QWidget *page1 = new QWidget;
    page1->setStyleSheet("background-color: lavender;");
    QWidget *page2 = new QWidget;
    page2->setStyleSheet("background-color: khaki;");
    stackedWidget->addWidget(page1);
    stackedWidget->addWidget(page2);
    // 切り替えボタンの作成とレイアウトの設定
    QPushButton *switchButton = new QPushButton("ウィジェット切り替え");
    QObject::connect(switchButton, &QPushButton::clicked, [=](){
        // 現在のインデックスに応じて切り替える
        int currentIndex = stackedWidget->currentIndex();
        int nextIndex = (currentIndex + 1) % stackedWidget->count();
        stackedWidget->setCurrentIndex(nextIndex);
    });
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    layout->addWidget(switchButton);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
<<ウィジェットが切り替わる動作を確認できるウィンドウが表示され、切り替えボタンを押すと背景色が変わります>>

この実装では、インデックスを用いてページの順番を制御しており、簡単な切り替えロジックを導入できる点が特徴です。

setCurrentWidgetによる指定

setCurrentWidgetメソッドは、スタック内の対象ウィジェットのポインタを直接指定して切り替える方法です。

ウィジェットの順序やインデックスに依存せず、特定のウィジェットを選択する場合に便利です。

例として、以下のサンプルコードでは、ボタン操作で特定のウィジェットに切り替える処理を示しています。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget mainWindow;
    mainWindow.setWindowTitle("setCurrentWidgetによるウィジェット切り替えサンプル");
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    QWidget *pageA = new QWidget;
    pageA->setStyleSheet("background-color: coral;");
    QWidget *pageB = new QWidget;
    pageB->setStyleSheet("background-color: lightgray;");
    stackedWidget->addWidget(pageA);
    stackedWidget->addWidget(pageB);
    // 切り替え対象に応じたボタンを生成
    QPushButton *buttonA = new QPushButton("Page Aを表示");
    QPushButton *buttonB = new QPushButton("Page Bを表示");
    QObject::connect(buttonA, &QPushButton::clicked, [=](){
        stackedWidget->setCurrentWidget(pageA);
    });
    QObject::connect(buttonB, &QPushButton::clicked, [=](){
        stackedWidget->setCurrentWidget(pageB);
    });
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    layout->addWidget(buttonA);
    layout->addWidget(buttonB);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
<<Page AおよびPage Bの表示がボタン操作で切り替えられるウィンドウが表示されます>>

この方法では、ウィジェットそのもののポインタで切り替えを管理できるため、インデックスの連携が不要になり、特定のウィジェットへの直接アクセスが可能になります。

ウィジェットの削除

removeWidgetの利用

スタックからウィジェットを削除する必要がある場合は、removeWidgetメソッドを利用します。

削除自体はウィジェットの表示から除外する動作で、必要に応じてその後メモリ解放などの処理が必要な場合があります。

例えば、以下のサンプルコードではウィジェットを削除する操作をシンプルに表現しています。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget mainWindow;
    mainWindow.setWindowTitle("removeWidgetの利用サンプル");
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    QWidget *pageOne = new QWidget;
    pageOne->setStyleSheet("background-color: lightcoral;");
    QWidget *pageTwo = new QWidget;
    pageTwo->setStyleSheet("background-color: lightseagreen;");
    stackedWidget->addWidget(pageOne);
    stackedWidget->addWidget(pageTwo);
    QPushButton *removeButton = new QPushButton("Page Oneを削除");
    QObject::connect(removeButton, &QPushButton::clicked, [=](){
        // pageOneをスタックから削除
        stackedWidget->removeWidget(pageOne);
        // 必要に応じてdeleteする処理を実行
        pageOne->deleteLater();
    });
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    layout->addWidget(removeButton);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
<<ボタンをクリックすると、Page Oneがスタックから削除され、残りのウィジェットのみが表示されます>>

この例では、removeWidgetを使ってウィジェットを削除した後、deleteLater関数を呼び出すことでメモリ解放にも配慮しています。

ウィジェットの削除処理は適切なタイミングで行うと、不要なリソース消費を防ぐことにつながります。

動的なウィジェット管理

ウィジェットの生成と破棄

作成タイミングのポイント

動的にウィジェットを生成する場合、ユーザーの操作や特定のイベントに合わせてページを作成できる点が魅力です。

たとえば、ウィザード形式のインターフェースで次のページを生成する際などに利用できます。

作成タイミングとしては、ユーザー入力に従って生成するか、画面遷移時に生成する方式などが考えられます。

実際の利用シーンに合わせて、初期化のタイミングを柔軟に調整することが大切です。

削除時の注意点

動的に生成したウィジェットを削除する際は、削除前にウィジェットが正しくスタックから除外されているか確認する必要があります。

削除するウィジェットがまだ参照されている場合、不安定な動作やメモリリークの原因になることがあります。

特に、シグナルとスロットでウィジェット間を連携している場合、削除後の参照先が適切に解放されるようにする対策を講じると安心です。

メモリ管理の考慮

親子関係での管理

Qtのウィジェットは、親子関係によりメモリ管理が自動で行われる仕組みを持っています。

たとえば、QStackedWidgetを親ウィジェットとして各子ウィジェットを追加すると、親ウィジェットの破棄時に子ウィジェットもまとめて破棄されるので、手動でdeleteする必要がなくなります。

この仕組みを上手に活用すれば、メモリリークのリスクを軽減できます。

リソース解放の確認

動的に作成したウィジェットを削除する場合は、リソースが正しく解放されるように確認することが大切です。

特に、外部リソースにアクセスするウィジェットの場合、終了処理やリソースの明示的な解放を忘れずに行うと安全です。

場合によっては、Qtのメモリ管理に依存せず手動解放のコードを追加することも検討してください。

UIへの統合

レイアウトへの組み込み

QVBoxLayoutとの連携

QStackedWidgetは、レイアウトに組み込むとウィンドウ全体のUIとして管理しやすくなります。

特にQVBoxLayoutを利用すると、上部にナビゲーション、下部に切り替え操作といった配置も容易に実現できます。

以下に、QVBoxLayoutを用いた簡単なサンプルコードを示します。

#include <QApplication>
#include <QWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget mainWindow;
    mainWindow.setWindowTitle("QVBoxLayoutとQStackedWidgetの連携サンプル");
    QStackedWidget *stackedWidget = new QStackedWidget(&mainWindow);
    QWidget *upperPage = new QWidget;
    upperPage->setStyleSheet("background-color: lightsteelblue;");
    QWidget *lowerPage = new QWidget;
    lowerPage->setStyleSheet("background-color: beige;");
    stackedWidget->addWidget(upperPage);
    stackedWidget->addWidget(lowerPage);
    QPushButton *switchButton = new QPushButton("ページを切替");
    QObject::connect(switchButton, &QPushButton::clicked, [=](){
        int currentIndex = stackedWidget->currentIndex();
        int nextIndex = (currentIndex + 1) % stackedWidget->count();
        stackedWidget->setCurrentIndex(nextIndex);
    });
    QVBoxLayout *layout = new QVBoxLayout(&mainWindow);
    layout->addWidget(stackedWidget);
    layout->addWidget(switchButton);
    mainWindow.setLayout(layout);
    mainWindow.resize(400, 300);
    mainWindow.show();
    return app.exec();
}
<<上部にウィジェット、下部にボタンが配置されたウィンドウが表示され、ボタン操作でページが切り替わります>>

QHBoxLayoutなど他レイアウトの活用

場合によってはQHBoxLayoutやグリッドレイアウトと組み合わせることで、より複雑なUI構造を構築できます。

たとえば、左右に切り替えメニューを配置したいときは、QHBoxLayoutを利用して以下のように実装できます。

  • 左側に操作パネル
  • 右側にQStackedWidgetを配置

それぞれのレイアウトの特性を活かすと、柔軟なウィンドウ設計ができるメリットがあります。

シグナルとスロットの連動

切り替えトリガーの設定

Qtのシグナルとスロットの機能を利用すると、ウィジェットの切り替え操作をシンプルに連動できるため、より対話的なUIが実現可能です。

たとえば、ボタンなどのシグナルを発生させ、スタック内のウィジェット切り替えを行う方法がよく使われます。

ボタンのクリックと連動してページが切り替わる動作は、ユーザーにとって分かりやすいインターフェースを提供します。

状態管理の連動処理

複数のUI要素間で連動処理を実現する場合、ウィジェットの状態変更と同時に、他のウィジェットの状態を更新する工夫が求められることもあります。

たとえば、現在表示されているページに応じて、ステータスバーや他の入力フォームの状態を自動的に変更する実装などが考えられます。

各ウィジェットの状態をシステム全体で共有することで、一貫性のあるUI操作が可能となります。

エラーチェックとトラブルシューティング

共通エラー事例

追加時の問題と対策

ウィジェットの追加時に発生するエラーとしては、重複して追加してしまった場合や、無効なポインタを渡してしまうケースが見受けられます。

そのため、追加前にウィジェットが正しく初期化されていることを確認し、重複追加を防ぐ仕組みを導入すると安心です。

以下のチェックリストを参考にするとよいでしょう。

  • ウィジェットがすでにスタックに存在しないか確認
  • ポインタがnullptrではないかチェック
  • レイアウトに正しく配置されているか再確認

切り替え時のエラー確認

切り替え操作時には、指定したインデックスやウィジェットが実際にスタック内に存在するかを確認する必要があります。

インデックス指定時は、currentIndex()count()を活用して整合性を保つと安心です。

また、ウィジェットが正しく削除されていた場合、切り替えで意図しない動作が発生することもあるため、エラーチェックをしっかり行うと安全です。

デバッグのポイント

シグナル確認の方法

シグナルとスロットの連携が想定通りに動作しているかを検証するため、デバッグ出力などを活用すると効果的です。

qDebug()を使ってシグナル発火時にメッセージを出力する方法や、専用のデバッガを利用してシグナル接続状況を確認するのがおすすめです。

デバッグ中にシグナルの連動がうまくいかない場合は、接続の漏れや間違いがないか再確認してください。

ウィジェット状態の検証

ウィジェットの状態確認には、QtのobjectNameプロパティを有効活用できるため、各ウィジェットに名前を設定しておくとデバッグがしやすくなります。

また、ランタイム中に各ウィジェットのプロパティやスタイルシートを確認するツールを使うと、状態異常の早期発見につながります。

拡張利用の可能性

カスタムウィジェットとの連携

プロパティの活用

カスタムウィジェットを利用する場合、ウィジェット自体に独自のプロパティを持たせることで、より柔軟な動作が実現できます。

例えば、ウィジェットごとに関連するデータをプロパティとして登録し、切り替え時にその情報を利用してUIを更新する方法があります。

デザイナーが意図する操作性を実現するため、プロパティの設定と管理に工夫するとよいでしょう。

イベント処理の拡充

カスタムウィジェットでは、標準のイベント処理に加えて独自のイベントを定義することも可能です。

たとえば、ウィジェットが切り替えられる直前に特定の処理を実行したり、切り替え後のアニメーションを追加したりすることができます。

イベントハンドリングに工夫を凝らすと、ユーザーにとってより直感的な操作性を高めることができます。

複雑なUI構造への応用

動的ページ生成の実装

複雑なUIアプリケーションでは、動的にページを生成する必要が出てくる場合があります。

ユーザーの操作や外部データの変化に合わせて、ページの数や内容をリアルタイムに変更できる仕組みを導入すると便利です。

動的生成では、ウィジェットの作成や削除のタイミング、またリソース管理を十分に考慮しながら実装することが大切です。

パフォーマンス最適化の考え方

ウィジェットが多数存在する場合、パフォーマンスへの影響が懸念されます。

パフォーマンス最適化のためには、以下の点を考慮するとよいでしょう。

  • 不要なウィジェットをバックグラウンドで管理しない
  • 更新が必要な部分のみを再描画する
  • イベント処理やシグナルの最適化を行う

これらの対策を講じると、複雑なUI構造でもユーザビリティを損なうことなく管理が可能となります。

まとめ

今回紹介したQStackedWidgetの基本操作から動的なウィジェット管理、さらにUIへの統合やトラブルシューティング、拡張利用まで、各機能の利用方法を柔らかく説明しました。

ユーザーの操作に合わせた柔軟なウィジェット管理と、適切なエラーチェックを導入することで、操作性の高いアプリケーションを実現できる仕組みを取り入れることができます。

各サンプルコードを参考に、実際の開発において使いやすいUI構築にチャレンジしてみてください。

関連記事

Back to top button
目次へ