QScrollArea 派生クラスを直接描画する方法

スクロール可能なウィジェットを実装するには QScrollArea を使用するとよい。
通常は、中身のウィジェットを生成し、setWidget() すれば、自動的にスクロールしてくれるようになる。

ただし、ウィジェットが ScrollArea に比べてかなり大きいと、クリッピングを自前で行わないと描画処理に不必要に時間を食う場合がある。だが、中身のウィジェットが親が持つスクロール情報を参照するのはあまり好ましくない。
で、ひとつの解決方法として、QScrollArea の派生クラスを作り、その中で直接描画する、という方法がある。

実はそのようなコードを以前にも書いたことがあるのだが、いざ書こうとすると、その具体的な方法をすっかり忘れていたし、ぐぐってもなかなか目的とする情報に行き着かなかったので、ここにメモしておく。

描画

描画は void paintEvent(QPaintEvent *); で行う。
ポイントは、自分自身に対して描画するのではなく、viewport() に対して描画するという点だ。

void ScrollWidget::paintEvent(QPaintEvent *)
{
  QPainter painter(viewport());
  painter に対して描画;
}

 スクロールバー情報の設定

setWidget() せず、自前で描画する場合は、スクロール範囲などを自前で設定しなくてはいけない。
そのための関数を用意しておき、resize() イベントが発生した場合は、それをコールする。

void ScrollWidget::resizeEvent(QResizeEvent *event)
{
  QScrollArea::resizeEvent(event);
  updateScrollInfo();
}
void ScrollWidget::updateScrollInfo()
{
  QScrollBar *hScrollBar = horizontalScrollBar();
  hScrollBar->setMinimum(0);
  hScrollBar->setMaximum(qMax(0, WIDTH - vprct.width()));
  hScrollBar->setPageStep(vprct.width());
  hScrollBar->setSingleStep(10);
  .....
}

 スクロール位置を考慮した描画

スクロール位置を考慮した描画を行うには、描画ハンドラ内で、horizontalScrollBar()->value(); にてスクロール位置を取得し、それを考慮した描画を行うとよい。

具体的には座標を上記値でそのつど補正するか、以下のように座標原点を移動するようにする。

void ScrollWidget::paintEvent(QPaintEvent *)
{
  int hpos = horizontalScrollBar()->value();
  QPainter painter(viewport());
  QTransform tf;
  tf.translate(-hpos, 0);		//	原点移動
  painter.setTransform(tf);
  .....
}