QTableViewの見た目を自由自在に:paintEvent以外のカスタマイズ方法

2025-05-16

Qtプログラミングにおける void QTableView::paintEvent(QPaintEvent *event) は、QTableView ウィジェットが自身の内容を描画する際に呼び出される仮想関数です。

これは QWidget::paintEvent() を再実装したもので、テーブルビューが表示されるべき領域が変更されたり、無効化されたりしたときに、Qtのイベントシステムによって自動的に呼び出されます。

以下に詳しく説明します。

paintEvent の役割

  1. 描画のトリガー: ウィジェットの内容を更新する必要がある場合(例:ウィジェットのリサイズ、別のウィンドウに隠されていた部分が露出、update()repaint() の呼び出しなど)、Qtは対応するウィジェットに対して QPaintEvent を発行し、paintEvent() メソッドが呼び出されます。
  2. 描画処理の実行: paintEvent() の中で、実際の描画処理が行われます。QTableView の場合、これはセルの内容、グリッド線、ヘッダーなどの描画を指します。
  3. 効率的な描画: QPaintEvent オブジェクト (event 引数) には、描画が必要な領域 (QPaintEvent::rect() または QPaintEvent::region()) が含まれています。これにより、ウィジェット全体ではなく、変更があった部分のみを効率的に再描画することができます。

QTableView における paintEvent の特徴

QTableView は、QAbstractItemView から派生したウィジェットで、モデル/ビューアーキテクチャの一部です。そのため、paintEvent は以下の点を考慮して実装されています。

  • ビューのスタイル: QTableView の外観(グリッド線のスタイル、ヘッダーの表示など)は、Qtのスタイルシステムによって制御されます。paintEvent は、これらのスタイル設定を尊重して描画を行います。
  • デリゲートの使用: 各セルの描画は、通常、QItemDelegate を通して行われます。デフォルトのデリゲートが使用されるか、カスタムデリゲートを設定して、セルの描画方法をカスタマイズできます。paintEvent は、適切なデリゲートの paint() メソッドを呼び出す役割を担います。
  • モデルからのデータの取得: QTableView は、表示するデータを関連付けられた QAbstractItemModel から取得します。paintEvent の内部では、このモデルから各セルのデータを取得し、描画に利用します。

通常、QTableView を直接使う場合、paintEvent を自分でオーバーライドする必要はほとんどありません。QTableView は非常に高機能であり、多くの描画要件に対応できるように設計されているためです。

しかし、以下のような特殊なケースでは、QTableView を継承して paintEvent をオーバーライドすることが考えられます。

  • デフォルトの描画の変更: QTableView の標準的な描画では対応できないような、大幅な描画の変更を行いたい場合。
  • 独自の描画要素の追加: 例えば、セルの背景にカスタムの模様を描画したい、特定の行や列に特別なインジケーターを表示したい、など。

オーバーライドする際の注意点:

  • 描画領域の考慮: event->rect()event->region() を参照して、描画が必要な領域のみを描画するように心がけることで、パフォーマンスを向上させることができます。
  • QPainter の使用: 描画は QPainter クラスを使って行います。paintEvent の中では、QPainter painter(this); のようにして QPainter オブジェクトを作成し、それを使って線、図形、テキストなどを描画します。
  • 基底クラスの呼び出し: paintEvent をオーバーライドする際は、必ず最初に QTableView::paintEvent(event); を呼び出すようにしてください。これにより、QTableView の標準的な描画処理が実行され、その上で独自の描画を追加することができます。これを怠ると、ウィジェットが正しく描画されなかったり、予期せぬ動作が発生したりする可能性があります。

<!-- end list -->

#include <QTableView>
#include <QPainter>
#include <QPaintEvent>

class MyTableView : public QTableView
{
    Q_OBJECT // Q_OBJECT マクロが必要な場合

public:
    explicit MyTableView(QWidget *parent = nullptr) : QTableView(parent) {}

protected:
    void paintEvent(QPaintEvent *event) override
    {
        // まず、基底クラスの paintEvent を呼び出して、通常の描画を実行させる
        QTableView::paintEvent(event);

        // ここから、独自の描画処理を追加する
        QPainter painter(viewport()); // QTableViewのviewportに対して描画

        // 例:特定のセルの背景を赤く塗る
        // この例では、モデルからのデータやインデックスの取得は省略
        // 実際のアプリケーションでは、QModelIndexなどを利用してセルの位置を特定します
        painter.fillRect(rect().adjusted(10, 10, -10, -10), Qt::red); // 例としてテーブル全体を赤く塗る

        // 独自の描画が終わったら、painterはスコープを抜けると自動的に破棄される
    }
};


基底クラスの paintEvent() を呼び忘れる

エラーの症状:

  • カスタム描画は表示されるが、テーブル本来の機能が失われる。
  • QTableView の基本的な描画(セルの内容、グリッド線、ヘッダーなど)が全く表示されない、または一部しか表示されない。

原因: paintEvent() をオーバーライドする際、そのメソッドの最初の行で QTableView::paintEvent(event); を呼び出すのを忘れると、Qtのデフォルトの描画ロジックが実行されません。これにより、テーブルビューが適切に描画されず、空になったり、一部が欠落したりします。

トラブルシューティング: オーバーライドした paintEvent() の一番最初に、必ず基底クラスの paintEvent() を呼び出すようにしてください。

void MyTableView::paintEvent(QPaintEvent *event)
{
    QTableView::paintEvent(event); // これを必ず呼び出す!
    // ここに独自の描画コードを追加
}

QPainter の初期化場所が間違っている

エラーの症状:

  • アプリケーションがクラッシュする。
  • コンソールに QPainter::begin: Paint device returned engine == 0, type: X のような警告が表示される。
  • 描画が全く表示されない。

原因: QTableViewQAbstractScrollArea を継承しており、その描画はビューポート (viewport()) に対して行われます。QPainter を初期化する際に、誤って this (つまり QTableView 自体) を描画デバイスとして指定してしまうと、正しく描画されないことがあります。

トラブルシューティング: QPainterQTableView::viewport() を描画デバイスとして初期化する必要があります。

void MyTableView::paintEvent(QPaintEvent *event)
{
    QTableView::paintEvent(event);

    QPainter painter(viewport()); // ここを viewport() にする
    // ... 描画コード ...
}

paintEvent 内で重い処理やレイアウト変更を行う

エラーの症状:

  • 描画がループに陥り、無限に paintEvent が呼び出され続ける。
  • CPU使用率が異常に高くなる(特に描画イベントが頻繁に発生する場合)。
  • GUIのフリーズやラグが発生する。

原因: paintEvent() は非常に頻繁に呼び出される可能性のあるメソッドです。この中でファイルI/O、ネットワーク通信、複雑な計算、あるいはウィジェットのレイアウトを変更するような処理(例: setColumnWidth(), hideColumn() など)を行うと、パフォーマンスが著しく低下したり、無限再描画ループに陥ったりすることがあります。

トラブルシューティング:

  • 高負荷な処理を避ける: 時間のかかる処理は、別のスレッドで行うか、QTimer を使って後で実行するなど、paintEvent() の中からは避けてください。
  • 必要な領域のみを再描画: QPaintEvent *event オブジェクトには event->rect()event->region() というメソッドがあり、再描画が必要な領域を示します。この情報を使って、描画処理をその領域に限定することで、無駄な描画を省き、パフォーマンスを向上させることができます。
  • 描画処理のみに集中する: paintEvent() の目的は、ウィジェットの状態を画面に描画することです。データの準備やレイアウトの調整は、paintEvent() 以外の場所(例: resizeEvent(), モデルのデータ変更通知ハンドラ、または別途カスタムスロットなど)で行うべきです。

QPainter の状態管理の誤り

エラーの症状:

  • 他のウィジェットの描画に影響を与える。
  • 期待しない描画結果になる(例: 線が太すぎる、色が違う、変換が適用されたままになる)。

原因: QPainter でフォント、色、変換(回転、拡大縮小、移動)などを設定した後、その状態を元に戻さないと、その後の描画に影響を与えてしまいます。特に、複数の描画処理を行う場合や、複雑なカスタム描画を行う場合に発生しやすいです。

トラブルシューティング: QPainter の状態を一時的に保存・復元するために、QPainter::save()QPainter::restore() を使用します。

void MyTableView::paintEvent(QPaintEvent *event)
{
    QTableView::paintEvent(event);

    QPainter painter(viewport());

    // カスタム描画1
    painter.save(); // 状態を保存
    painter.setPen(Qt::red);
    painter.drawLine(0, 0, 100, 100);
    painter.restore(); // 状態を復元

    // カスタム描画2 (QPainterの状態は元の状態に戻っている)
    painter.save(); // 状態を保存
    painter.translate(50, 50);
    painter.drawRect(0, 0, 20, 20);
    painter.restore(); // 状態を復元
}

エラーの症状:

  • パフォーマンスの問題が発生しやすい。
  • セルの内容の一部だけをカスタマイズしたいのに、paintEvent を複雑にしすぎている。

原因: QTableView の個々のセルの描画をカスタマイズしたい場合、paintEvent を直接オーバーライドするのではなく、QStyledItemDelegate を継承し、その paint() メソッドをオーバーライドするのがQtの推奨する方法です。paintEvent はウィジェット全体の描画に使われるもので、個々のアイテムの描画にはデリゲートが適しています。

トラブルシューティング: セルの描画をカスタマイズする場合は、QTableView::setItemDelegate() または QTableView::setItemDelegateForColumn() / QTableView::setItemDelegateForRow() を使用して、カスタムデリゲートを設定することを検討してください。

// myitemdelegate.h
#include <QStyledItemDelegate>
#include <QPainter>
#include <QDebug>

class MyItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit MyItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        // まず、基底クラスの paint メソッドを呼び出して、通常のセルの描画を実行させる
        QStyledItemDelegate::paint(painter, option, index);

        // 例:特定の条件のセルに赤い枠を描画
        if (index.data(Qt::DisplayRole).toString() == "特殊") {
            painter->save();
            painter->setPen(QPen(Qt::red, 2));
            painter->drawRect(option.rect.adjusted(1, 1, -1, -1)); // オプションの矩形の内側に描画
            painter->restore();
        }
    }
};

// メインウィンドウなどでの使用例
// MyTableView *tableView = new MyTableView();
// tableView->setModel(myModel);
// tableView->setItemDelegate(new MyItemDelegate(tableView)); // デリゲートを設定


重要な注意点:

  • 以下の例では、QTableView の基本的な描画は維持しつつ、その上に何かを追加する形になっています。
  • QTableView のカスタム描画の多くは、QStyledItemDelegate を使用して個々のセルの描画をカスタマイズする方が適切です。paintEvent を直接オーバーライドするのは、テーブル全体にわたる描画(例:グリッド線以外に独自の線を描く、テーブルの特定領域にオーバーレイを描くなど)が必要な場合に適しています。

例1: テーブルの角にカスタムのマークを描画する

この例では、QTableView の右下隅に小さな色の付いた四角形を描画します。これは、テーブル全体に対するオーバーレイ描画の簡単な例です。

mycustomtableview.h

#ifndef MYCUSTOMTABLEVIEW_H
#define MYCUSTOMTABLEVIEW_H

#include <QTableView>
#include <QPainter>
#include <QPaintEvent>
#include <QDebug> // デバッグ出力用

class MyCustomTableView : public QTableView
{
    Q_OBJECT

public:
    explicit MyCustomTableView(QWidget *parent = nullptr);

protected:
    // paintEvent をオーバーライド
    void paintEvent(QPaintEvent *event) override;
};

#endif // MYCUSTOMTABLEVIEW_H

mycustomtableview.cpp

#include "mycustomtableview.h"

MyCustomTableView::MyCustomTableView(QWidget *parent)
    : QTableView(parent)
{
    // テーブルビューの初期設定(必要に応じて)
    // 例: モデルの設定など
}

void MyCustomTableView::paintEvent(QPaintEvent *event)
{
    // 1. まず、基底クラスの paintEvent を呼び出す
    //    これにより、QTableView の通常の描画(セル、ヘッダー、グリッドなど)が行われます。
    QTableView::paintEvent(event);

    // 2. 独自の描画処理を開始
    //    QPainter をビューポートに対して初期化します。
    //    QTableView は QAbstractScrollArea を継承しており、
    //    描画はスクロール可能な領域 (viewport) で行われます。
    QPainter painter(viewport());

    // 独自の描画を追加する例:右下隅に小さな四角形を描画
    // ウィジェットのサイズを取得
    QRect viewRect = viewport()->rect();

    // 右下隅に20x20ピクセルの四角形を描画
    int squareSize = 20;
    QRect customRect(viewRect.width() - squareSize,
                     viewRect.height() - squareSize,
                     squareSize,
                     squareSize);

    // 描画設定
    painter.setBrush(Qt::blue);      // 青いブラシで塗りつぶし
    painter.setPen(Qt::NoPen);       // 枠線なし

    // 四角形を描画
    painter.drawRect(customRect);

    // デバッグ出力
    // qDebug() << "MyCustomTableView::paintEvent called.";

    // QPainter オブジェクトはスコープを抜けるときに自動的に破棄されます。
}

main.cpp (使用例)

#include <QApplication>
#include <QStandardItemModel>
#include "mycustomtableview.h"

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

    // モデルの準備 (QStandardItemModelの例)
    QStandardItemModel model(4, 3); // 4行3列のモデル
    for (int row = 0; row < 4; ++row) {
        for (int col = 0; col < 3; ++col) {
            model.setItem(row, col, new QStandardItem(QString("Item %0,%1").arg(row).arg(col)));
        }
    }
    model.setHeaderData(0, Qt::Horizontal, "Column A");
    model.setHeaderData(1, Qt::Horizontal, "Column B");
    model.setHeaderData(2, Qt::Horizontal, "Column C");

    // カスタムQTableViewのインスタンスを作成
    MyCustomTableView tableView;
    tableView.setModel(&model); // モデルを設定
    tableView.setWindowTitle("Custom Table View with Custom Mark");
    tableView.resize(400, 300);
    tableView.show();

    return a.exec();
}

例2: 特定の列全体に透かし(オーバーレイテキスト)を描画する

この例では、テーブルの特定の列の上に半透明のテキストを描画します。これは、列全体に何らかのステータスや情報を示す場合に便利です。

mycustomtableview.h は例1と同じでOKです。

mycustomtableview.cpp (内容を更新)

#include "mycustomtableview.h"

MyCustomTableView::MyCustomTableView(QWidget *parent)
    : QTableView(parent)
{
    // テーブルビューの初期設定
}

void MyCustomTableView::paintEvent(QPaintEvent *event)
{
    QTableView::paintEvent(event); // 基底クラスの描画を呼び出す

    QPainter painter(viewport());

    // オーバーレイテキストを描画する列のインデックス
    int targetColumn = 1; // 2列目 (インデックス1)

    // その列が表示されているか確認
    if (isColumnHidden(targetColumn)) {
        return; // 隠れている場合は何もしない
    }

    // 列のジオメトリ(位置とサイズ)を取得
    // visualRect(index) は QModelIndex を必要とするため、ここでは ColumnViewportPosition() を使う
    // QTableView::columnViewportPosition() は QTableView の public メソッド
    // const QRect columnRect = QRect(columnViewportPosition(targetColumn), 0,
    //                                columnWidth(targetColumn), viewport()->height());
    // ↑ 上記はQt6.0以降で推奨される
    // Qt5系では QHeaderView の geometry() と sectionViewportPosition() を組み合わせる
    // より簡単な方法として、モデルのダミーインデックスを作成して visualRect を使う
    QModelIndex topLeftIndex = model()->index(0, targetColumn);
    QModelIndex bottomRightIndex = model()->index(model()->rowCount() - 1, targetColumn);

    if (!topLeftIndex.isValid() || !bottomRightIndex.isValid()) {
        return; // モデルが空の場合など
    }

    QRect columnRect = visualRect(topLeftIndex).united(visualRect(bottomRightIndex));
    // 列全体がビューポートからはみ出している可能性があるので、viewportの矩形にクリップ
    columnRect = columnRect.intersected(viewport()->rect());

    // 描画設定
    painter.setPen(QColor(0, 0, 0, 100)); // 半透明の黒
    QFont font = painter.font();
    font.setPointSize(24);
    font.setBold(true);
    painter.setFont(font);

    // テキストを描画 (列の真ん中に)
    QString overlayText = "DRAFT"; // 表示するテキスト

    // テキストを中央揃えで描画
    painter.drawText(columnRect, Qt::AlignCenter | Qt::TextWordWrap, overlayText);

    // デバッグ出力
    // qDebug() << "Overlay text drawn on column" << targetColumn;
}

main.cpp は例1と同じでOKです。実行すると、2列目(Column B)の上に "DRAFT" という半透明の文字が重なって表示されるはずです。

例3: 特定の行全体に背景色を適用する(条件付き描画)

この例では、特定の条件を満たす行(例: 2行目)に対してカスタムの背景色を描画します。これは QStyledItemDelegate を使う方が一般的ですが、paintEvent でも可能です。ただし、セルごとに描画するデリゲートに比べて、paintEvent で行全体を描画すると、テキストやアイコンがその上に描かれるように描画順序を注意する必要があります。

mycustomtableview.h は例1と同じでOKです。

mycustomtableview.cpp (内容を更新)

#include "mycustomtableview.h"

MyCustomTableView::MyCustomTableView(QWidget *parent)
    : QTableView(parent)
{
    // テーブルビューの初期設定
}

void MyCustomTableView::paintEvent(QPaintEvent *event)
{
    // 独自の背景描画を先に行うために、ここでは基底クラスの paintEvent を後に呼び出す
    // ただし、これによりQTableViewの描画が上書きされる可能性があるので注意が必要
    // 多くの場合、QStyledItemDelegateを使用する方が良い
    // この例では、基底クラスの描画の前に背景を塗りつぶし、その上に通常の描画が重なるようにする

    QPainter painter(viewport());

    // 描画が必要な領域のみを処理する (パフォーマンス向上)
    const QRect paintRect = event->rect();

    // 例: 2行目 (インデックス1) の背景を黄色にする
    int targetRow = 1;

    // ターゲット行の矩形を取得
    QModelIndex firstIndexInRow = model()->index(targetRow, 0);
    QModelIndex lastIndexInRow = model()->index(targetRow, model()->columnCount() - 1);

    if (firstIndexInRow.isValid() && lastIndexInRow.isValid()) {
        QRect rowRect = visualRect(firstIndexInRow).united(visualRect(lastIndexInRow));
        rowRect = rowRect.intersected(paintRect); // 描画イベントの矩形にクリップ

        if (!rowRect.isEmpty()) {
            painter.setBrush(QBrush(QColor(255, 255, 0, 100))); // 半透明の黄色
            painter.setPen(Qt::NoPen);
            painter.fillRect(rowRect, painter.brush());
        }
    }

    // 独自の背景描画が終わった後で、基底クラスの描画を呼び出す
    // これにより、黄色い背景の上にセルの内容(テキストなど)が描画されます。
    QTableView::paintEvent(event);

    // デバッグ出力
    // qDebug() << "MyCustomTableView::paintEvent called for custom row background.";
}

main.cpp は例1と同じでOKです。実行すると、2行目の背景が半透明の黄色になるはずです。

これらの例は、QTableView::paintEvent() をオーバーライドする基本的な方法と、その中で QPainter を使って独自の描画を行う方法を示しています。繰り返しますが、個々のセルの描画をカスタマイズしたい場合は、通常は QStyledItemDelegate を利用することを強くお勧めします。paintEvent は、テーブルビュー全体の描画や、特定の列/行のヘッダーなど、より広範囲なカスタマイズに適しています。 Qt の QTableView::paintEvent() を利用したプログラミングの例をいくつかご紹介します。これらの例は、QTableView を継承し、paintEvent をオーバーライドして独自の描画を追加する方法を示しています。

例1: テーブル全体に透かし(ウォーターマーク)を描画する

この例では、テーブルビューのデータの上に、半透明のテキストの透かしを描画します。

MyTableView.h

#ifndef MYTABLEVIEW_H
#define MYTABLEVIEW_H

#include <QTableView>
#include <QPaintEvent>
#include <QPainter>
#include <QDebug> // デバッグ用

class MyTableView : public QTableView
{
    Q_OBJECT

public:
    explicit MyTableView(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;
};

#endif // MYTABLEVIEW_H

MyTableView.cpp

#include "MyTableView.h"

MyTableView::MyTableView(QWidget *parent)
    : QTableView(parent)
{
}

void MyTableView::paintEvent(QPaintEvent *event)
{
    // まず、基底クラスの paintEvent を呼び出して、通常のテーブル描画を実行させる
    QTableView::paintEvent(event);

    // ここから独自の描画処理を開始
    QPainter painter(viewport()); // QTableViewのviewportに対して描画

    // 透かしのテキストを設定
    QString watermarkText = "機密情報";
    QFont font = painter.font();
    font.setPointSize(font.pointSize() * 3); // フォントサイズを大きくする
    font.setBold(true);
    painter.setFont(font);

    // 半透明の色を設定
    QColor textColor = Qt::red;
    textColor.setAlpha(80); // 半透明に設定 (0:完全透明, 255:完全不透明)
    painter.setPen(textColor);

    // 透かしの描画位置を計算(中央に配置)
    QRect viewRect = viewport()->rect();
    // QRectF textRect = painter.boundingRect(viewRect, Qt::AlignCenter, watermarkText); // Qt::AlignCenterだと自動で中央配置される
    // painter.drawText(viewRect, Qt::AlignCenter, watermarkText); // シンプルな中央配置

    // より複雑な配置(例:斜めに配置)
    painter.translate(viewRect.center()); // 原点をビューポートの中心に移動
    painter.rotate(-45); // -45度回転
    painter.drawText(-viewRect.width()/2, -viewRect.height()/2, viewRect.width(), viewRect.height(), Qt::AlignCenter, watermarkText);
    painter.resetTransform(); // 変換をリセット (重要!)

    // painter はスコープを抜けると自動的に破棄される
}

main.cpp (使用例)

#include <QApplication>
#include <QMainWindow>
#include <QStandardItemModel>
#include "MyTableView.h"

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

    QMainWindow window;
    MyTableView *tableView = new MyTableView(&window);

    // モデルを作成し、データを設定
    QStandardItemModel *model = new QStandardItemModel(5, 3, &window);
    for (int row = 0; row < 5; ++row) {
        for (int col = 0; col < 3; ++col) {
            QStandardItem *item = new QStandardItem(QString("Cell (%0, %1)").arg(row).arg(col));
            model->setItem(row, col, item);
        }
    }
    model->setHeaderData(0, Qt::Horizontal, "列A");
    model->setHeaderData(1, Qt::Horizontal, "列B");
    model->setHeaderData(2, Qt::Horizontal, "列C");

    tableView->setModel(model);
    tableView->resizeColumnsToContents(); // 列幅を内容に合わせる
    tableView->setGridStyle(Qt::DotLine); // グリッドスタイルを変更してみる

    window.setCentralWidget(tableView);
    window.setWindowTitle("Custom QTableView Example (Watermark)");
    window.resize(600, 400);
    window.show();

    return a.exec();
}

この例では、paintEvent 内で QPainter を使い、viewport() に透かしを描画しています。painter.translate()painter.rotate() を使って透かしを斜めに配置し、painter.resetTransform() で変換をリセットすることが重要です。

例2: 特定の行全体に背景色を描画する(より直接的な方法)

paintEvent を使って特定の行全体に色を塗る場合、QPainter を使って直接矩形を描画します。この方法は、QAbstractItemView::setRowBackground() のようなQtの既存の機能ではできない、より複雑な描画(例:グラデーション、パターンなど)が必要な場合に有効です。

MyTableView.h (例1と同じ)

MyTableView.cpp

#include "MyTableView.h"

MyTableView::MyTableView(QWidget *parent)
    : QTableView(parent)
{
}

void MyTableView::paintEvent(QPaintEvent *event)
{
    // まず、基底クラスの paintEvent を呼び出して、通常のテーブル描画を実行させる
    QTableView::paintEvent(event);

    QPainter painter(viewport()); // QTableViewのviewportに対して描画

    // 特定の行のインデックスを設定 (例: 2行目)
    int targetRow = 2;

    // 描画が必要な領域 (event->rect()) と交差するかチェック
    // これにより、必要な部分のみを描画し、パフォーマンスを向上させる
    if (targetRow >= 0 && targetRow < model()->rowCount()) {
        QRect rowRect = visualRect(model()->index(targetRow, 0)); // 0列目のインデックスから行の視覚的な矩形を取得
        rowRect.setWidth(viewport()->width()); // 行全体をカバーするために幅をビューポートの幅にする

        if (event->rect().intersects(rowRect)) {
            // 矩形をペイント
            painter.fillRect(rowRect, QColor(255, 255, 0, 100)); // 黄色を半透明で塗る

            // オプション:行全体に枠線を描画
            painter.setPen(QPen(Qt::darkYellow, 2));
            painter.drawRect(rowRect.adjusted(1, 1, -1, -1)); // 少し内側に描画
        }
    }
}

main.cpp (使用例) (例1と同じ)

注意点:

  • event->rect().intersects(rowRect) で、再描画が必要な領域と現在の行が重なっているかを確認しています。これにより、無駄な描画を避けることができます。
  • rowRect.setWidth(viewport()->width()); を使うことで、その行の幅をビューポート全体に広げています。
  • visualRect(QModelIndex) は、与えられたモデルインデックスに対応するウィジェット座標での矩形を返します。

QTableView のグリッド線は通常、スタイルシートや setGridStyle() で設定できますが、paintEvent をオーバーライドしてより高度なカスタマイズをすることも可能です。しかし、多くの場合、QTableViewdrawGrid() メソッドを再実装する方がより適切です。これは QTableView が内部的にグリッドを描画するために使用する仮想関数だからです。

ここでは、drawGrid() をオーバーライドする例を示します。

MyTableView.h

#ifndef MYTABLEVIEW_H
#define MYTABLEVIEW_H

#include <QTableView>
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOptionViewItem> // drawGrid() で使う
#include <QHeaderView> // ヘッダー情報を取得するため

class MyTableView : public QTableView
{
    Q_OBJECT

public:
    explicit MyTableView(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;
    // drawGrid() をオーバーライドしてグリッド線の描画をカスタマイズ
    // void drawGrid(QPainter *painter, const QRect &rect); // 古いQtバージョン
    // void drawGrid(QPainter *painter, const QRect &rect) override; // 最近のQtバージョン (protected slotsではない)
    void drawGrid(QPainter *painter, const QRect &rect) const override; // Qt 6
};

#endif // MYTABLEVIEW_H

MyTableView.cpp

#include "MyTableView.h"

MyTableView::MyTableView(QWidget *parent)
    : QTableView(parent)
{
    // 通常のグリッド線描画を無効にする(drawGridをオーバーライドするため)
    setGridStyle(Qt::NoPen);
}

void MyTableView::paintEvent(QPaintEvent *event)
{
    // 基底クラスの paintEvent を呼び出す
    QTableView::paintEvent(event);

    // 通常のpaintEventの後に、追加の描画をここで行う
    // drawGrid() をオーバーライドしているので、ここではグリッド線は描画しない
}

// グリッド線描画のカスタム実装
void MyTableView::drawGrid(QPainter *painter, const QRect &rect) const
{
    // ここで独自のグリッド線を描画
    // このメソッドは paintEvent 内部から自動的に呼び出される

    // 水平線を引く
    painter->setPen(QPen(Qt::blue, 1, Qt::DotLine)); // 青い点線
    for (int row = 0; row <= model()->rowCount(); ++row) {
        int y = rowViewportPosition(row);
        if (y < rect.top() || y > rect.bottom()) continue; // 描画範囲外ならスキップ
        painter->drawLine(rect.left(), y, rect.right(), y);
    }

    // 垂直線を引く
    painter->setPen(QPen(Qt::darkGreen, 1, Qt::SolidLine)); // 濃い緑の実線
    for (int col = 0; col <= model()->columnCount(); ++col) {
        int x = columnViewportPosition(col);
        if (x < rect.left() || x > rect.right()) continue; // 描画範囲外ならスキップ
        painter->drawLine(x, rect.top(), x, rect.bottom());
    }

    // オプション: ヘッダーと最初のセルの境界線を太くする
    painter->setPen(QPen(Qt::black, 2, Qt::SolidLine));
    painter->drawLine(rect.left(), horizontalHeader()->height(), rect.right(), horizontalHeader()->height()); // 水平ヘッダーの下
    painter->drawLine(verticalHeader()->width(), rect.top(), verticalHeader()->width(), rect.bottom()); // 垂直ヘッダーの右

}

main.cpp (使用例) (例1と同じ)

drawGrid() オーバーライドの注意点:

  • drawGrid のシグネチャはQtのバージョンによって若干異なる場合があります。上記はQt 6のシグネチャ void drawGrid(QPainter *painter, const QRect &rect) const override; に準拠しています。Qt 5では void drawGrid(QPainter *painter, const QRect &rect) override; であることが多いです。
  • drawGridpaintEvent の一部として呼び出されるため、QPainter は既に開始されています。引数で受け取った painter をそのまま使用してください。
  • rowViewportPosition(row)columnViewportPosition(col) は、指定された行/列のビューポート内でのピクセル位置を返します。
  • setGridStyle(Qt::NoPen); を呼び出して、Qtのデフォルトのグリッド線描画を無効にしています。そうしないと、カスタムの線とデフォルトの線が両方描画されてしまいます。


Qt で QTableView の描画をカスタマイズしたい場合、void QTableView::paintEvent() を直接オーバーライドすることは非常に強力な方法ですが、多くの場合、より特化した代替手段が存在します。これらの代替手段は、特定の描画要件に対してより効率的で、コードの見通しを良くし、Qtのモデル/ビューアーキテクチャの原則に沿ったものです。

QStyledItemDelegate を使用する (最も一般的で推奨される方法)

QTableView 内の個々のセルの描画をカスタマイズしたい場合、QStyledItemDelegate (またはその基底クラスである QAbstractItemDelegate) を継承し、その paint() メソッドをオーバーライドするのが最も推奨される方法です。

目的

  • セルの描画ロジックをデータモデルから分離する。
  • セル内にカスタムな要素(アイコン、プログレスバー、チェックボックスなど)を描画する。
  • セルのテキストの色、フォント、背景色を変更する。

利点

  • 編集機能との統合: デリゲートは描画だけでなく、セルの編集機能 (createEditor, setEditorData, setModelData) も担当します。
  • 再利用性: 作成したデリゲートは、他の QAbstractItemView クラス(QListViewQTreeView など)でも再利用できます。
  • 効率性: デリゲートは必要なセルのみを描画するため、paintEvent で全体の描画を処理するよりも効率的です。
  • MVCの原則: モデル(データ)、ビュー(表示)、コントローラ(デリゲート)を分離し、コードの保守性を高めます。

// MyCustomDelegate.h
#include <QStyledItemDelegate>
#include <QPainter>
#include <QDebug>

class MyCustomDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit MyCustomDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        // まず、基底クラスの paint メソッドを呼び出して、通常のセルの描画を実行させる
        QStyledItemDelegate::paint(painter, option, index);

        // 例: 2列目のセルの背景に特定の図形を描画
        if (index.column() == 1) {
            painter->save(); // 描画状態を保存

            // セル全体を覆うように長方形を描画
            painter->setBrush(QColor(100, 100, 255, 80)); // 半透明の青
            painter->setPen(Qt::NoPen);
            painter->drawRect(option.rect);

            // セルの中央にテキストを描画
            painter->setPen(Qt::white);
            painter->drawText(option.rect, Qt::AlignCenter, index.data(Qt::DisplayRole).toString());

            painter->restore(); // 描画状態を復元
        }
    }
};

// 使用例 (QTableView を設定する場所で)
// MyTableView *tableView = new MyTableView();
// QStandardItemModel *model = new QStandardItemModel(5, 3);
// // ... モデルにデータを設定 ...
// tableView->setModel(model);
// tableView->setItemDelegate(new MyCustomDelegate(tableView)); // デリゲートを設定

QAbstractItemModel の data() メソッドで Qt::DecorationRole や Qt::BackgroundRole などを利用する

モデルの data() メソッドは、ビューに表示するデータだけでなく、表示スタイルに関する情報も提供できます。これは、簡単な見た目の変更には非常に便利で、デリゲートを別途作成する手間を省けます。

  • セルの背景色、前景色、フォント、アイコンなどをデータに基づいて動的に変更する。
  • データ駆動: データの値に基づいて直接スタイルを変更できるため、ロジックが一箇所にまとまります。
  • シンプルさ: 複雑なカスタム描画が不要な場合、最も少ないコードで実現できます。

```cpp // MyStandardItemModel.h #include <QStandardItemModel>

class MyStandardItemModel : public QStandardItemModel { Q_OBJECT public: explicit MyStandardItemModel(int rows, int columns, QObject *parent = nullptr) : QStandardItemModel(rows, columns, parent) {}

QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::BackgroundRole) {
        // 例: 偶数行の背景色を薄い灰色にする
        if (index.row() % 2 == 0) {
            return QColor(240, 240, 240);
        }
    } else if (role == Qt::TextColorRole) {
        // 例: 特定のテキストを持つセルの色を赤にする
        if (QStandardItemModel::data(index, Qt::DisplayRole).toString() == "Warning") {
            return Qt::red;
        }
    } else if (role == Qt::FontRole) {
        // 例: 最初の列のフォントを太字にする
        if (index.column() == 0) {
            QFont font;
            font.setBold(true);
            return font;
        }
    }
    // 他のロールは基底クラスの data() に任せる
    return QStandardItemModel::data(index, role);
}

};

// 使用例 (QTableView を設定する場所で) // MyTableView *tableView = new MyTableView(); // MyStandardItemModel *model = new MyStandardItemModel(5, 3); // // ... モデルにデータを設定 (例: model->setItem(0, 0, new QStandardItem("Warning"));) ... // tableView->setModel(model);


-----

### 3\. `QTableView::drawGrid()` をオーバーライドする

`QTableView` のグリッド線だけをカスタマイズしたい場合、`paintEvent` をオーバーライドするよりも、**`drawGrid()` 仮想関数をオーバーライドする**方が適切です。

#### **目的**

  * グリッド線の色、スタイル、太さを完全にカスタマイズする。
  * 特定の条件に基づいてグリッド線を描画・非描画する。

#### **利点**

  * **特化**: グリッド線の描画に特化しており、他の描画ロジックと混ざりません。
  * **効率性**: `QTableView` の描画パイプラインに直接組み込まれています。

#### **例**

```cpp
// MyTableView.h (例1のdrawGrid()の定義と同じ)
#include <QTableView>
#include <QPainter>
#include <QRect>

class MyTableView : public QTableView
{
    Q_OBJECT
public:
    explicit MyTableView(QWidget *parent = nullptr) : QTableView(parent) {
        setGridStyle(Qt::NoPen); // デフォルトのグリッド描画を無効にする
    }
protected:
    void drawGrid(QPainter *painter, const QRect &rect) const override; // Qt 6
    // void drawGrid(QPainter *painter, const QRect &rect) override; // Qt 5
};

// MyTableView.cpp
#include "MyTableView.h"

void MyTableView::drawGrid(QPainter *painter, const QRect &rect) const
{
    // ここでカスタムのグリッド線を描画
    // 水平線
    painter->setPen(QPen(Qt::darkBlue, 1, Qt::DotLine)); // 青い点線
    for (int row = 0; row <= model()->rowCount(); ++row) {
        int y = rowViewportPosition(row);
        if (y < rect.top() || y > rect.bottom()) continue;
        painter->drawLine(rect.left(), y, rect.right(), y);
    }

    // 垂直線
    painter->setPen(QPen(Qt::darkCyan, 1, Qt::SolidLine)); // シアンの実線
    for (int col = 0; col <= model()->columnCount(); ++col) {
        int x = columnViewportPosition(col);
        if (x < rect.left() || x > rect.right()) continue;
        painter->drawLine(x, rect.top(), x, rect.bottom());
    }
}

スタイルシート (QSS) を使用する

Qtのスタイルシートは、CSSに似た構文でQtウィジェットの外観をカスタマイズするための強力な方法です。簡単な背景色、ボーダー、フォントなどの変更には非常に適しています。

  • ホバー時のスタイル変更など、インタラクティブな見た目を実現する。
  • テーブルビュー全体、ヘッダー、セルなどの背景色、フォント、ボーダーを変更する。
  • デザインの分離: UIデザインをコードから分離できる。
  • 柔軟性: 多くのプロパティをカスタマイズでき、状態 (:hover, :selected など) に応じた変更も可能。
  • コード不要: C++コードを書かずに見た目を変更できる。
// main.cpp または UI ファイルで
// QTableView のオブジェクト名が "myTableView" の場合
tableView->setStyleSheet(
    "QTableView {"
    "   background-color: #f0f0f0;" // テーブル全体の背景色
    "   selection-background-color: lightblue;" // 選択セルの背景色
    "   gridline-color: #d0d0d0;" // グリッド線の色
    "}"
    "QTableView::item {"
    "   padding: 5px;" // セルのパディング
    "}"
    "QTableView::item:selected {"
    "   background-color: #a0c0ff;" // 選択されたアイテムの背景色
    "   color: white;" // 選択されたアイテムの文字色
    "}"
    "QHeaderView::section {"
    "   background-color: #e0e0e0;" // ヘッダーの背景色
    "   padding: 4px;"
    "   border: 1px solid #c0c0c0;"
    "   font-weight: bold;"
    "}"
);

QAbstractScrollArea::setViewport() を使用する(より高度なケース)

QTableViewQAbstractScrollArea から継承しており、その viewport() はスクロール可能な領域を描画する QWidget です。非常に特殊な描画が必要な場合、このビューポートウィジェットをカスタムウィジェットに置き換えることができます。

  • QTableView の基本描画を根本的に変更したいが、paintEvent のオーバーライドだけでは不十分な場合。
  • スクロールエリアの描画パイプライン全体を制御したい場合。
  • 最大の柔軟性: ビューポート全体をカスタムウィジェットで置き換えられる。

注意点

  • 最後の手段: 他のどの方法でも実現できない場合にのみ検討すべきです。
  • 複雑性: この方法は非常に高度であり、通常は推奨されません。多くのデフォルト動作を手動で再実装する必要があるため、複雑性が大幅に増します。
  • 非常に特殊な描画が必要で、上記のどの方法でも実現できない: → QAbstractScrollArea::setViewport() (上級者向け)
  • テーブル全体に透かし、オーバーレイ、または完全にカスタムなレイヤーを追加したい: → QTableView::paintEvent() をオーバーライド
  • グリッド線の描画をカスタマイズしたい: → QTableView::drawGrid() をオーバーライド
  • テーブル全体の背景やヘッダーの見た目を変更したい: → スタイルシート
  • 個々のセルの見た目を変更したい: → QStyledItemDelegate または モデルの data() メソッド