QWidget サイズ変更時の処理:resizeEvent() の基本と応用【Qt】

2025-05-16

void QWidget::resizeEvent(QResizeEvent *event) は、Qtのウィジェット(QWidgetを継承するオブジェクト)のサイズが変更された際に自動的に呼び出される特別な仮想関数(virtual function)です。

この関数を**再実装(override)**することで、ウィジェットのサイズ変更に応じて特別な処理を行うことができます。たとえば、以下のような場合に resizeEvent() を再実装します。

  • ウィジェットの新しいサイズに関する情報を取得し、他の処理に利用したい場合。
  • ウィジェットのサイズ変更に応じて、何らかのカスタムな描画を行いたい場合。
  • ウィジェットの内部要素(子ウィジェット、描画内容など)の配置やサイズを、新しいウィジェットのサイズに合わせて調整したい場合。

resizeEvent() 関数が呼び出される際には、引数として QResizeEvent クラスのポインタ event が渡されます。この event オブジェクトを通じて、以下のような情報を取得できます。

  • 新しいサイズ (new size)
    サイズ変更後のウィジェットのサイズ。event->size() で取得できます。
  • 古いサイズ (old size)
    サイズ変更前のウィジェットのサイズ。event->oldSize() で取得できます。

具体的な使用例のイメージ

例えば、カスタムウィジェット内で複数の子ウィジェットを配置しており、親ウィジェットのサイズが変更された際に、子ウィジェットが常に均等に配置されるようにしたいとします。この場合、resizeEvent() 関数を再実装し、新しいウィジェットのサイズに基づいて子ウィジェットの位置とサイズを計算し直す処理を記述します。



一般的なエラーとトラブルシューティング

    • エラー
      古いサイズと新しいサイズを混同して使用してしまうことがあります。例えば、新しいサイズに基づいて計算すべき箇所で古いサイズを使ってしまうなどです。
    • トラブルシューティング
      どのタイミングでどのサイズが必要なのかを正確に理解し、意図したとおりの変数を使用しているか確認してください。デバッガを使用して、サイズの変化を追跡するのも有効です。
  1. 無限ループの発生

    • エラー
      resizeEvent() の中で、ウィジェットのサイズをプログラム的に変更する処理(例えば setGeometry()resize() の呼び出し)を行うと、再び resizeEvent() がトリガーされ、無限ループに陥ることがあります。
    • トラブルシューティング
      resizeEvent() 内で直接的にウィジェットのサイズを変更することは極力避けるべきです。もしサイズ変更が必要な場合は、タイマー (QTimer::singleShot()) を使ってイベントループを一度戻してから実行するなど、間接的な方法を検討してください。
  2. レイアウトマネージャーとの干渉

    • エラー
      ウィジェットがレイアウトマネージャー(QVBoxLayout, QHBoxLayout, QGridLayout など)によって管理されている場合、resizeEvent() 内で手動で子ウィジェットのジオメトリを設定しようとすると、レイアウトマネージャーの動作と競合し、意図しない表示になることがあります。
    • トラブルシューティング
      レイアウトマネージャーを使用している場合は、可能な限りレイアウトマネージャーの機能を利用して子ウィジェットの配置やサイズ調整を行うべきです。resizeEvent() は、レイアウトマネージャーでは対応できない特別な処理が必要な場合にのみ使用しましょう。もし手動でジオメトリを設定する必要がある場合は、レイアウトマネージャーを無効にする (setLayout(nullptr)) ことを検討してください。
  3. 描画処理の効率

    • エラー
      resizeEvent() 内で非常に重い描画処理を行うと、ウィンドウのリサイズ操作が遅延し、ユーザーエクスペリエンスを損なう可能性があります。
    • トラブルシューティング
      描画処理はできるだけ効率的に行うように心がけましょう。複雑な描画は、必要最小限の領域のみを再描画するように最適化したり、バックグラウンドスレッドで処理することを検討してください。update() 関数を適切に呼び出すことも重要です。
  4. 親ウィジェットのサイズ変更への対応漏れ

    • エラー
      子ウィジェットの resizeEvent() で、親ウィジェットのサイズ変更に適切に対応できていない場合があります。
    • トラブルシューティング
      親ウィジェットのサイズが変更された際に、子ウィジェットがどのように振る舞うべきかを考慮し、resizeEvent() 内で必要な調整を行いましょう。親ウィジェットのサイズを取得するには、parentWidget()->size() などを使用できます。
  5. 初期化忘れ

    • エラー
      resizeEvent() 内で使用する変数やリソースが適切に初期化されていないと、予期せぬ動作を引き起こすことがあります。
    • トラブルシューティング
      resizeEvent() で使用する変数は、コンストラクタや初期化処理で適切に初期化されていることを確認してください。
  6. シグナルとスロットの誤用

    • エラー
      resizeEvent() 内でシグナルを発行し、それに対するスロットでウィジェットのサイズを変更するような処理を行う場合、意図しないタイミングで処理が実行されたり、無限ループに繋がる可能性があります。
    • トラブルシューティング
      サイズ変更に関連する処理は、できるだけ resizeEvent() 内で直接行うか、タイマーなどを利用して制御するようにしましょう。

デバッグのヒント

  • シンプルなテストケースの作成
    問題が複雑な場合は、最小限のコードで問題を再現できるテストケースを作成し、原因の特定を試みましょう。
  • ブレークポイントの設定
    デバッガを使用して resizeEvent() にブレークポイントを設定し、ステップ実行しながら変数の状態を確認しましょう。
  • qDebug() の活用
    resizeEvent() の中で、ウィジェットのサイズや関連する変数の値を qDebug() で出力して、処理の流れや値の変化を確認しましょう。


例1: サイズ変更に合わせて円の半径を調整するカスタムウィジェット

この例では、ウィジェットのサイズが変更されるたびに、描画される円の半径がウィジェットの小さい方の辺の半分になるように調整します。

#include <QtWidgets/QWidget>
#include <QPainter>
#include <QResizeEvent>
#include <QtDebug>

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

protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);

        int side = qMin(width(), height());
        int radius = side / 2;
        QPoint center(width() / 2, height() / 2);

        painter.drawEllipse(center, radius, radius);
    }

    void resizeEvent(QResizeEvent *event) override {
        qDebug() << "ウィジェットのサイズが変更されました: 新しいサイズ =" << event->size() << ", 古いサイズ =" << event->oldSize();
        // サイズが変更されたので、再描画を要求します
        update();
        // 親クラスの resizeEvent() も忘れずに呼び出す
        QWidget::resizeEvent(event);
    }
};

解説

  • resizeEvent(QResizeEvent *event) 関数がオーバーライドされています。
    • この関数が呼び出されると、qDebug() で新しいサイズと古いサイズが出力されます。
    • update() 関数を呼び出すことで、ウィジェットの再描画がスケジュールされます。これにより、新しいサイズに合わせて paintEvent() が再度実行され、円が新しいサイズで描画されます。
    • QWidget::resizeEvent(event) を呼び出すことで、親クラスのデフォルトのサイズ変更処理も実行されます。これは通常、忘れずに記述する必要があります。
  • paintEvent() 関数は、ウィジェットの内容を描画するためにオーバーライドされています。ここでは、ウィジェットの中央に円を描画しています。円の半径は、ウィジェットの幅と高さのうち小さい方の半分として計算されます。
  • CircleWidget クラスは QWidget を継承しています。

例2: サイズ変更に合わせて子ウィジェットの配置を調整するカスタムウィジェット

この例では、カスタムウィジェット内に配置された子ウィジェットの位置を、親ウィジェットのサイズ変更に合わせて調整します。

#include <QtWidgets/QWidget>
#include <QPushButton>
#include <QResizeEvent>
#include <QDebug>

class ParentWidget : public QWidget {
public:
    ParentWidget(QWidget *parent = nullptr) : QWidget(parent) {
        button1 = new QPushButton("ボタン1", this);
        button2 = new QPushButton("ボタン2", this);
    }

protected:
    void resizeEvent(QResizeEvent *event) override {
        QSize newSize = event->size();
        int buttonWidth = 100;
        int buttonHeight = 30;
        int margin = 10;

        // ボタン1を左上に配置
        button1->setGeometry(margin, margin, buttonWidth, buttonHeight);

        // ボタン2を右下に配置
        button2->setGeometry(newSize.width() - buttonWidth - margin,
                             newSize.height() - buttonHeight - margin,
                             buttonWidth, buttonHeight);

        qDebug() << "親ウィジェットのサイズが変更されました: " << newSize;

        // 親クラスの resizeEvent() も忘れずに呼び出す
        QWidget::resizeEvent(event);
    }

private:
    QPushButton *button1;
    QPushButton *button2;
};

解説

  • ここでも、QWidget::resizeEvent(event) を呼び出して、親クラスのデフォルトの処理を維持しています。
  • サイズ変更のたびに、ボタンの位置が再計算され、適切な場所に移動します。
  • resizeEvent(QResizeEvent *event) 関数では、新しい親ウィジェットのサイズ (event->size()) を取得し、それに基づいて button1 を左上に、button2 を右下に配置するように setGeometry() を呼び出しています。
  • ParentWidget クラスは QWidget を継承し、2つの QPushButton の子ウィジェットを持っています。

例3: サイズ変更時に特定の条件を満たす場合に処理を行う

この例では、ウィジェットの幅が高さよりも大きくなった場合にのみ、特定の処理(ここではデバッグ出力)を実行します。

#include <QtWidgets/QWidget>
#include <QResizeEvent>
#include <QDebug>

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

protected:
    void resizeEvent(QResizeEvent *event) override {
        QSize newSize = event->size();
        QSize oldSize = event->oldSize();

        if (newSize.width() > newSize.height()) {
            qDebug() << "幅が高さよりも大きくなりました: 新しいサイズ =" << newSize;
            // ここで、幅が高さより大きい場合の処理を記述します
        } else if (oldSize.width() > oldSize.height() && newSize.width() <= newSize.height()) {
            qDebug() << "幅が高さ以下になりました: 新しいサイズ =" << newSize;
            // ここで、幅が高さ以下になった場合の処理を記述します
        }

        // 親クラスの resizeEvent() も忘れずに呼び出す
        QWidget::resizeEvent(event);
    }
};
  • 古いサイズ (event->oldSize()) と新しいサイズを比較することで、状態の変化を検知し、異なる処理を行うことも可能です。
  • resizeEvent() 関数内で、新しいサイズ (event->size()) の幅と高さを比較し、幅が高さよりも大きい場合に特定のメッセージを qDebug() で出力しています。


resizeEvent() の代替となる主な方法

    • 説明
      QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout などのレイアウトマネージャーは、ウィジェットのサイズ変更に応じて、子ウィジェットのサイズや位置を自動的に調整します。多くの場合、resizeEvent() を直接扱うよりも、適切なレイアウトマネージャーを使用する方が簡単かつ効率的です。
    • 利点
      • コードが簡潔になり、視覚的な配置の意図が明確になります。
      • ウィンドウのリサイズに対する子ウィジェットの自動的な調整が容易です。
      • さまざまな配置ポリシー(ストレッチファクター、アラインメントなど)を簡単に設定できます。
    • 欠点
      • レイアウトマネージャーの機能だけでは実現できない、複雑なカスタムレイアウトが必要な場合があります。
    • 使用例
      #include <QtWidgets/QWidget>
      #include <QtWidgets/QPushButton>
      #include <QtWidgets/QVBoxLayout>
      
      class MyWidget : public QWidget {
      public:
          MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
              QVBoxLayout *layout = new QVBoxLayout(this);
              QPushButton *button1 = new QPushButton("上", this);
              QPushButton *button2 = new QPushButton("下", this);
              layout->addWidget(button1);
              layout->addWidget(button2);
              setLayout(layout); // レイアウトを設定
          }
      };
      
      この例では、QVBoxLayout がウィジェットのサイズ変更に合わせてボタンのサイズと位置を自動的に調整します。resizeEvent() は明示的にオーバーライドされていません。
  1. シグナルとスロット (Signals and Slots) の利用

    • 説明
      ウィジェットのサイズが変更されたことを示すシグナル (resize) は存在しませんが、必要であれば、resizeEvent() 内でカスタムシグナルを発行し、そのシグナルに接続されたスロットで必要な処理を行うことができます。
    • 利点
      • サイズ変更の処理を、ウィジェットのクラス外の別のオブジェクトや関数に委譲できます。
      • 関心の分離 (Separation of Concerns) を高めることができます。
    • 欠点
      • resizeEvent() のオーバーライドとシグナルの定義、接続が必要となり、やや冗長になる場合があります。
    • 使用例
      #include <QtWidgets/QWidget>
      #include <QResizeEvent>
      #include <QDebug>
      #include <QSignalMapper>
      
      class ResizableWidget : public QWidget {
          Q_OBJECT
      signals:
          void resized(const QSize &newSize, const QSize &oldSize);
      
      protected:
          void resizeEvent(QResizeEvent *event) override {
              emit resized(event->size(), event->oldSize());
              QWidget::resizeEvent(event);
          }
      
      public:
          ResizableWidget(QWidget *parent = nullptr) : QWidget(parent) {}
      };
      
      class SizeReporter : public QObject {
          Q_OBJECT
      public slots:
          void reportSize(const QSize &newSize, const QSize &oldSize) {
              qDebug() << "サイズが変更されました - 新しいサイズ:" << newSize << ", 古いサイズ:" << oldSize;
          }
      };
      
      // ... (main 関数など)
      int main(int argc, char *argv[]) {
          QApplication a(argc, argv);
          ResizableWidget widget;
          SizeReporter reporter;
          QObject::connect(&widget, &ResizableWidget::resized, &reporter, &SizeReporter::reportSize);
          widget.show();
          return a.exec();
      }
      #include "main.moc" // moc による自動生成ファイル
      
      この例では、ResizableWidgetresizeEvent() 内で resized シグナルが発行され、SizeReporterreportSize スロットでサイズ情報が出力されます。

どの方法を選ぶべきか

  • 非常に特殊なレイアウト要件
    カスタムレイアウトマネージャーの作成が必要になるかもしれません。
  • サイズ変更イベントを他のオブジェクトに通知したい
    シグナルとスロットを使用します。
  • 複雑なカスタムレイアウトや描画
    QGraphicsViewQGraphicsScene の利用を検討します。
  • 一般的なウィジェットの配置
    ほとんどの場合、標準のレイアウトマネージャーで十分です。これらを優先的に検討すべきです。

resizeEvent() の直接的なオーバーライドは、これらの代替方法では実現できない、ウィジェット自身の内部状態に基づいてサイズ変更処理を行う必要がある場合に有効です。例えば、カスタムウィジェットの描画ロジックがウィジェットのサイズに強く依存する場合などです。