sizeHint()だけじゃない!Qt QAbstractScrollAreaのサイズ制御と代替手段

2025-05-27

Qt プログラミングにおける QSize QAbstractScrollArea::sizeHint() は、スクロール可能な領域を提供するウィジェットである QAbstractScrollArea クラスの推奨サイズを返す仮想関数です。

sizeHint() とは?

Qt のウィジェットは、レイアウトマネージャーによって配置される際に、そのウィジェットが「このくらいのサイズが欲しい」と推奨するサイズを sizeHint() 関数を通してレイアウトマネージャーに伝えます。レイアウトマネージャーは、この sizeHint() の値や、minimumSizeHint() (最小推奨サイズ)、maximumSize() (最大許容サイズ)、sizePolicy() (サイズポリシー) などを考慮して、最終的なウィジェットのサイズと位置を決定します。

QAbstractScrollArea::sizeHint() の役割

QAbstractScrollArea は、ビューポート(実際のコンテンツが表示される領域)と、必要に応じてスクロールバーを表示するウィジェットです。QAbstractScrollArea::sizeHint() は、このスクロールエリア全体として、どのくらいのサイズが適切かを返します。

具体的には、QAbstractScrollAreasizeHint() は、以下の要素を考慮して計算されることが一般的です。

  • マージン (Margins): setViewportMargins() などで設定されたマージンも考慮されます。
  • フレームの幅 (Frame width): QAbstractScrollAreaQFrame を継承しているため、フレームが設定されている場合はその幅も考慮されます。
  • スクロールバーのサイズ: スクロールバーが表示される場合、その幅や高さが sizeHint() に加算されます。例えば、垂直スクロールバーが表示される場合は、その幅が横方向に、水平スクロールバーが表示される場合は、その高さが縦方向に加算されます。スクロールバーの表示ポリシー(常に表示、必要に応じて表示など)によって、この加算が行われるかどうかが変わります。
  • ビューポートの推奨サイズ (Viewport's sizeHint()): コンテンツが表示されるビューポート自体の推奨サイズです。これは、ビューポートに設定されているウィジェットの sizeHint() や、コンテンツの量によって影響されます。

通常、QAbstractScrollArea を直接使うことは少なく、QScrollAreaQGraphicsViewQPlainTextEdit など、QAbstractScrollArea を継承したクラスを使うことが多いです。これらのクラスでは、コンテンツの特性に応じて sizeHint() が適切に実装されています。

しかし、もしあなたが独自のカスタムスクロール可能なウィジェットを作成し、それが QAbstractScrollArea を継承している場合、そのコンテンツの推奨サイズをレイアウトマネージャーに正しく伝えるために、sizeHint() をオーバーライドして実装する必要があるかもしれません。



sizeHint() の一般的なエラーと問題

    • 問題: コンテンツが十分に小さいのにスクロールバーが表示されたり、逆にコンテンツがはみ出しているのにスクロールバーが表示されなかったりする。
    • 原因:
      • sizeHint() がコンテンツの実際のサイズを正しく反映していない。特に、カスタムウィジェットを QScrollArea のビューポートに設定している場合、そのカスタムウィジェットの sizeHint() が適切に実装されていないと、QScrollArea が適切なサイズを判断できません。
      • setWidgetResizable(true) の設定が適切でない。QScrollArea::setWidgetResizable(true) は、スクロールエリア内のウィジェットが自身の sizeHint() に従って拡大・縮小することを許可します。これが false だと、ウィジェットが縮小できず、スクロールバーが表示されやすくなります。
      • Qt::ScrollBarPolicy (e.g., Qt::ScrollBarAlwaysOn, Qt::ScrollBarAsNeeded) の設定が意図と異なる。
      • ビューポートのマージン (setViewportMargins()) やフレームの幅が考慮されていない。
    • トラブルシューティング:
      • カスタムウィジェットの sizeHint() を確認・修正: ビューポート内に表示するカスタムウィジェットがある場合、その sizeHint() が実際に表示されるコンテンツの最小限のサイズ(または推奨サイズ)を正確に返すように実装されているかを確認します。例えば、QLabel に長いテキストを設定した場合、sizeHint() はそのテキスト全体が表示されるサイズを返します。レイアウトを使用しているウィジェットであれば、レイアウトが子ウィジェットの sizeHint() を集計して親ウィジェットの sizeHint() を計算します。
      • QScrollArea::setWidgetResizable(true) を試す: これにより、内包するウィジェットがスクロールエリアの利用可能なスペースに合わせてリサイズされるため、不要なスクロールバーの表示が減る可能性があります。
      • Qt::ScrollBarPolicy を確認: デフォルトの Qt::ScrollBarAsNeeded は、必要に応じてスクロールバーを表示・非表示にするため、ほとんどのケースで適切です。Qt::ScrollBarAlwaysOn に設定すると、常にスクロールバーが表示され、その分ビューポートの領域が狭まります。
      • フレームやマージンを考慮: QAbstractScrollArea はフレームやマージンを持つため、これらも sizeHint() の計算に含まれます。カスタムで sizeHint() をオーバーライドする場合は、これらの要素も加算するように考慮します。
  1. レイアウト内でスクロールエリアのサイズが期待通りにならない

    • 問題: QScrollArea が配置された親のレイアウト内で、スクロールエリアが小さすぎたり、大きすぎたりして、コンテンツが適切に表示されない。
    • 原因:
      • QScrollArea 自体の sizeHint() が、親のレイアウトに正しく伝わっていない。
      • QSizePolicy の設定が適切でない。QSizePolicy は、ウィジェットがレイアウト内でどのようにサイズを振る舞うかを定義します。
    • トラブルシューティング:
      • QScrollAreasizePolicy() を調整: QScrollArea::setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)QSizePolicy::Expanding など、レイアウト内でスクロールエリアがどのように振る舞うべきかを指定します。
      • 子ウィジェットの sizeHint()sizePolicy() を確認: スクロールエリア内のウィジェットが大きすぎると、スクロールエリアもそのサイズを要求し、レイアウト全体に影響を与えることがあります。逆に、小さすぎるとスクロールエリアが縮んでしまうこともあります。
      • updateGeometry() の呼び出し: コンテンツのサイズが動的に変化する場合(例えば、アイテムが追加・削除されたり、テキストの内容が変わったりする場合)、その変更をレイアウトマネージャーに通知するために、コンテンツを保持するウィジェットの updateGeometry() を呼び出す必要があります。これにより、sizeHint() が再計算され、レイアウトが更新されます。
  2. コンテンツのサイズが動的に変化したときにスクロールバーが更新されない

    • 問題: スクロールエリア内のコンテンツ(ビューポートのウィジェット)のサイズが変わっても、スクロールバーの表示範囲や状態が更新されない。
    • 原因: コンテンツのサイズ変更が QAbstractScrollArea に通知されていない。
    • トラブルシューティング:
      • updateGeometry() を呼び出す: コンテンツのサイズが変更された後(例: addItemremoveItem など)、そのコンテンツを保持するウィジェット、またはその親ウィジェット(直接 QAbstractScrollArea のビューポートに設定されているウィジェット)に対して updateGeometry() を呼び出します。これにより、ウィジェットの sizeHint() が再評価され、レイアウトシステムが更新されます。
      • 適切なレイアウトを使用する: スクロールエリア内にレイアウトされたウィジェットを配置している場合、レイアウトがコンテンツのサイズ変更を自動的に検出し、親ウィジェットの updateGeometry() をトリガーすることが多いです。しかし、手動で描画している場合など、レイアウトを使用していない場合は、明示的に updateGeometry() を呼び出す必要があります。
  3. sizeHint() をオーバーライドする際の注意点

    • 問題: QAbstractScrollArea を継承して sizeHint() をオーバーライドしたが、期待通りの動作にならない。
    • 原因:
      • 親クラスの sizeHint() の結果を考慮していない。
      • スクロールバーの幅や高さを正しく加算していない。
      • フレームやマージンを考慮していない。
    • トラブルシューティング:
      • 親クラスの sizeHint() を呼び出す: カスタムの sizeHint() を実装する際には、通常、まず QAbstractScrollArea::sizeHint() (または QScrollArea::sizeHint()) を呼び出し、その結果に独自の計算(例えば、ビューポートのコンテンツの sizeHint() やスクロールバーのサイズ)を加算していくのが良いプラクティスです。
      • スクロールバーのサイズを考慮: スクロールバーが表示される場合、その幅や高さが全体の推奨サイズに影響します。verticalScrollBar()->sizeHint().width()horizontalScrollBar()->sizeHint().height() などを使って、スクロールバーのサイズを取得し、適切に加算します。
      • フレームやマージンの考慮: frameWidth()contentsMargins() などを使用して、ウィジェットのフレームやマージンのサイズも最終的な sizeHint() に含めます。
  • Qt のソースコードを参照する: QAbstractScrollAreaQScrollAreasizeHint() の実装は、Qt のソースコードを参照することで、内部的な動作を理解する手助けになります。これにより、どのような要素が推奨サイズに影響するかを正確に把握できます。
  • 簡単な再現コードを作成する: 問題が複雑な場合、問題の発生する最小限のコードを作成し、そのコードでデバッグを行うと原因を特定しやすくなります。
  • qDebug() を使用して sizeHint() の戻り値を確認する: sizeHint() がどのような値を返しているかをコンソールに出力して確認します。期待する値と異なる場合は、計算ロジックに問題がある可能性があります。


ここでは、いくつかのプログラミング例を通じて QSize QAbstractScrollArea::sizeHint() の使い方を説明します。

例1: 基本的な QScrollArea と内包するウィジェットの sizeHint()

QScrollAreaQAbstractScrollArea を継承しており、ほとんどのケースで直接 QAbstractScrollArea をオーバーライドするよりも QScrollArea を使うことが多いです。この例では、QScrollArea に大きなカスタムウィジェットを設定し、そのカスタムウィジェットの sizeHint() がどのようにスクロールエリアに影響するかを示します。

MyCustomWidget.h (ビューポートに表示されるカスタムウィジェット)

#ifndef MYCUSTOMWIDGET_H
#define MYCUSTOMWIDGET_H

#include <QWidget>
#include <QPainter>
#include <QSize>

class MyCustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MyCustomWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        // カスタムウィジェットの最小推奨サイズを設定(非常に大きく設定)
        // このサイズがQScrollAreaのサイズHintに影響します
        setMinimumSize(2000, 1500); // 例えば、非常に大きなコンテンツを想定
    }

    // sizeHint() をオーバーライドして推奨サイズを返す
    QSize sizeHint() const override
    {
        // ここでは、コンテンツの実際のサイズを推奨サイズとして返します。
        // 例えば、描画されるコンテンツの最大サイズなど。
        // この例では、setMinimumSize() で設定したサイズをそのまま推奨サイズとして返します。
        return QSize(2000, 1500);
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.fillRect(rect(), Qt::lightGray); // 背景色
        painter.drawText(10, 20, "これは非常に大きなカスタムウィジェットです。");
        painter.drawRect(50, 50, 1900, 1400); // 広い範囲に矩形を描画
        painter.drawText(100, 100, "スクロールして全体を確認してください。");
    }
};

#endif // MYCUSTOMWIDGET_H

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QPushButton>
#include "MyCustomWidget.h" // 作成したカスタムウィジェットをインクルード

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
        // 中央ウィジェットとレイアウトの作成
        QWidget *centralWidget = new QWidget(this);
        setCentralWidget(centralWidget);
        QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

        // MyCustomWidget のインスタンスを作成
        MyCustomWidget *customWidget = new MyCustomWidget(this);

        // QScrollArea を作成し、カスタムウィジェットをセット
        QScrollArea *scrollArea = new QScrollArea(this);
        scrollArea->setWidget(customWidget); // customWidgetをスクロールエリアのビューポートとして設定
        scrollArea->setWidgetResizable(false); // これをtrueにすると、カスタムウィジェットがスクロールエリアのサイズに合わせてリサイズされ、スクロールバーが表示されない可能性があります。
                                              // ここでは、カスタムウィジェットのsizeHint()を尊重させるためfalseに設定します。

        // スクロールバーのポリシーを設定 (デフォルトはQt::ScrollBarAsNeeded)
        scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);

        mainLayout->addWidget(scrollArea);

        // レイアウト更新のためのボタン (オプション)
        QPushButton *updateButton = new QPushButton("Update Content Size (Not used in this example)", this);
        mainLayout->addWidget(updateButton);

        setWindowTitle("QScrollArea sizeHint Example");
        resize(600, 400); // メインウィンドウの初期サイズ
    }
};

#endif // MAINWINDOW_H

main.cpp

#include <QApplication>
#include "MainWindow.h"

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

説明: この例では、MyCustomWidgetsizeHint() をオーバーライドし、(2000, 1500) という大きなサイズを返しています。QScrollArea はこの sizeHint() を参照し、その内部に収まりきらないと判断した場合にスクロールバーを自動的に表示します。setWidgetResizable(false) が重要なポイントで、これにより MyCustomWidgetQScrollArea の利用可能なスペースに無理に合わせられることなく、自身の推奨サイズを保ちます。

例2: QAbstractScrollArea を直接継承し、sizeHint() をオーバーライドする

より低レベルな制御が必要な場合、QAbstractScrollArea を直接継承し、paintEvent なども自身で実装することがあります。この場合、sizeHint() のオーバーライドは必須です。

MyAbstractScrollArea.h

#ifndef MYABSTRACTSCROLLAREA_H
#define MYABSTRACTSCROLLAREA_H

#include <QAbstractScrollArea>
#include <QPainter>
#include <QScrollBar> // スクロールバーのサイズを取得するために必要

class MyAbstractScrollArea : public QAbstractScrollArea
{
    Q_OBJECT
public:
    explicit MyAbstractScrollArea(QWidget *parent = nullptr) : QAbstractScrollArea(parent)
    {
        // ビューポートの設定 (通常、QAbstractScrollAreaを使う場合は自分で描画するか、シンプルなQWidgetをビューポートにする)
        // ここでは、ビューポートに直接描画する例として、viewport() をそのまま利用します。
    }

    // QAbstractScrollArea::sizeHint() をオーバーライド
    QSize sizeHint() const override
    {
        // 1. 親クラスのデフォルトのsizeHint() を取得
        // これは、フレームやマージンなど、QAbstractScrollArea自体が持つ推奨サイズを含みます。
        QSize baseSize = QAbstractScrollArea::sizeHint();

        // 2. コンテンツの理想的なサイズ(ビューポートで表示したいサイズ)を計算
        // ここでは仮に固定値を設定しますが、実際のアプリケーションでは動的に計算されるべきです。
        QSize contentSize(800, 600); // 描画したいコンテンツの論理的なサイズ

        // 3. コンテンツサイズとビューポートサイズを比較し、スクロールバーが表示される可能性を考慮して最終的な推奨サイズを決定
        // ここで重要なのは、スクロールバーが表示される場合に、そのスペースを確保することです。
        // Qt::ScrollBarAsNeeded の場合、スクロールバーが表示されるかどうかは実行時に決まるため、
        // sizeHint() では常にスクロールバーのスペースを加算するのが安全なアプローチです。
        // QStyle::pixelMetric() を使うと、現在のスタイルに応じたスクロールバーの幅/高さを取得できます。
        int scrollBarWidth = verticalScrollBar()->sizeHint().width(); // 垂直スクロールバーの幅
        int scrollBarHeight = horizontalScrollBar()->sizeHint().height(); // 水平スクロールバーの高さ

        // 推奨されるビューポートサイズ(スクロールバーのスペースを除く)
        QSize desiredViewportSize = contentSize;

        // もしコンテンツが現在のビューポートより大きい場合、スクロールバーのスペースを考慮
        // これは非常に単純なロジックです。実際はQt::ScrollBarAsNeededの振る舞いを模倣するのは難しいです。
        // 一般的には、コンテンツ全体を表示するために必要な最小サイズを返します。
        // もしコンテンツが viewport のサイズより大きい場合に、スクロールバーが表示されると仮定して、
        // その分だけサイズヒントを大きくします。
        // ここでは、常にスクロールバーのサイズを加算することで、十分なスペースを確保します。
        baseSize.setWidth(qMax(baseSize.width(), contentSize.width() + scrollBarWidth));
        baseSize.setHeight(qMax(baseSize.height(), contentSize.height() + scrollBarHeight));

        return baseSize;
    }

protected:
    // QAbstractScrollArea::viewportEvent() をオーバーライドしてビューポートの描画を処理
    bool viewportEvent(QEvent *event) override
    {
        if (event->type() == QEvent::Paint) {
            paintViewport(static_cast<QPaintEvent*>(event));
            return true;
        }
        // 他のイベントは親クラスに任せる
        return QAbstractScrollArea::viewportEvent(event);
    }

    // ビューポートの描画処理
    void paintViewport(QPaintEvent *event)
    {
        Q_UNUSED(event);
        QPainter painter(viewport()); // viewport() は QWidget* を返す
        painter.fillRect(viewport()->rect(), Qt::white); // ビューポートの背景色

        // スクロール位置に基づいてコンテンツを描画
        int xOffset = -horizontalScrollBar()->value();
        int yOffset = -verticalScrollBar()->value();

        // 例として、非常に大きな矩形とテキストを描画
        painter.setBrush(Qt::blue);
        painter.drawRect(xOffset + 50, yOffset + 50, 700, 500); // 論理的なコンテンツの一部
        painter.setPen(Qt::black);
        painter.setFont(QFont("Arial", 20));
        painter.drawText(xOffset + 100, yOffset + 100, "これはカスタムスクロールエリアのコンテンツです。");
        painter.drawText(xOffset + 150, yOffset + 150, "スクロールしてください。");

        // さらに離れた位置に何かを描画し、スクロールが必要であることを示す
        painter.drawText(xOffset + 700, yOffset + 550, "右下にもコンテンツがあります。");
    }

    // スクロールバーの値が変更されたときに呼び出される仮想関数
    void scrollContentsBy(int dx, int dy) override
    {
        // ビューポートのコンテンツを移動
        // QAbstractScrollArea::viewport() が QWidget の場合、QWidget::scroll() を使うと効率的
        viewport()->scroll(dx, dy);

        // 必要に応じてビューポートを更新 (paintEventが再発行される)
        viewport()->update();
    }

    // ビューポートがリサイズされたときに呼び出される仮想関数
    void resizeEvent(QResizeEvent *event) override
    {
        Q_UNUSED(event);
        // ビューポートのサイズが変更されたら、スクロールバーの範囲を更新する必要がある
        // コンテンツの総サイズ
        QSize contentSize(800, 600); // この例では固定値だが、動的に計算されるべき

        // 垂直スクロールバーの範囲を設定
        int vMax = qMax(0, contentSize.height() - viewport()->height());
        verticalScrollBar()->setRange(0, vMax);
        verticalScrollBar()->setPageStep(viewport()->height());

        // 水平スクロールバーの範囲を設定
        int hMax = qMax(0, contentSize.width() - viewport()->width());
        horizontalScrollBar()->setRange(0, hMax);
        horizontalScrollBar()->setPageStep(viewport()->width());

        // 親クラスのresizeEventも呼び出す
        QAbstractScrollArea::resizeEvent(event);
    }
};

#endif // MYABSTRACTSCROLLAREA_H

MainWindow.h (上記MyAbstractScrollArea を使用する例)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QVBoxLayout>
#include "MyAbstractScrollArea.h" // 作成したカスタムスクロールエリアをインクルード

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
        QWidget *centralWidget = new QWidget(this);
        setCentralWidget(centralWidget);
        QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

        // MyAbstractScrollArea のインスタンスを作成
        MyAbstractScrollArea *customScrollArea = new MyAbstractScrollArea(this);

        mainLayout->addWidget(customScrollArea);

        setWindowTitle("QAbstractScrollArea sizeHint Example (Custom)");
        resize(400, 300); // メインウィンドウの初期サイズ
    }
};

#endif // MAINWINDOW_H

main.cpp (上記MyAbstractScrollArea を使用する例)

#include <QApplication>
#include "MainWindow.h"

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

説明: この例では、MyAbstractScrollArea を直接継承しています。

  • resizeEvent(): QAbstractScrollArea のサイズが変更されたときに、スクロールバーの範囲(setRange())とページステップ(setPageStep())を更新する必要があります。これは、コンテンツのサイズとビューポートの現在のサイズに基づいて行われます。
  • scrollContentsBy(): スクロールバーが動かされたときに、コンテンツを移動させるための仮想関数です。viewport()->scroll(dx, dy) を呼び出すことで、ビューポートの描画を効率的に移動させることができます。
  • viewportEvent()paintViewport(): QAbstractScrollArea はビューポート内の描画を自身で管理するため、viewportEvent() をオーバーライドし、その中で QEvent::Paint を処理する paintViewport() を呼び出します。描画は viewport() に対して行われます。
  • sizeHint(): コンテンツの推奨サイズ (800x600) と、スクロールバーの幅/高さ を考慮して、最終的な推奨サイズを返します。これにより、レイアウトマネージャーがこのスクロールエリアに十分なスペースを割り当てようとします。
  1. コンテンツの実際のサイズを正確に返す: sizeHint() は、そのウィジェットが自身のコンテンツを表示するために必要な最小限の、または推奨されるサイズを返すべきです。特に、動的にコンテンツが変化する場合(例: テキストの追加、画像の読み込み、リストアイテムの増減など)は、それに合わせて sizeHint() も動的に計算されるようにします。
  2. レイアウトマネージャーとの連携: Qt のレイアウトマネージャーは、子ウィジェットの sizeHint()minimumSizeHint()maximumSize()sizePolicy() などを総合的に判断して、ウィジェットの最終的なサイズと位置を決定します。sizeHint() をオーバーライドする際は、これらの他のプロパティとの兼ね合いも考慮する必要があります。
  3. updateGeometry() の呼び出し: sizeHint() が返す値が動的に変化する場合(例: コンテンツが増減した場合)、その変更を親ウィジェット(またはレイアウト)に通知するために updateGeometry() を呼び出す必要があります。これにより、レイアウトマネージャーが sizeHint() を再評価し、レイアウトを更新します。
  4. スクロールバーのスペース: QAbstractScrollAreaQScrollAreasizeHint() を計算する際、もしスクロールバーが表示される可能性があるなら、そのスクロールバーが占める幅や高さも最終的な推奨サイズに含めるべきです。これは、QScrollBar::sizeHint() を使って取得できます。


ここでは、sizeHint() を直接オーバーライドする代わりの方法をいくつか説明します。

QWidget::setMinimumSize() と QWidget::setMaximumSize() の利用

これは最も単純で直接的な方法の一つです。ウィジェットが特定の最小サイズまたは最大サイズを持つことを保証したい場合に有効です。

  • setFixedSize(const QSize &size): 最小サイズと最大サイズを同じ値に設定し、ウィジェットのサイズを固定します。
  • setMaximumSize(const QSize &size): ウィジェットの最大サイズを設定します。レイアウトマネージャーは、ウィジェットをこれより大きくすることはありません。
  • setMinimumSize(const QSize &size): ウィジェットの最小サイズを設定します。レイアウトマネージャーは、ウィジェットをこれより小さくすることはありません。

いつ使うか?

  • sizeHint() が適切な値を返さないが、固定された最小/最大サイズで対処できる場合。
  • QScrollArea のビューポートウィジェットが、コンテンツの最小限の表示領域を保証したい場合。
  • ウィジェットが常に特定のサイズであるべき場合(例: アイコンボタン、固定サイズの画像ビューア)。


// MyCustomWidget.h (QScrollAreaのビューポートとして使うウィジェット)
class MyCustomWidget : public QWidget
{
    // ...
public:
    explicit MyCustomWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        // このウィジェットの最小推奨サイズを設定
        // sizeHint() をオーバーライドしなくても、この設定がレイアウトに影響します
        setMinimumSize(800, 600); // 少なくともこのサイズで表示してほしい
    }
    // ...
};

// MainWindow.cpp
QScrollArea *scrollArea = new QScrollArea();
MyCustomWidget *myWidget = new MyCustomWidget();
scrollArea->setWidget(myWidget);
scrollArea->setWidgetResizable(false); // setMinimumSize/FixedSize を尊重させるため

この場合、MyCustomWidgetsizeHint() が何であろうと、setMinimumSize() で設定したサイズが優先され、QScrollArea はそのサイズに合わせてスクロールバーを表示します。

QWidget::setSizePolicy() の利用

QSizePolicy は、ウィジェットがレイアウト内で利用可能なスペースに対してどのように振る舞うかを定義します。これは sizeHint() と密接に関連していますが、sizeHint() が「望ましいサイズ」を伝えるのに対し、setSizePolicy() は「望ましい振る舞い」(拡大/縮小の可否、優先度)を伝えます。

QSizePolicy は、水平方向と垂直方向でそれぞれ QSizePolicy::PolicyQSizePolicy::RetainSizeHintQSizePolicy::GrowFlag などのフラグを持ちます。

  • QSizePolicy::Ignored: レイアウトマネージャーがこのウィジェットの sizeHint() を無視し、利用可能なすべてのスペースを占有しようとします。
  • QSizePolicy::Minimum: sizeHint() を最小サイズと見なし、それより小さくならないようにします。sizeHint() より大きくはなります。
  • QSizePolicy::Expanding: sizeHint() を推奨サイズとしますが、利用可能なスペースがあれば、それに応じて拡大しようとします。これは Preferred よりも拡大の傾向が強いです。
  • QSizePolicy::Preferred: sizeHint() を推奨サイズとしますが、必要に応じて拡大・縮小します。
  • QSizePolicy::Fixed: sizeHint() と同じサイズに固定されます。

いつ使うか?

  • QScrollArea 内のウィジェットが、スクロールエリアのサイズに合わせて拡大・縮小すべきか、それとも自身の推奨サイズを保持すべきかを制御したい場合。
  • QScrollArea が、ビューポートのコンテンツのサイズに基づいて自身のサイズを調整すべきだが、特定の方向では伸縮を許可したい場合。
  • ウィジェットがレイアウト内でどのようにサイズを調整すべきかを指定したい場合。


// MainWindow.cpp (QScrollAreaのサイズポリシーを調整)
QScrollArea *scrollArea = new QScrollArea();
// ... (ビューポートのウィジェットを設定)

// QScrollAreaが水平方向には拡大せず、垂直方向には拡大するように設定
scrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);

// もしくは、ビューポート内のウィジェットのサイズポリシーを調整
myWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // ビューポート内で最大に広がる

QScrollArea::setWidgetResizable(true) は、ビューポートウィジェットの sizePolicy()QSizePolicy::Ignored でない限り、その sizeHint() を尊重しつつ、スクロールエリアの利用可能なスペースに合わせてビューポートウィジェットをリサイズします。

レイアウトを利用してコンテンツのサイズを管理する

QScrollArea のビューポート内に直接カスタムウィジェットを配置する代わりに、QLayout を配置し、その中にコンテンツを配置することがよくあります。QLayout は、その子ウィジェットの sizeHint() を集計し、自身の sizeHint() を計算します。これにより、スクロールエリアはレイアウトの推奨サイズに基づいてスクロールバーを適切に表示します。

いつ使うか?

  • 複雑なレイアウトをスクロール可能にしたい場合。
  • コンテンツのサイズが、その中の複数の要素によって決定される場合。
  • 複数のウィジェットをスクロール可能な領域に配置したい場合。


// MainWindow.cpp
#include <QScrollArea>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>

// ...

QScrollArea *scrollArea = new QScrollArea(this);

// スクロールエリアのビューポートとしてQScrollArea内部でQWidgetを作成
QWidget *scrollContent = new QWidget(scrollArea);
QVBoxLayout *contentLayout = new QVBoxLayout(scrollContent);

// たくさんのコンテンツを追加
for (int i = 0; i < 20; ++i) {
    contentLayout->addWidget(new QLabel(QString("アイテム %1").arg(i), scrollContent));
}
contentLayout->addWidget(new QPushButton("ボタン", scrollContent));

// レイアウトされたウィジェットをスクロールエリアのビューポートに設定
scrollArea->setWidget(scrollContent);
scrollArea->setWidgetResizable(true); // コンテンツがスクロールエリア内でリサイズ可能になる (重要!)

// scrollContent のレイアウトが子ウィジェットの sizeHint を集計し、
// scrollContent 自体の sizeHint() を計算します。
// QScrollArea はその sizeHint() を参照します。

この方法では、scrollContent ウィジェットの sizeHint() は、その内部の contentLayout に含まれるすべてのウィジェットの sizeHint() の合計に基づいて自動的に計算されます。QScrollArea::setWidgetResizable(true) と組み合わせることで、ほとんどのケースで適切にスクロールバーが表示されるようになります。

sizeHint() そのものをオーバーライドするわけではありませんが、sizeHint() が返す値が動的に変化する可能性がある場合に、その変更をレイアウトマネージャーに通知するために使用します。

  • updateGeometry(): 親ウィジェットの sizeHint() が変更された可能性があることをレイアウトマネージャーに通知します。これにより、レイアウトマネージャーは sizeHint() を再評価し、必要に応じてレイアウトを再計算します。

いつ使うか?

  • QAbstractScrollArea のビューポート内で表示するコンテンツのサイズが変更された場合。
  • 例えば、テキストの追加/削除、画像の読み込み、項目の表示/非表示など。
  • カスタムウィジェットのコンテンツが動的に変化し、その結果として理想的なサイズ (sizeHint()) も変化する場合。


// MyCustomWidget.h (コンテンツが動的に変化するカスタムウィジェット)
class MyCustomWidget : public QWidget
{
    Q_OBJECT
public:
    // ...
    void addText(const QString &text) {
        m_texts << text;
        // テキストが追加されたので、推奨サイズが変わる可能性がある
        updateGeometry(); // 親(QScrollAreaなど)にサイズ変更を通知
    }

    QSize sizeHint() const override {
        // m_texts の内容に基づいて推奨サイズを計算するロジック
        // 例: QFontMetrics を使ってテキストの高さと幅を計算
        QFontMetrics fm(font());
        int totalHeight = 0;
        int maxWidth = 0;
        for (const QString &text : m_texts) {
            totalHeight += fm.height();
            maxWidth = qMax(maxWidth, fm.horizontalAdvance(text));
        }
        return QSize(maxWidth + 20, totalHeight + 20); // マージンなどを考慮
    }
    // ...
private:
    QStringList m_texts;
};

// MainWindow.cpp
MyCustomWidget *myWidget = new MyCustomWidget();
QScrollArea *scrollArea = new QScrollArea();
scrollArea->setWidget(myWidget);
scrollArea->setWidgetResizable(true); // リサイズ可能にしておく

// ボタンクリックなどでコンテンツを追加し、updateGeometry()が呼ばれるようにする
QPushButton *addButton = new QPushButton("テキスト追加");
connect(addButton, &QPushButton::clicked, [myWidget]() {
    myWidget->addText("新しいテキスト行が追加されました。");
});

QSize QAbstractScrollArea::sizeHint() を直接オーバーライドすることは、最も柔軟な方法ですが、必ずしも常に必要ではありません。多くの場合、以下の代替手段で十分な制御が可能です。

  • 動的なサイズ変更の通知: updateGeometry()
  • 複雑なコンテンツの自動サイズ計算: レイアウト (QVBoxLayout, QHBoxLayout など) を活用する
  • レイアウト内での振る舞いの指定: setSizePolicy()
  • 簡単な固定サイズや最小/最大サイズ: setMinimumSize(), setMaximumSize(), setFixedSize()