QtのQAbstractScrollArea::setViewport()徹底解説

2025-05-27

QAbstractScrollArea::setViewport() とは

QAbstractScrollArea は、スクロールバーを持つウィジェットの基本的な機能を提供する抽象クラスです。このクラスは、表示する内容がウィジェットのサイズよりも大きい場合に、その内容をスクロールして表示するためのメカニズムを提供します。

setViewport() メソッドは、この QAbstractScrollArea の中に、実際にスクロールされるコンテンツが表示される領域(ビューポート)として、任意の QWidget を設定するために使用されます。

主要なポイント

  • 用途
    • QAbstractScrollArea を直接継承して、独自のスクロール領域ウィジェットを作成する場合に非常に重要です。
    • 例えば、カスタムの描画ロジックを持つウィジェットや、特定のデータ構造(例:大きな画像、カスタムグラフなど)をスクロールして表示したい場合などに利用されます。
    • Qtの標準的な QScrollAreaQAbstractScrollArea を継承しており、内部でこの setViewport() を利用して、任意の QWidget をスクロール可能にしています。
  • 所有権の取得
    setViewport() に渡されたウィジェットの所有権は QAbstractScrollArea が取得します。これは、QAbstractScrollArea が破棄される際に、設定されたビューポートウィジェットも自動的に破棄されることを意味します。自分で delete する必要はありません。
  • ビューポート (Viewport)
    QAbstractScrollArea の中で、実際にコンテンツが描画され、スクロールによって表示される部分のことです。ユーザーはこのビューポートを通して、広いコンテンツの一部を見ていることになります。

QAbstractScrollArea を継承する場合の注意点

QAbstractScrollArea を継承してカスタムのスクロール領域を作成する場合、setViewport() を使用するだけでなく、いくつかの追加の処理が必要になります。

  1. スクロールバーの制御
    スクロールバーの範囲(setRange())、現在値(setValue())、ページステップ(setPageStep())などを設定し、ユーザーがスクロールバーを操作した際の動きを追跡する必要があります。これには、scrollContentsBy() 仮想関数を再実装することがよくあります。
  2. コンテンツの描画
    ビューポート内でコンテンツを正しく描画する必要があります。これは通常、ビューポートの paintEvent() ハンドラを処理することで行われます。QAbstractScrollArea は、ビューポートで発生するイベント(paintEvent(), mousePressEvent() など)を viewportEvent() 仮想関数にリマップするため、カスタムイベント処理が必要な場合はこれを再実装できます。
  3. ビューポートの更新
    コンテンツのサイズが変わったり、ビューポートのサイズが変わったり、スクロールバーの値が変わったりしたときに、ビューポートを適切に更新する必要があります。これには viewport()->update() を使用します。

例えば、非常に大きな画像をスクロールして表示するカスタムウィジェットを作成するとします。

#include <QAbstractScrollArea>
#include <QPainter>
#include <QImage>

class CustomImageViewer : public QAbstractScrollArea
{
public:
    CustomImageViewer(QWidget *parent = nullptr)
        : QAbstractScrollArea(parent)
    {
        // ビューポートとして標準の QWidget を設定
        setViewport(new QWidget(this));

        // スクロールバーのポリシーを設定
        setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    }

    void setImage(const QImage &image) {
        m_image = image;
        // 画像が変更されたらスクロールバーの範囲を更新
        horizontalScrollBar()->setRange(0, m_image.width() - viewport()->width());
        verticalScrollBar()->setRange(0, m_image.height() - viewport()->height());
        // ビューポートを再描画
        viewport()->update();
    }

protected:
    // スクロールバーの移動に応じてコンテンツをスクロール
    void scrollContentsBy(int dx, int dy) override {
        // ビューポートを移動する代わりに、描画オフセットを更新する
        // (QScrollAreaとは異なり、QAbstractScrollAreaは自動的にコンテンツを移動しない)
        m_offsetX += dx;
        m_offsetY += dy;
        viewport()->update(); // ビューポートを再描画して、新しいオフセットで描画させる
    }

    // ビューポートのイベント処理(主に描画イベントを扱う)
    bool viewportEvent(QEvent *event) override {
        if (event->type() == QEvent::Paint) {
            QPaintEvent *paintEvent = static_cast<QPaintEvent*>(event);
            QPainter painter(viewport());
            // 現在のスクロールオフセットを考慮して画像を描画
            painter.drawImage(paintEvent->rect(), m_image, paintEvent->rect().translated(-m_offsetX, -m_offsetY));
            return true;
        }
        return QAbstractScrollArea::viewportEvent(event);
    }

    // ビューポートのサイズ変更時にスクロールバーの範囲を更新
    void resizeEvent(QResizeEvent *event) override {
        QAbstractScrollArea::resizeEvent(event);
        horizontalScrollBar()->setRange(0, m_image.width() - viewport()->width());
        verticalScrollBar()->setRange(0, m_image.height() - viewport()->height());
    }

private:
    QImage m_image;
    int m_offsetX = 0;
    int m_offsetY = 0;
};

// 使用例
// CustomImageViewer *viewer = new CustomImageViewer();
// viewer->setImage(QImage("path/to/your/image.png"));
// viewer->show();

この例では、CustomImageViewerQAbstractScrollArea を継承し、setViewport()QWidget をビューポートとして設定しています。そして、scrollContentsBy()viewportEvent() を再実装して、画像を指定されたオフセットで描画しています。



QAbstractScrollArea を直接使用する場合、QScrollArea とは異なり、開発者が多くのロジックを自分で実装する必要があります。これがエラーの主な原因となることが多いです。

ビューポートに何も表示されない / paintEvent が呼ばれない

  • トラブルシューティング
    • ビューポートとして設定したウィジェット(viewport() メソッドで取得できる)が、実際に paintEvent() を処理しているか確認してください。
    • paintEvent() 内で QPainter を使用して描画を行っていることを確認してください。
    • コンテンツの変更時やスクロールバーの値が変更されたときに、必ず viewport()->update() を呼び出して再描画を要求してください。QAbstractScrollArea クラスの update() ではなく、ビューポートの update() であることに注意してください。
  • 原因
    • QAbstractScrollArea は、それ自体では何も描画しません。setViewport() で設定したウィジェットがコンテンツの描画を担当します。
    • そのビューポートウィジェットの paintEvent() を適切に再実装していない、または update() (もしくは viewport()->update()) を呼び出して再描画をトリガーしていない。
    • ビューポートウィジェットのサイズが正しく設定されていない(例: setSizePolicysetMinimumSize が不足している)。

スクロールバーが表示されない / スクロールが機能しない

  • トラブルシューティング
    • スクロールバーの範囲設定
      コンテンツの全体サイズ(例:画像の幅と高さ)に基づいて、スクロールバーの setRange(min, max) を設定してください。
      • 例: horizontalScrollBar()->setRange(0, contentWidth - viewport()->width());
      • 例: verticalScrollBar()->setRange(0, contentHeight - viewport()->height());
    • scrollContentsBy() の実装
      このメソッドは、スクロールバーが動いたときに呼ばれます。この中で、ビューポート内の描画オフセットを更新し、viewport()->update() を呼び出して再描画をトリガーする必要があります。QScrollArea のように自動的に子ウィジェットを移動してくれるわけではありません。
    • resizeEvent() の考慮
      QAbstractScrollArea またはビューポートウィジェットのサイズが変更されたときに、スクロールバーの範囲を再計算し、viewport()->update() を呼び出す必要があります。
  • 原因
    • QAbstractScrollArea のスクロールバー(horizontalScrollBar()verticalScrollBar() で取得)の範囲 (range) が設定されていない。範囲が0-0の場合、スクロールする意味がないためスクロールバーは表示されません(Qt::ScrollBarAsNeeded ポリシーの場合)。
    • ビューポートのサイズとコンテンツのサイズの関係が正しく計算されていない。
    • スクロールバーの値が変更されたときに、コンテンツの位置を更新するロジックが不足している。
    • scrollContentsBy(int dx, int dy) 仮想関数を再実装していない、またはその中でビューポートのコンテンツを適切に移動する処理を行っていない。

コンテンツが正しくスクロールされない / スクロールがカクカクする

  • トラブルシューティング
    • scrollContentsBy(int dx, int dy) で受け取る dxdy は、スクロールされるべき「ピクセル量」です。これらを累積して現在の描画オフセット (m_offsetX, m_offsetY など) を更新してください。
    • paintEvent() では、QPaintertranslate() メソッドを使用するか、描画するオブジェクトの座標にオフセット値を加減することで、コンテンツを正しい位置に描画してください。
      // paintEvent内で
      QPainter painter(viewport());
      // オフセットを適用
      painter.translate(-m_offsetX, -m_offsetY);
      // ここでコンテンツを描画
      painter.drawSomeContent(0, 0); // コンテンツは(0,0)から描画されると仮定
      
    • viewport()->update() を呼び出す際に、update(QRect rect) のように再描画が必要な領域だけを指定することで、描画パフォーマンスを改善できる場合があります。
  • 原因
    • scrollContentsBy() での描画オフセットの計算が間違っている。
    • paintEvent() 内での描画時に、スクロールオフセットが正しく適用されていない。
    • 再描画の頻度が低い、または不必要な部分まで再描画している。

イベント処理(クリック、ドラッグなど)がビューポートで機能しない

  • トラブルシューティング
    • ビューポートウィジェットでカスタムイベント処理を行う場合、QAbstractScrollArea を継承したクラスで viewportEvent(QEvent *event) をオーバーライドし、イベントの種類をチェックして適切な処理を行ってください。
    • もし、標準的なウィジェットのイベントハンドラ(例: mousePressEvent)をビューポートウィジェット側でオーバーライドしている場合、イベントが viewportEvent() に到達しない可能性があります。QAbstractScrollArea のドキュメントで、どのイベントが viewportEvent() にリマップされるかを確認してください。通常は、ビューポートウィジェットでのイベント処理が推奨されますが、QAbstractScrollArea 側で集中管理したい場合は viewportEvent() を利用します。
  • 原因
    • ビューポートウィジェットで直接イベントを処理しているが、QAbstractScrollAreaviewportEvent() 仮想関数を適切に再実装していない。QAbstractScrollArea は、ビューポートに送信される多くのイベント(mousePressEvent など)を自身の viewportEvent() にリマップします。

QScrollArea と QAbstractScrollArea の混同

  • 原因
    • QScrollArea は、内部で QAbstractScrollArea を継承しており、その名の通り「任意の単一ウィジェット」をスクロール可能にするための便利なラッパーです。QScrollArea::setWidget() を使用すると、与えられたウィジェットがビューポートの子として自動的に配置され、スクロールロジック(描画オフセットの計算、スクロールバーの更新など)をQtが自動で行ってくれます。
    • 一方、QAbstractScrollArea はより低レベルで、スクロールエリアとしての枠組みだけを提供し、具体的な描画やスクロールのロジックは開発者が完全に実装する必要があります。

メモリリーク (稀だが注意)

  • トラブルシューティング
    • setViewport() は一度だけ呼び出すのが基本です。ビューポートを交換したい場合は、新しいウィジェットを渡す前に、既存のビューポートの親を解除したり(setParent(nullptr))、明示的に削除したりする必要があるかもしれません。しかし、Qtの所有権システムが正しく機能していれば、通常は自動的に処理されます。
    • QAbstractScrollArea のデストラクタが呼ばれると、設定されたビューポートも自動的に破棄されます。手動で delete viewport(); のようなコードを書かないでください。
  • 原因
    setViewport() は渡されたウィジェットの所有権を取得しますが、誤って同じウィジェットを複数回 setViewport() に渡したり、手動で delete してしまったりすると、予期せぬ動作やクラッシュの原因になることがあります。


ここでは、非常にシンプルなカスタムスクロールエリアとして、大きな四角形を描画し、それをスクロールさせる例を考えます。

例1: シンプルなカスタムスクロールエリア

この例では、QAbstractScrollArea を継承した CustomScrollArea クラスを作成し、その中にカスタムの描画ロジックを持つビューポートを設定します。

ヘッダファイル (customscrollarea.h)

#ifndef CUSTOMSCROLLAREA_H
#define CUSTOMSCROLLAREA_H

#include <QAbstractScrollArea>
#include <QScrollBar> // スクロールバーの操作のために必要
#include <QPainter>   // 描画のために必要
#include <QPaintEvent> // 描画イベントのために必要
#include <QDebug>     // デバッグ出力のために必要

// ビューポートとして使用するカスタムウィジェット
class CustomViewportWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomViewportWidget(QWidget *parent = nullptr);

    // 描画するコンテンツのサイズを設定するメソッド
    void setContentSize(const QSize &size);

    // 現在のスクロールオフセットを設定するメソッド
    void setScrollOffset(int x, int y);

protected:
    // 描画イベントハンドラ
    void paintEvent(QPaintEvent *event) override;

private:
    QSize m_contentSize; // 描画するコンテンツの論理的なサイズ
    int m_offsetX;       // 現在のスクロールオフセットX
    int m_offsetY;       // 現在のスクロールオフセットY
};

// QAbstractScrollAreaを継承したカスタムスクロールエリア
class CustomScrollArea : public QAbstractScrollArea
{
    Q_OBJECT
public:
    explicit CustomScrollArea(QWidget *parent = nullptr);

    // コンテンツのサイズを設定するメソッド (親クラスからの呼び出し用)
    void setContentSize(const QSize &size);

protected:
    // スクロールバーの移動に応じてコンテンツをスクロール
    // QAbstractScrollAreaの純粋仮想関数ではないが、重要なオーバーライド
    void scrollContentsBy(int dx, int dy) override;

    // サイズ変更イベントハンドラ
    void resizeEvent(QResizeEvent *event) override;

private:
    QSize m_contentSize; // 描画するコンテンツの論理的なサイズ
    int m_currentOffsetX; // 現在のスクロールオフセットX
    int m_currentOffsetY; // 現在のスクロールオフセットY
};

#endif // CUSTOMSCROLLAREA_H

ソースファイル (customscrollarea.cpp)

#include "customscrollarea.h"

// CustomViewportWidget の実装
CustomViewportWidget::CustomViewportWidget(QWidget *parent)
    : QWidget(parent), m_contentSize(1000, 800), m_offsetX(0), m_offsetY(0)
{
    // ビューポートの背景色を設定(見やすくするため)
    // setAutoFillBackground(true);
    // QPalette palette = this->palette();
    // palette.setColor(QPalette::Window, Qt::white);
    // setPalette(palette);
}

void CustomViewportWidget::setContentSize(const QSize &size)
{
    m_contentSize = size;
    // 必要であれば、ここでビューポート自体の推奨サイズなどを更新できますが、
    // QAbstractScrollAreaがビューポートのサイズを管理するため、通常は不要です。
}

void CustomViewportWidget::setScrollOffset(int x, int y)
{
    m_offsetX = x;
    m_offsetY = y;
    // 描画オフセットが変更されたので、再描画を要求
    update(); // QWidget::update() を呼び出す
}

void CustomViewportWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event); // イベントパラメータを使用しない場合、警告を抑制

    QPainter painter(this);
    // アンチエイリアシングを有効にする(描画を滑らかにする)
    painter.setRenderHint(QPainter::Antialiasing);

    // デバッグ出力:描画イベントの発生とオフセット
    // qDebug() << "Viewport paintEvent: offset(" << m_offsetX << "," << m_offsetY << ")";

    // 現在のスクロールオフセットを適用
    // QPainter::translate() は描画原点を移動させるので、
    // これ以降の描画はオフセットされた位置から行われる
    painter.translate(-m_offsetX, -m_offsetY);

    // --- ここに描画コンテンツを追加 ---

    // 背景色でコンテンツ全体を塗りつぶす(オプション)
    painter.fillRect(0, 0, m_contentSize.width(), m_contentSize.height(), Qt::lightGray);

    // 例として、大きな長方形を描画
    painter.setPen(QPen(Qt::darkBlue, 2));
    painter.setBrush(QBrush(Qt::cyan));
    painter.drawRect(50, 50, m_contentSize.width() - 100, m_contentSize.height() - 100);

    // 描画範囲を示す赤い枠
    painter.setPen(QPen(Qt::red, 3));
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(0, 0, m_contentSize.width() - 1, m_contentSize.height() - 1);

    // 中央にテキストを描画
    painter.setFont(QFont("Arial", 50, QFont::Bold));
    painter.setPen(Qt::black);
    painter.drawText(QRect(0, 0, m_contentSize.width(), m_contentSize.height()),
                     Qt::AlignCenter, "Custom Scroll Area Content");

    // --- 描画コンテンツここまで ---
}

// CustomScrollArea の実装
CustomScrollArea::CustomScrollArea(QWidget *parent)
    : QAbstractScrollArea(parent),
      m_contentSize(1000, 800), // デフォルトのコンテンツサイズ
      m_currentOffsetX(0),
      m_currentOffsetY(0)
{
    // ★★★ ここが QAbstractScrollArea::setViewport() の最も重要な部分 ★★★
    // コンテンツを表示するウィジェットをビューポートとして設定
    // setViewport() は渡されたウィジェットの所有権を取得する
    setViewport(new CustomViewportWidget(this)); // 'this' を親として渡す

    // ビューポートウィジェットにコンテンツサイズを設定
    static_cast<CustomViewportWidget*>(viewport())->setContentSize(m_contentSize);

    // スクロールバーのポリシーを設定
    setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 必要なら水平スクロールバーを表示
    setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);   // 必要なら垂直スクロールバーを表示

    // 初期状態でスクロールバーの範囲を更新
    updateScrollBars();
}

void CustomScrollArea::setContentSize(const QSize &size)
{
    m_contentSize = size;
    static_cast<CustomViewportWidget*>(viewport())->setContentSize(m_contentSize);
    updateScrollBars(); // コンテンツサイズが変わったらスクロールバーを更新
    viewport()->update(); // ビューポートを再描画
}

// スクロールバーの移動に応じてコンテンツをスクロールさせる
// QAbstractScrollAreaが提供するイベントハンドラのようなもの
void CustomScrollArea::scrollContentsBy(int dx, int dy)
{
    // デバッグ出力:スクロール量
    // qDebug() << "scrollContentsBy: dx =" << dx << ", dy =" << dy;

    // 現在のオフセットを更新
    m_currentOffsetX += dx;
    m_currentOffsetY += dy;

    // ビューポートウィジェットに新しいオフセットを通知
    static_cast<CustomViewportWidget*>(viewport())->setScrollOffset(m_currentOffsetX, m_currentOffsetY);

    // この関数内で viewport()->update() を呼び出す必要は通常ありません。
    // setScrollOffset() がすでに update() を呼び出しているためです。
    // Qtの内部で描画領域の更新が適切に行われます。
}

// ウィジェットのサイズが変更されたときにスクロールバーの範囲を更新
void CustomScrollArea::resizeEvent(QResizeEvent *event)
{
    QAbstractScrollArea::resizeEvent(event); // 基底クラスの処理を呼び出す
    updateScrollBars(); // サイズが変わったらスクロールバーを更新
}

// スクロールバーの範囲を更新するプライベートヘルパー関数
void CustomScrollArea::updateScrollBars()
{
    // 水平スクロールバーの範囲を設定
    int hRange = m_contentSize.width() - viewport()->width();
    horizontalScrollBar()->setRange(0, qMax(0, hRange)); // 範囲は0以上であるべき
    horizontalScrollBar()->setPageStep(viewport()->width()); // ページステップはビューポートの幅に設定

    // 垂直スクロールバーの範囲を設定
    int vRange = m_contentSize.height() - viewport()->height();
    verticalScrollBar()->setRange(0, qMax(0, vRange)); // 範囲は0以上であるべき
    verticalScrollBar()->setPageStep(viewport()->height()); // ページステップはビューポートの高さに設定

    // スクロールバーの現在値を更新
    // ここで現在値が範囲外になっていないかチェックし、調整することも重要です
    horizontalScrollBar()->setValue(qMax(0, qMin(m_currentOffsetX, hRange)));
    verticalScrollBar()->setValue(qMax(0, qMin(m_currentOffsetY, vRange)));
}

main.cpp (アプリケーションのエントリポイント)

#include <QApplication>
#include <QMainWindow>
#include "customscrollarea.h"

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

    QMainWindow window;
    window.setWindowTitle("QAbstractScrollArea Example");
    window.resize(600, 400); // ウィンドウの初期サイズ

    CustomScrollArea *scrollArea = new CustomScrollArea(&window);
    // 例としてコンテンツサイズを設定 (デフォルトとは異なるサイズも試せる)
    // scrollArea->setContentSize(QSize(1200, 900));

    window.setCentralWidget(scrollArea);
    window.show();

    return a.exec();
}

ビルドと実行方法:

  1. 上記3つのファイルを同じディレクトリに保存します。(例: customscrollarea.h, customscrollarea.cpp, main.cpp

  2. .pro ファイルを作成します。(例: abstractscrollarea_example.pro

    QT += widgets
    
    HEADERS += customscrollarea.h
    SOURCES += customscrollarea.cpp main.cpp
    
    # QtCreatorで実行する際にコンソール出力を見たい場合(デバッグ用)
    # CONFIG += console
    
  3. Qt Creator を開き、.pro ファイルを開きます。

  4. プロジェクトをビルドし、実行します。

コードの解説

    • これが QAbstractScrollArea の「ビューポート」となるウィジェットです。
    • paintEvent(QPaintEvent *event) をオーバーライドして、実際にスクロールされるコンテンツ(この例では大きな長方形とテキスト)を描画します。
    • setScrollOffset(int x, int y) メソッドを通じて、親である CustomScrollArea から現在のスクロール位置を受け取ります。
    • paintEvent 内で painter.translate(-m_offsetX, -m_offsetY); を呼び出すことで、描画原点をスクロールオフセット分移動させ、コンテンツがスクロールしているように見せかけます。
  1. CustomScrollArea クラス

    • QAbstractScrollArea を継承します。
    • コンストラクタ (CustomScrollArea::CustomScrollArea):
      • setViewport(new CustomViewportWidget(this));
        • これが QAbstractScrollArea::setViewport() の最も重要な呼び出しです。 新しい CustomViewportWidget インスタンスを作成し、それをこの CustomScrollArea のビューポートとして設定します。QAbstractScrollArea はこのウィジェットの所有権を取得するため、手動で delete する必要はありません。
      • setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
      • setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        • スクロールバーが表示されるべきポリシーを設定します。Qt::ScrollBarAsNeeded は、コンテンツがビューポートより大きい場合にのみスクロールバーを表示します。
      • updateScrollBars();
        • スクロールバーの初期状態を設定するためにヘルパー関数を呼び出します。
    • scrollContentsBy(int dx, int dy) override
      • これは、スクロールバーが移動したとき(またはプログラム的にスクロール位置が変更されたとき)に QAbstractScrollArea によって呼び出される仮想関数です。
      • dxdy は、コンテンツが水平方向と垂直方向にどれだけ「スクロールしたか」を示すピクセル値です。
      • この関数内で、m_currentOffsetXm_currentOffsetY を更新し、その新しいオフセットを CustomViewportWidget に通知します (setScrollOffset() を介して)。これにより、ビューポートが再描画されます。
    • resizeEvent(QResizeEvent *event) override
      • CustomScrollArea ウィジェット自体のサイズが変更されたときに呼び出されます。
      • ビューポートのサイズも変更されるため、スクロールバーの範囲を再計算するために updateScrollBars() を呼び出す必要があります。
    • updateScrollBars() プライベートヘルパー関数
      • 水平および垂直スクロールバーの rangepageStep を計算し、設定します。
      • rangeコンテンツのサイズ - ビューポートのサイズ で計算されます。この値が0より大きい場合にスクロールバーが表示されます。
      • pageStep は、スクロールバーの溝をクリックしたときにスクロールする量を設定します(通常はビューポートのサイズに設定)。
      • horizontalScrollBar()->setValue() / verticalScrollBar()->setValue() を呼び出すことで、現在のスクロール位置がスクロールバーに反映されます。
  • スクロールバーの管理
    スクロールバーの range (範囲) と pageStep (ページステップ) は、コンテンツとビューポートのサイズに基づいて手動で計算し、設定する必要があります。resizeEvent やコンテンツのサイズが変更されたときにこれを更新することが重要です。
  • スクロールロジック
    QAbstractScrollArea は、スクロールバーのイベントや、scrollTo() などのメソッド呼び出しを scrollContentsBy() 仮想関数に変換します。この関数内で、ビューポートに描画するコンテンツのオフセットを計算し、ビューポートの再描画をトリガーする必要があります。
  • 描画の責任
    QAbstractScrollArea はコンテンツを描画しません。描画は、setViewport() で設定したウィジェットの paintEvent() で行う必要があります。
  • 所有権
    setViewport() に渡されたウィジェットの所有権は QAbstractScrollArea が取得します。自分で delete しないでください。


QScrollArea を使用する(最も一般的で推奨される代替策)


  • #include <QApplication>
    #include <QMainWindow>
    #include <QScrollArea>
    #include <QLabel>
    #include <QVBoxLayout>
    #include <QPushButton>
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
    
        QMainWindow window;
        window.setWindowTitle("QScrollArea Example");
        window.resize(400, 300);
    
        QScrollArea *scrollArea = new QScrollArea(&window);
        scrollArea->setWidgetResizable(true); // ウィジェットのサイズに合わせてリサイズ
    
        // スクロールさせたいコンテンツを作成
        QWidget *contentWidget = new QWidget();
        QVBoxLayout *layout = new QVBoxLayout(contentWidget);
        layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); // コンテンツを上揃え、左揃えに
    
        // たくさんのボタンを追加してスクロール可能にする
        for (int i = 0; i < 50; ++i) {
            layout->addWidget(new QPushButton(QString("Button %1").arg(i)));
        }
    
        // QScrollAreaにコンテンツウィジェットを設定
        scrollArea->setWidget(contentWidget); // setViewport() の代わり
    
        window.setCentralWidget(scrollArea);
        window.show();
    
        return a.exec();
    }
    
  • いつ使うか

    • 既存の QWidget や、QVBoxLayout / QHBoxLayout を持つ QWidget (複数の子ウィジェットを含む)をスクロールさせたい場合。
    • 複雑な描画ロジックが不要で、標準のウィジェットを配置するだけでよい場合。
    • ほとんどの一般的なスクロール可能なUI(例: フォーム、テキストエディタ、画像ビューアなど)に適しています。
    • スクロールバーの範囲やステップサイズ、スクロールオフセットの計算など、面倒なロジックをQtが自動的に処理してくれます。
    • ビューポート内のウィジェットのサイズが変更されたり、レイアウトの内容が変更されたりしても、QScrollArea が自動的にスクロールバーを調整します。
    • setWidgetResizable(bool resizable) プロパティを true に設定することで、ビューポート内のウィジェットをスクロールエリアのサイズに合わせて自動的にリサイズさせることもできます。

QGraphicsView を使用する


  • #include <QApplication>
    #include <QGraphicsView>
    #include <QGraphicsScene>
    #include <QGraphicsRectItem>
    #include <QMainWindow>
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
    
        QMainWindow window;
        window.setWindowTitle("QGraphicsView Example");
        window.resize(600, 400);
    
        QGraphicsScene *scene = new QGraphicsScene(0, 0, 1000, 800); // シーンの論理的なサイズ
        // シーンにアイテムを追加
        QGraphicsRectItem *rect = new QGraphicsRectItem(50, 50, 900, 700);
        rect->setPen(QPen(Qt::blue, 3));
        rect->setBrush(Qt::lightBlue);
        scene->addItem(rect);
    
        QGraphicsTextItem *text = new QGraphicsTextItem("QGraphicsView Content");
        text->setFont(QFont("Arial", 40, QFont::Bold));
        text->setPos(200, 300);
        scene->addItem(text);
    
        // QGraphicsViewを作成し、シーンを設定
        QGraphicsView *view = new QGraphicsView(scene, &window);
        view->setRenderHint(QPainter::Antialiasing); // アンチエイリアシング有効化
        view->setDragMode(QGraphicsView::ScrollHandDrag); // ドラッグでパン(スクロール)できるように
    
        // QGraphicsView は QAbstractScrollArea を継承しているため、
        // setViewport() は内部で既に使われている。
    
        window.setCentralWidget(view);
        window.show();
    
        return a.exec();
    }
    
  • いつ使うか

    • CADアプリケーション、図形エディタ、ゲーム、地図ビューアなど、大量の2Dグラフィックス要素をインタラクティブに表示・操作する必要がある場合。
    • 描画するコンテンツが単なるウィジェットの集合ではなく、カスタムな形状や複雑な描画ロジックを必要とする場合。
  • 利点

    • 非常に多数のアイテム(数千から数万)を効率的に描画できます。
    • ズーム、パン(スクロール)、回転といった変換を簡単に適用できます。
    • アイテムレベルでのイベント処理(クリック、ドラッグなど)が強力です。
    • 衝突検出、アイテムの選択、アニメーションなどをサポートします。

QAbstractItemView を継承したクラス(モデル/ビュープログラミング)


    • QTableView, QListView, QTreeView を直接使用するか、それらを継承してカスタムビューを作成します。
    #include <QApplication>
    #include <QMainWindow>
    #include <QListView>
    #include <QStringListModel>
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
    
        QMainWindow window;
        window.setWindowTitle("QListView Example");
        window.resize(300, 400);
    
        QListView *listView = new QListView(&window);
        QStringListModel *model = new QStringListModel(&window);
    
        QStringList data;
        for (int i = 0; i < 100; ++i) {
            data << QString("Item %1").arg(i);
        }
        model->setStringList(data);
    
        listView->setModel(model);
        // QListViewも内部でスクロールバーを持ちます。
    
        window.setCentralWidget(listView);
        window.show();
    
        return a.exec();
    }
    
  • いつ使うか

    • 表形式、リスト形式、ツリー形式など、構造化されたデータをスクロールして表示する必要がある場合。
    • データ量が多く、仮想スクロール(表示されている部分だけを描画する)が必要な場合。
  • 利点

    • 大量の構造化データを効率的に表示・操作できます。
    • データの表示とロジックを分離できるため、保守性が高いです。
    • ソート、フィルタリング、選択などの機能が標準で提供されます。
    • カスタムデリゲートを使用して、データの表示方法を高度にカスタマイズできます。

QAbstractScrollArea::setViewport() は、最も低レベルで柔軟なスクロールエリアの構築方法を提供しますが、ほとんどのアプリケーションでは、以下の代替手段がより適切で効率的です。

  1. QScrollArea
    既存の単一ウィジェット(またはレイアウトを持つウィジェット)をスクロールさせたい場合に最適です。最も推奨されるアプローチです。
  2. QGraphicsView
    複雑な2Dグラフィックス、多数のアイテム、インタラクティブな描画、ズーム/パンが必要な場合に最適です。
  3. QAbstractItemView を継承したクラス
    構造化されたデータを効率的に表示・操作したい場合に最適です。