scrollToItem()だけじゃない!QListWidgetの多彩なスクロール制御

2025-05-31

QListWidget は、リスト形式でアイテムを表示するUIコンポーネントです。リストに多くのアイテムがあり、すべてが一度に表示できない場合、スクロールバーが表示されます。scrollToItem() 関数は、特定の QListWidgetItem を引数として受け取り、そのアイテムがビューポート(表示領域)内に収まるようにリストをスクロールさせます。

関数シグネチャ

この関数には、通常、以下の2つのオーバーロードがあります。

  1. void QListWidget::scrollToItem(const QListWidgetItem *item)

    • 指定された item がビューポート内に表示されるようにスクロールします。どのように表示されるかは、QAbstractItemView::EnsureVisible というデフォルトのヒント(後述)に従います。
  2. void QListWidget::scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible)

    • 指定された item がビューポート内に表示されるようにスクロールします。hint パラメータを使用して、アイテムがビューポート内でどのように配置されるかをより細かく制御できます。

QAbstractItemView::ScrollHint について

hint パラメータには、QAbstractItemView::ScrollHint 列挙型の値を指定できます。これにより、スクロールの挙動を調整できます。主な値は以下の通りです。

  • QAbstractItemView::PositionAtCenter

    • アイテムがビューポートの中央に表示されるようにスクロールします。
  • QAbstractItemView::PositionAtBottom

    • アイテムがビューポートの最下部に表示されるようにスクロールします。
  • QAbstractItemView::PositionAtTop

    • アイテムがビューポートの最上部に表示されるようにスクロールします。
    • アイテムがビューポート内に完全に表示されるようにスクロールします。もしアイテムがすでに完全に表示されている場合は、スクロールは行われません。

例えば、特定のテキストを持つアイテムを検索し、そのアイテムまでスクロールしたい場合などに利用できます。

// C++ の例

#include <QApplication>
#include <QListWidget>
#include <QListWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>

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

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QListWidget *listWidget = new QListWidget(&window);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("Item %1").arg(i));
    }
    layout->addWidget(listWidget);

    QPushButton *scrollButton = new QPushButton("Item 40までスクロール", &window);
    layout->addWidget(scrollButton);

    QObject::connect(scrollButton, &QPushButton::clicked, [&]() {
        // "Item 40" を検索
        QList<QListWidgetItem *> items = listWidget->findItems("Item 40", Qt::MatchExactly);
        if (!items.isEmpty()) {
            // 見つかったアイテムまでスクロールし、中央に表示する
            listWidget->scrollToItem(items.first(), QAbstractItemView::PositionAtCenter);
            // オプションで、そのアイテムを選択状態にする
            listWidget->setCurrentItem(items.first());
        }
    });

    window.show();
    return app.exec();
}


指定したアイテムが見つからない、または無効なアイテムを渡している

これは最も基本的な問題です。scrollToItem()に渡すQListWidgetItemが、実際にそのQListWidgetに存在しない場合、何も起こりません。

よくある原因とトラブルシューティング

  • アイテムの追加忘れ
    アイテムを作成したが、まだQListWidget::addItem()insertItem()でリストに追加していない。
    • 対策
      アイテムがリストに追加されてからscrollToItem()を呼び出すようにします。
  • 異なるQListWidgetのアイテム
    別のQListWidgetに属するアイテムを、現在のQListWidgetscrollToItem()に渡している。
    • 対策
      scrollToItem()を呼び出すQListWidgetに、渡すQListWidgetItemが実際に属していることを確認します。
  • アイテムの削除
    アイテムをリストから削除した後に、そのアイテムへのポインタを使ってscrollToItem()を呼び出している。
    • 対策
      QListWidgetItemへのポインタがまだ有効であり、リストに存在することを確認してから呼び出すようにします。例えば、findItems()などで取得したアイテムを使用し、その結果が空でないことを確認します。

スクロールが実行されない、または意図した場所にスクロールしない

scrollToItem()を呼び出したにもかかわらず、スクロールが行われない、または予期せぬ位置にスクロールされることがあります。

よくある原因とトラブルシューティング

  • カスタムデリゲートやアイテムウィジェットの影響
    カスタムデリゲートを使用している場合や、setItemWidget()でアイテムに独自のウィジェットを設定している場合、そのウィジェットのサイズヒント(sizeHint())が不正確だと、スクロールの計算に影響を与える可能性があります。
    • 対策
      カスタムデリゲートやアイテムウィジェットのsizeHint()が正確なサイズを返すように実装されていることを確認します。
  • 巨大なアイテム/不均一なアイテムサイズ
    非常に大きなアイテムが含まれていたり、各アイテムのサイズが大きく異なる場合、Qtが正確なスクロール位置を計算するのに問題が生じることがあります。
    • 対策
      QListWidget::setUniformItemSizes(true) を設定することで、すべてのアイテムが同じサイズであるとQtに伝え、パフォーマンスとスクロールの正確性を向上させることができます。ただし、これによりアイテムの見た目が崩れる可能性もあります。
  • QListWidgetがまだ表示されていない、または隠されている
    ウィジェットがまだ親に配置されていない、またはshow()が呼ばれていない、あるいはQStackedWidgetなどで非表示になっている場合、正確なレイアウト情報が取得できず、スクロールが正しく機能しないことがあります。
    • 対策
      QListWidgetが完全に表示され、レイアウトが解決された後にscrollToItem()を呼び出すようにします。例えば、ウィジェットのshowEvent()や、レイアウトが確定するシグナル(もしあれば)を利用します。
  • レイアウトの更新が不十分
    アイテムの追加や削除、サイズの変更など、QListWidgetのコンテンツやレイアウトに影響を与える操作を行った直後にscrollToItem()を呼び出すと、Qtがまだレイアウトの計算を終えておらず、正しくスクロールできない場合があります。
    • 対策
      • qApp->processEvents() を呼び出して、イベントキューを処理し、UIの更新を強制します。ただし、これは多用しすぎるとパフォーマンスの問題を引き起こす可能性があります。
      • レイアウトの変更が完了した後にscrollToItem()を呼び出すように、QTimer::singleShot()などを使用して遅延実行を試みることも有効です。
      • QListWidget::update()repaint() はUIの再描画を要求しますが、レイアウトの計算を強制するものではありません。
  • アイテムが既に表示領域内にある
    QAbstractItemView::EnsureVisible (デフォルト) を使用している場合、指定されたアイテムがすでにビューポート内に完全に表示されていれば、スクロールは行われません。
    • 対策
      アイテムを強制的に特定の位置に表示したい場合は、QAbstractItemView::PositionAtTopPositionAtBottom、または PositionAtCenterhint パラメータとして使用します。

パフォーマンスの問題

大量のアイテムを持つQListWidgetで頻繁にscrollToItem()を呼び出すと、パフォーマンスが低下する可能性があります。

よくある原因とトラブルシューティング

  • レイアウト計算のオーバーヘッド
    アイテムの追加や削除と同時にscrollToItem()を呼び出すことで、Qtが何度もレイアウト計算を行う必要がある。
    • 対策
      setUniformItemSizes(true) を使用して、アイテムのサイズ計算を最適化します。
    • 複数のアイテム操作(追加、削除)を行う場合は、QListWidget::setUpdatesEnabled(false) で一時的に更新を停止し、すべての操作が完了した後に setUpdatesEnabled(true) を呼び出すことで、一度にUIを更新することができます。ただし、これにより操作中のUIがフリーズしたように見える場合があるため、注意が必要です。
  • 過剰なスクロール操作
    不要なスクロール操作を頻繁に行っている。
    • 対策
      必要な場合にのみscrollToItem()を呼び出すようにロジックを見直します。例えば、ユーザーの入力にリアルタイムで反応する場合など、短期間に何度も呼び出す必要がある場合は、スロットル(一定時間内の呼び出し回数を制限)やデバウンス(最後の呼び出しから一定時間経過後に実行)のテクニックを検討します。

scrollToItem()以外のスクロール関連の関数(例: scrollToTop(), scrollToBottom(), setVerticalScrollBarPolicy(), autoScroll プロパティなど)と組み合わせて使用する際に、予期せぬ挙動が発生することがあります。

  • 他のスクロール操作とのタイミング
    複数の場所で異なるスクロール操作を呼び出している場合、それらのタイミングによっては競合が生じることがあります。
    • 対策
      スクロールを制御するロジックを一元化し、どのスクロール操作がいつ行われるかを明確に管理します。
  • autoScroll プロパティ
    QAbstractItemView::autoScroll プロパティが true に設定されている場合、ドラッグ&ドロップ操作などでビューが自動的にスクロールすることがあります。これがscrollToItem()によるスクロールと競合する可能性があります。
    • 対策
      必要に応じて setAutoScroll(false) を設定し、自動スクロールを無効にします。特に、ユーザーの選択に基づいて明示的にスクロールを制御したい場合に検討します。


基本的なスクロール例

最も基本的な使用例は、特定のアイテムを指定して、それが「見えるように」スクロールすることです。

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QLabel>
#include <QDebug> // デバッグ出力用

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

    QMainWindow window;
    window.setWindowTitle("QListWidget scrollToItem Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) { // 50個のアイテムを追加
        listWidget->addItem(QString("アイテム %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QPushButton *scrollToItemButton = new QPushButton("アイテム40までスクロール (EnsureVisible)", centralWidget);
    mainLayout->addWidget(scrollToItemButton);

    QObject::connect(scrollToItemButton, &QPushButton::clicked, [&]() {
        // "アイテム 40" を検索します
        // findItems() は一致するアイテムのリストを返します。ここでは最初のものを取得します。
        QList<QListWidgetItem*> foundItems = listWidget->findItems("アイテム 40", Qt::MatchExactly);
        if (!foundItems.isEmpty()) {
            QListWidgetItem *itemToScrollTo = foundItems.first();
            listWidget->scrollToItem(itemToScrollTo); // デフォルトのEnsureVisibleを使用
            listWidget->setCurrentItem(itemToScrollTo); // オプション:スクロール後、そのアイテムを選択する
            qDebug() << "「アイテム 40」までスクロールしました。";
        } else {
            qDebug() << "「アイテム 40」が見つかりませんでした。";
        }
    });

    window.resize(400, 600); // ウィンドウサイズを設定
    window.show();

    return app.exec();
}

解説

  1. QListWidget の初期化
    QListWidget を作成し、50個のアイテムを追加します。これにより、スクロールバーが表示されるのに十分なアイテムがリストに含まれるようになります。
  2. ボタンの作成と接続
    「アイテム40までスクロール」ボタンを作成し、その clicked シグナルをラムダ関数に接続します。
  3. アイテムの検索
    listWidget->findItems("アイテム 40", Qt::MatchExactly) を使用して、正確に「アイテム 40」というテキストを持つアイテムを検索します。Qt::MatchExactly は完全一致を指定します。
  4. スクロールの実行
    foundItems が空でない場合(アイテムが見つかった場合)、listWidget->scrollToItem(itemToScrollTo) を呼び出します。このオーバーロードはデフォルトで QAbstractItemView::EnsureVisible ヒントを使用します。これは、アイテムがビューポートに完全に表示されるようにスクロールします。
  5. setCurrentItem() (オプション)
    listWidget->setCurrentItem(itemToScrollTo) は、スクロールしたアイテムを視覚的に選択状態にします。これはスクロール操作とは独立した操作ですが、ユーザーエクスペリエンスを向上させます。

スクロールヒントを指定した例

scrollToItem() のもう一つのオーバーロードを使用すると、スクロール後のアイテムの配置をより細かく制御できます。

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QLabel>
#include <QLineEdit> // 入力フィールド用
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget scrollToItem with Hint Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 100; ++i) { // 100個のアイテムを追加
        listWidget->addItem(QString("データ項目 %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QLineEdit *targetItemLineEdit = new QLineEdit(centralWidget);
    targetItemLineEdit->setPlaceholderText("スクロールするアイテム番号を入力 (例: 50)");
    mainLayout->addWidget(targetItemLineEdit);

    QPushButton *scrollToTopButton = new QPushButton("指定アイテムを上端にスクロール", centralWidget);
    mainLayout->addWidget(scrollToTopButton);

    QPushButton *scrollToCenterButton = new QPushButton("指定アイテムを中央にスクロール", centralWidget);
    mainLayout->addWidget(scrollToCenterButton);

    QPushButton *scrollToBottomButton = new QPushButton("指定アイテムを下端にスクロール", centralWidget);
    mainLayout->addWidget(scrollToBottomButton);

    auto scrollHandler = [&](QAbstractItemView::ScrollHint hint) {
        bool ok;
        int itemNumber = targetItemLineEdit->text().toInt(&ok);
        if (ok && itemNumber >= 1 && itemNumber <= listWidget->count()) {
            // item() 関数で指定された行のQListWidgetItemを取得
            QListWidgetItem *itemToScrollTo = listWidget->item(itemNumber - 1); // 0-indexedなので-1
            if (itemToScrollTo) {
                listWidget->scrollToItem(itemToScrollTo, hint);
                listWidget->setCurrentItem(itemToScrollTo);
                qDebug() << QString("「データ項目 %1」までスクロールしました (Hint: %2)。")
                                .arg(itemNumber)
                                .arg(hint == QAbstractItemView::PositionAtTop ? "Top" :
                                     (hint == QAbstractItemView::PositionAtCenter ? "Center" :
                                      (hint == QAbstractItemView::PositionAtBottom ? "Bottom" : "EnsureVisible")));
            }
        } else {
            qDebug() << "無効なアイテム番号です。1から" << listWidget->count() << "の間の数値を入力してください。";
        }
    };

    QObject::connect(scrollToTopButton, &QPushButton::clicked, [&]() {
        scrollHandler(QAbstractItemView::PositionAtTop);
    });

    QObject::connect(scrollToCenterButton, &QPushButton::clicked, [&]() {
        scrollHandler(QAbstractItemView::PositionAtCenter);
    });

    QObject::connect(scrollToBottomButton, &QPushButton::clicked, [&]() {
        scrollHandler(QAbstractItemView::PositionAtBottom);
    });

    window.resize(400, 700);
    window.show();

    return app.exec();
}

解説

  1. 入力フィールドの追加
    QLineEdit を使用して、ユーザーがスクロールしたいアイテムの番号を入力できるようにします。
  2. 複数のボタンと共通のハンドラ
    • 3つのボタン(上端、中央、下端)を作成します。
    • scrollHandler というラムダ関数を作成し、QAbstractItemView::ScrollHint を引数として受け取るようにします。これにより、コードの重複を避けることができます。
    • 各ボタンの clicked シグナルを scrollHandler に接続し、適切な ScrollHint を渡します。
  3. アイテムの取得
    listWidget->item(itemNumber - 1) を使用して、行番号に基づいて QListWidgetItem を直接取得します。QListWidget の行は0から始まるため、ユーザー入力の番号から1を引いています。
  4. スクロールヒントの適用
    listWidget->scrollToItem(itemToScrollTo, hint) を呼び出し、指定された hint に従ってアイテムをスクロールします。

この例では、フィルタリングによって一部のアイテムが非表示になっているリストで、検索条件に合致する最初の非表示アイテムまでスクロールします。

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QLineEdit>
#include <QLabel>
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget Scroll to Hidden Item Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("ログメッセージ %1: データX").arg(i + 1));
    }
    // 特定のアイテムを非表示にする
    listWidget->item(5)->setHidden(true); // "ログメッセージ 6" を隠す
    listWidget->item(15)->setHidden(true); // "ログメッセージ 16" を隠す
    listWidget->item(30)->setHidden(true); // "ログメッセージ 31" を隠す
    mainLayout->addWidget(listWidget);

    QLineEdit *filterLineEdit = new QLineEdit(centralWidget);
    filterLineEdit->setPlaceholderText("フィルタ (例: データ)");
    mainLayout->addWidget(filterLineEdit);

    QPushButton *filterButton = new QPushButton("フィルタ適用", centralWidget);
    mainLayout->addWidget(filterButton);

    QPushButton *scrollToNextHiddenMatchButton = new QPushButton("次の隠れた一致アイテムへスクロール", centralWidget);
    mainLayout->addWidget(scrollToNextHiddenMatchButton);

    QObject::connect(filterButton, &QPushButton::clicked, [&]() {
        QString filterText = filterLineEdit->text();
        for (int i = 0; i < listWidget->count(); ++i) {
            QListWidgetItem *item = listWidget->item(i);
            if (item->text().contains(filterText, Qt::CaseInsensitive)) {
                item->setHidden(false); // フィルタに一致するものを表示
            } else {
                item->setHidden(true); // フィルタに一致しないものを隠す
            }
        }
    });

    QObject::connect(scrollToNextHiddenMatchButton, &QPushButton::clicked, [&]() {
        QString filterText = filterLineEdit->text();
        QListWidgetItem *foundItem = nullptr;
        for (int i = 0; i < listWidget->count(); ++i) {
            QListWidgetItem *item = listWidget->item(i);
            // アイテムが非表示で、かつフィルタテキストに一致する場合
            if (item->isHidden() && item->text().contains(filterText, Qt::CaseInsensitive)) {
                foundItem = item;
                break; // 最初に見つかったものを取得
            }
        }

        if (foundItem) {
            // 非表示アイテムまでスクロールし、中央に配置
            listWidget->scrollToItem(foundItem, QAbstractItemView::PositionAtCenter);
            // アイテムを可視化(オプション、目的による)
            // foundItem->setHidden(false);
            listWidget->setCurrentItem(foundItem);
            qDebug() << "次の隠れた一致アイテムまでスクロールしました: " << foundItem->text();
        } else {
            qDebug() << "隠れていて一致するアイテムは見つかりませんでした。";
        }
    });

    window.resize(400, 700);
    window.show();

    return app.exec();
}
  1. アイテムの非表示化
    ループでアイテムを追加した後、いくつかのアイテムを意図的に setHidden(true) で非表示にします。
  2. フィルタ機能
    filterLineEditfilterButton を使って、ユーザーが入力したテキストに基づいてアイテムの表示/非表示を切り替えます。
  3. 隠れたアイテムへのスクロール
    scrollToNextHiddenMatchButton のクリックハンドラ内で、以下の処理を行います。
    • リストをイテレートし、item->isHidden() で非表示のアイテムかどうかをチェックします。
    • さらに item->text().contains(filterText) で、フィルタテキストに一致するかどうかを確認します。
    • 最初に見つかった非表示で一致するアイテムが見つかったら、scrollToItem(foundItem, QAbstractItemView::PositionAtCenter) を呼び出してスクロールします。
    • foundItem->setHidden(false); をコメントアウトしていますが、必要であれば非表示アイテムをスクロール後に表示させることも可能です。


スクロールバーを直接操作する

QListWidgetQAbstractScrollArea を継承しており、垂直スクロールバーと水平スクロールバーにアクセスできます。これらのスクロールバーは QScrollBar クラスのインスタンスであり、その setValue() メソッドを使ってスクロール位置を直接設定できます。

この方法を使うためには、目的のアイテムの表示位置を計算し、その位置に対応するスクロールバーの値を決定する必要があります。これは scrollToItem() が内部的に行っていることと似ていますが、手動で行うことになります。

利点

  • スクロールバーの値を設定する前に、カスタムロジックを挿入できる。
  • より詳細なスクロール位置の制御が可能。

欠点

  • scrollToItem() のように、アイテムが「見えるように」するためのロジック(部分的に見えている場合の調整など)を自分で実装する必要があります。
  • アイテムの高さが異なる場合(uniformItemSizesfalse の場合)は、計算がさらに複雑になります。
  • アイテムの正確なピクセル位置を計算する必要があり、複雑になりがちです。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QScrollBar> // QScrollBar を使用するため
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget ScrollBar Direct Control Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("アイテム %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QPushButton *scrollToRowButton = new QPushButton("行番号39までスクロール (直接操作)", centralWidget);
    mainLayout->addWidget(scrollToRowButton);

    QObject::connect(scrollToRowButton, &QPushButton::clicked, [&]() {
        int targetRow = 39; // 0-indexed なので、アイテム40は行番号39
        if (targetRow >= 0 && targetRow < listWidget->count()) {
            QListWidgetItem *itemToScrollTo = listWidget->item(targetRow);
            if (itemToScrollTo) {
                // アイテムの矩形情報を取得
                QRect itemRect = listWidget->visualItemRect(itemToScrollTo);

                // 垂直スクロールバーを取得
                QScrollBar *vScrollBar = listWidget->verticalScrollBar();

                if (vScrollBar) {
                    // スクロールバーの最小値と最大値を取得
                    int minScrollValue = vScrollBar->minimum();
                    int maxScrollValue = vScrollBar->maximum();

                    // アイテムを上端に表示するためのスクロール値 (概算)
                    // これはアイテムのy座標に対応します
                    int scrollValue = itemRect.top();

                    // スクロール値が範囲内にあることを確認
                    scrollValue = qBound(minScrollValue, scrollValue, maxScrollValue);

                    vScrollBar->setValue(scrollValue);
                    listWidget->setCurrentItem(itemToScrollTo);
                    qDebug() << QString("行 %1 までスクロールしました (スクロールバー値: %2)。")
                                    .arg(targetRow + 1)
                                    .arg(scrollValue);
                }
            }
        } else {
            qDebug() << "無効な行番号です。";
        }
    });

    window.resize(400, 600);
    window.show();

    return app.exec();
}

注意点
visualItemRect() はアイテムが現在表示されている場合の矩形を返します。もしアイテムが現在ビューポート外にある場合、その正確な位置を取得するためには、もう少し複雑な計算が必要になることがあります。これは、QListWidget の内部モデルがアイテムのサイズや位置をどのように管理しているかによります。scrollToItem() はこれらの複雑な計算を抽象化してくれます。

QAbstractItemView::scrollTo() を使用する(より汎用的)

QListWidgetQAbstractItemView を継承しており、QAbstractItemView::scrollTo() メソッドも使用できます。このメソッドは QModelIndex を引数に取ります。QListWidget は内部的にモデルを使用しているため、QListWidgetItem に対応する QModelIndex を取得することでこのメソッドを使用できます。

利点

  • scrollToItem() と同様に、ScrollHint を指定できます。
  • モデル/ビューアーキテクチャの他のビュー (QTableView, QTreeView) でも共通して使用できる汎用的な方法です。

欠点

  • QListWidgetItem から QModelIndex への変換が必要です。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget scrollTo (QModelIndex) Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("アイテム %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QPushButton *scrollToIndexButton = new QPushButton("アイテム40までスクロール (QModelIndex)", centralWidget);
    mainLayout->addWidget(scrollToIndexButton);

    QObject::connect(scrollToIndexButton, &QPushButton::clicked, [&]() {
        // "アイテム 40" を検索
        QList<QListWidgetItem*> foundItems = listWidget->findItems("アイテム 40", Qt::MatchExactly);
        if (!foundItems.isEmpty()) {
            QListWidgetItem *itemToScrollTo = foundItems.first();
            // QListWidgetItem から QModelIndex を取得
            QModelIndex index = listWidget->indexFromItem(itemToScrollTo);

            if (index.isValid()) {
                // scrollTo(QModelIndex, ScrollHint) を使用
                listWidget->scrollTo(index, QAbstractItemView::PositionAtCenter); // 中央にスクロール
                listWidget->setCurrentItem(itemToScrollTo);
                qDebug() << "QModelIndex を使って「アイテム 40」までスクロールしました。";
            }
        } else {
            qDebug() << "「アイテム 40」が見つかりませんでした。";
        }
    });

    window.resize(400, 600);
    window.show();

    return app.exec();
}

解説

  • 取得した QModelIndexlistWidget->scrollTo(index, hint) に渡します。
  • listWidget->indexFromItem(itemToScrollTo) を使用して、目的の QListWidgetItem から対応する QModelIndex を取得します。

スクロールアニメーションを実装する

scrollToItem() は瞬時にスクロールしますが、より滑らかなユーザーエクスペリエンスのためにアニメーションスクロールが必要な場合があります。これはQtのアニメーションフレームワーク(QPropertyAnimation など)を使用して実装できます。

利点

  • スクロール速度やイージングカーブをカスタマイズできます。
  • 視覚的に魅力的なスクロール効果を提供できます。

欠点

  • パフォーマンスに影響を与える可能性があります。
  • 実装が複雑になります。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QPropertyAnimation> // アニメーション用
#include <QScrollBar>
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget Animated Scroll Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("アニメーションアイテム %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QPushButton *animateScrollButton = new QPushButton("アイテム40までアニメーションスクロール", centralWidget);
    mainLayout->addWidget(animateScrollButton);

    QObject::connect(animateScrollButton, &QPushButton::clicked, [&]() {
        QList<QListWidgetItem*> foundItems = listWidget->findItems("アニメーションアイテム 40", Qt::MatchExactly);
        if (!foundItems.isEmpty()) {
            QListWidgetItem *itemToScrollTo = foundItems.first();
            QRect itemRect = listWidget->visualItemRect(itemToScrollTo); // アイテムの現在の表示位置

            QScrollBar *vScrollBar = listWidget->verticalScrollBar();
            if (vScrollBar) {
                // アイテムが中央に来るように目標スクロール値を計算
                // スクロールバーの現在値 + (アイテムの上端Y座標 - (ビューポートの高さ / 2 - アイテムの高さ / 2))
                int currentScrollValue = vScrollBar->value();
                int targetScrollValue = itemRect.top() - (listWidget->viewport()->height() / 2 - itemRect.height() / 2);

                // スクロールバーの範囲内に収める
                targetScrollValue = qBound(vScrollBar->minimum(), targetScrollValue, vScrollBar->maximum());

                QPropertyAnimation *animation = new QPropertyAnimation(vScrollBar, "value", &app); // "value"プロパティをアニメーション
                animation->setDuration(500); // 500ms (0.5秒) でアニメーション
                animation->setStartValue(currentScrollValue);
                animation->setEndValue(targetScrollValue);
                animation->setEasingCurve(QEasingCurve::OutQuad); // 緩やかなアニメーション

                animation->start(QAbstractAnimation::DeleteWhenStopped); // アニメーション終了時に自動削除

                listWidget->setCurrentItem(itemToScrollTo);
                qDebug() << "「アニメーションアイテム 40」までアニメーションスクロールを開始しました。";
            }
        } else {
            qDebug() << "「アニメーションアイテム 40」が見つかりませんでした。";
        }
    });

    window.resize(400, 600);
    window.show();

    return app.exec();
}

解説

  1. QPropertyAnimation の利用
    QPropertyAnimation は、オブジェクトのプロパティ(この場合は QScrollBarvalue プロパティ)を時間とともに変化させるために使用されます。
  2. verticalScrollBar() の取得
    QListWidget の垂直スクロールバーを取得します。
  3. 目標スクロール値の計算
    アイテムがビューポートの中央に来るように、スクロールバーの最終的な値を計算します。visualItemRect() を使用してアイテムの現在の位置を取得し、それを基準に計算します。
  4. アニメーションの設定
    • setDuration() でアニメーションの時間を設定します。
    • setStartValue() で現在のスクロールバーの値を開始値として設定します。
    • setEndValue() で計算した目標スクロール値を終了値として設定します。
    • setEasingCurve() でアニメーションの進行カーブを設定します(例: QEasingCurve::OutQuad は開始時に速く、終了時にゆっくりになります)。
  5. アニメーションの開始
    animation->start(QAbstractAnimation::DeleteWhenStopped) を呼び出してアニメーションを開始し、アニメーションが終了したら自動的にメモリから削除されるようにします。

QListWidget::setCurrentRow(int row)QListWidget::setCurrentItem(QListWidgetItem *item) は、指定されたアイテムを「現在のアイテム」として設定します。QAbstractItemView のデフォルトの挙動として、現在のアイテムが変更された場合、そのアイテムがビューポートに見えるように自動的にスクロールされます(autoScroll プロパティがデフォルトで true のため)。

利点

  • scrollToItem() と同様に、アイテムがビューポートに表示されることを保証します(ただし、表示位置の制御は scrollToItem() ほど柔軟ではありません)。
  • 非常にシンプルで直感的です。

欠点

  • スクロールヒントを指定できないため、scrollToItem() のようにアイテムを上端や中央に強制的に配置することはできません。通常は EnsureVisible と同じ挙動になります。
  • 常にアイテムを選択状態にする必要があります。アイテムを選択したくない場合は不適切です。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QListWidgetItem>
#include <QDebug>

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

    QMainWindow window;
    window.setWindowTitle("QListWidget setCurrentRow Scroll Example");

    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    QListWidget *listWidget = new QListWidget(centralWidget);
    for (int i = 0; i < 50; ++i) {
        listWidget->addItem(QString("アイテム %1").arg(i + 1));
    }
    mainLayout->addWidget(listWidget);

    QPushButton *setCurrentRowButton = new QPushButton("行番号40を選択 (スクロール)", centralWidget);
    mainLayout->addWidget(setCurrentRowButton);

    QObject::connect(setCurrentRowButton, &QPushButton::clicked, [&]() {
        int targetRow = 39; // アイテム40 (0-indexed)
        if (targetRow >= 0 && targetRow < listWidget->count()) {
            listWidget->setCurrentRow(targetRow); // アイテムを選択し、自動的にスクロール
            qDebug() << QString("行 %1 を選択しました。").arg(targetRow + 1);
        } else {
            qDebug() << "無効な行番号です。";
        }
    });

    window.resize(400, 600);
    window.show();

    return app.exec();
}
  • listWidget->setCurrentRow(targetRow) を呼び出すだけで、指定された行のアイテムが現在のアイテムとして設定され、自動的にビューポートにスクロールされます。