void QCheckBox::paintEvent()

2025-06-01

QCheckBox::paintEvent() は、Qt のウィジェット描画システムにおいて非常に重要な仮想関数です。QCheckBox はチェックボックスウィジェットを表し、ユーザーがオン(チェック済み)またはオフ(未チェック)を切り替えることができるボタンです。この paintEvent() 関数は、QCheckBox が画面上に自分自身を描画する必要があるときに Qt フレームワークによって自動的に呼び出されます。

描画イベントの仕組み

Qt のウィジェットは、何らかの変更(例:サイズ変更、内容の変更、再描画の要求など)があった場合、自身を再描画する必要があります。この再描画の要求があると、Qt はそのウィジェットに対して「描画イベント」(QPaintEvent)を発生させます。そして、この描画イベントを処理するために、ウィジェットの paintEvent() 関数が呼び出されます。

QCheckBox の場合、paintEvent() が呼び出されるのは以下のような状況です。

  • チェック状態が変更されたとき(チェックマークの表示が変わるため)。
  • update() または repaint() 関数が呼び出され、明示的に再描画が要求されたとき。
  • ウィジェットが別のウィンドウによって隠され、その後再び表示されたとき。
  • ウィジェットのサイズが変更されたとき。
  • ウィジェットが最初に表示されるとき。

QCheckBox::paintEvent() の役割

QCheckBoxpaintEvent() は、以下の要素を適切に描画する責任があります。

  1. チェックインジケーター(チェックマーク/ボックス): チェックボックスの最も特徴的な部分であり、チェック状態(チェック済み、未チェック、場合によっては三状態の「部分的にチェック済み」)を視覚的に示します。
  2. テキストラベル: チェックボックスの横に表示される説明文(例:「自動ログイン」など)。
  3. アイコン(オプション): テキストの横に表示される小さな画像。
  4. フォーカス矩形: ウィジェットがフォーカスを持っている場合に表示される、点線の枠線など。
  5. 背景: ウィジェットの背景。

標準の QCheckBoxpaintEvent() の実装は、Qt のスタイルシステムを利用してこれらの要素を描画します。通常、以下のような処理が行われます。

void QCheckBox::paintEvent(QPaintEvent *)
{
    QStylePainter p(this); // QPainter の一種で、スタイルに基づいて描画を行う
    QStyleOptionButton opt; // QCheckBox の描画オプションを格納する構造体
    initStyleOption(&opt);  // QCheckBox の現在の状態(チェック状態、有効/無効、ホバー状態など)を opt に初期化する
    p.drawControl(QStyle::CE_CheckBox, opt); // スタイルエンジンを使ってチェックボックス全体を描画する
}
  • p.drawControl(QStyle::CE_CheckBox, opt): 実際にチェックボックスを描画するメインの呼び出しです。QStyle::CE_CheckBox は、描画するコントロールがチェックボックスであることを示し、opt に含まれる情報に基づいて描画が行われます。
  • initStyleOption(&opt): この関数は、QCheckBox の現在の状態やプロパティに基づいて QStyleOptionButton オブジェクトを初期化します。これにより、描画時に正しい情報がスタイルに渡されます。
  • QStyleOptionButton: これは、描画に必要なすべての情報(ウィジェットの状態、テキスト、アイコン、サイズなど)を QStylePainter に伝えるための構造体です。
  • QStylePainter: Qt のスタイルシステムを使ってウィジェットを描画するための特別な QPainter です。これにより、アプリケーションが実行されているプラットフォームのネイティブなルック&フィール(Windows、macOS、Linuxなど)に合わせてウィジェットが描画されます。

カスタム描画

開発者が QCheckBox の見た目をカスタマイズしたい場合、QCheckBox を継承し、この paintEvent() 関数をオーバーライドします。オーバーライドした paintEvent() の中で、QPainter を使用して独自の描画ロジックを実装します。

例えば、チェックマークの形を変えたり、テキストの色を動的に変更したり、カスタムなインジケーターを追加したりすることが可能です。ただし、カスタム描画を行う場合、Qt のスタイルシステムが提供するデフォルトの描画ロジックを置き換えることになるため、すべてのプラットフォームで一貫した見た目を維持するのが難しくなる可能性があります。

  • paintEvent() 関数内で複雑な計算やI/O処理を行わないようにしてください。描画イベントは頻繁に発生する可能性があるため、パフォーマンスに悪影響を与える可能性があります。


paintEvent() のオーバーライドは強力な機能ですが、正しく実装しないと様々な問題が発生する可能性があります。

ウィジェットが正しく再描画されない、または表示が古いままになる

エラー/症状
チェックボックスの状態(チェック済み/未チェック)がプログラムで変更されたり、ウィジェットのプロパティが変更されたりしても、見た目が更新されない。

原因
paintEvent() は、描画イベントが発生したときにのみ呼び出されます。ウィジェットの状態が変更された際に、手動で再描画をトリガーするのを忘れている場合、見た目が更新されません。

// 例: チェックボックスの状態変更後
myCheckBox->setChecked(true); // または setCheckState()
myCheckBox->update(); // これで paintEvent() が呼び出される

カスタム描画後にデフォルトの描画が消える

エラー/症状
paintEvent() をオーバーライドして独自の描画コードを書いた後、チェックボックスの通常のインジケーター(チェックマーク)やテキストが表示されなくなる。

原因
paintEvent() をオーバーライドした場合、基底クラスの paintEvent() を呼び出すのを忘れると、元の QCheckBox の描画ロジックが実行されません。カスタム描画は、デフォルトの描画を上書きする形になります。

トラブルシューティング
カスタム描画コードの前に、必ず基底クラスの paintEvent() を呼び出してください。これにより、Qtのスタイルシステムが提供するデフォルトのチェックボックスの描画が最初に行われ、その上にカスタム描画を追加することができます。

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

    QPainter painter(this);
    // ここに独自の描画コードを追加
    // 例: テキストの追加、特定の状態での枠線の描画など
}

もしデフォルトの描画を完全に置き換えたい場合は、基底クラスの呼び出しは不要ですが、その場合はチェックボックスのすべての要素(インジケーター、テキスト、フォーカス矩形など)を自分で描画する必要があります。

描画パフォーマンスの問題

エラー/症状
カスタム描画を含むチェックボックスが多数配置されている場合や、頻繁に再描画される場合に、アプリケーションの動作が遅くなる、またはCPU使用率が高くなる。

原因
paintEvent() 内で重い処理(ファイルI/O、ネットワーク通信、複雑な計算など)を行っているため。paintEvent() は非常に頻繁に呼び出される可能性があるため、ここで時間のかかる処理を行うとパフォーマンスに悪影響が出ます。

トラブルシューティング

  • アンチエイリアシングなどの描画品質設定は、必要に応じて限定的に使用し、パフォーマンスへの影響を考慮してください。
  • 描画オブジェクト(QPen, QBrush, QColor など)は、必要に応じて毎回作成するのではなく、クラスのメンバー変数として保持し、再利用することを検討してください。
  • 計算結果や画像をキャッシュできる場合は、事前に計算して paintEvent() 内ではキャッシュされたデータを使用するようにしてください。
  • paintEvent() 内では、描画に必要な最小限の処理のみを行ってください。

QPainter の設定が正しくない、または状態が保持されない

エラー/症状
描画される図形やテキストが期待通りの色、フォント、太さにならない。または、複数のカスタムウィジェットがある場合に、前のウィジェットの QPainter 設定が次のウィジェットに影響を与える。

原因
QPainter の設定(ペン、ブラシ、フォントなど)が正しく行われていないか、描画後に元の状態に戻されていないため。

トラブルシューティング

  • 複雑な描画を行う場合や、複数の異なる描画スタイルを適用する場合は、QPainter::save()QPainter::restore() を使用して、QPainter の現在の状態を保存・復元することを推奨します。これにより、ある描画操作が他の描画操作に影響を与えるのを防ぐことができます。
  • QPainter を使用する際は、必要な設定(setPen(), setBrush(), setFont() など)を毎回明示的に行い、描画前に正しい状態になっていることを確認してください。
void MyCustomCheckBox::paintEvent(QPaintEvent *event)
{
    QCheckBox::paintEvent(event); // 基底クラスの描画

    QPainter painter(this);
    painter.save(); // 現在のQPainterの状態を保存

    // ここでカスタム設定を行う
    painter.setPen(Qt::red);
    painter.drawText(rect(), Qt::AlignCenter, "カスタムテキスト");

    painter.restore(); // QPainterの状態を元に戻す
}

プラットフォーム固有の見た目の不一致

エラー/症状
あるOS(Windows、macOS、Linux)では意図した通りに表示されるが、別のOSでは見た目が崩れる、またはネイティブなスタイルと異なる。

原因
QStylePainterQStyle を使わずに完全にカスタム描画を行っている場合、Qtのスタイルシステムによるプラットフォーム固有の描画が利用されません。また、特定のハードコードされた値(サイズ、マージンなど)が別のプラットフォームでは適切でない場合があります。

トラブルシューティング

  • 異なるプラットフォームでテストを行い、見た目の差異を検証してください。
  • 特に細かなレイアウトやサイズ計算が必要な場合は、QStyleOptionButton を利用して、現在のスタイルが推奨するサイズや位置情報を取得するようにしてください。
  • 可能な限りQtのスタイルシステム(QStylePainterQStyledrawControl()subElementRect() など)を利用し、プラットフォームのルック&フィールに合わせた描画を心がけてください。

再帰的な paintEvent の呼び出し

エラー/症状
paintEvent() 内で、意図せずウィジェットの再描画をトリガーする操作(例: show()hide()resize()、または別のダイアログの表示など)を行うと、paintEvent() が無限ループに陥り、クラッシュしたり、システムがフリーズしたりする。

原因
paintEvent() は描画のために予約された関数です。ここでウィジェットの状態を大きく変更する操作(特にレイアウトや可視性を変更するもの)を行うと、その変更がさらなる描画イベントを発生させ、無限の描画ループに陥る可能性があります。

トラブルシューティング
paintEvent() 内では、描画以外の操作を絶対に行わないでください。ウィジェットの状態変更や、新しいウィンドウの表示などは、別のスロット関数やイベントハンドラで行うべきです。もし描画結果に応じて何かのアクションをトリガーする必要がある場合は、QTimer::singleShot() などを使って、描画サイクルの外で実行されるようにスケジュールしてください。

paintEvent() をオーバーライドする際は、以下の点を常に念頭に置いてください。

  • 描画のみ: paintEvent() 内では描画以外の副作用のある処理は行わない。
  • QPainter の管理: QPainter の設定は明示的に行い、save()/restore() で状態を管理する。
  • update(): 状態変更後に再描画が必要な場合は update() を呼び出す。
  • 効率: 描画は頻繁に行われるため、パフォーマンスを意識し、重い処理は行わない。
  • 基底クラスの呼び出し: デフォルトの描画が必要な場合は、QCheckBox::paintEvent(event); を呼び出す。


void QCheckBox::paintEvent() のプログラミング例 (Qt)

QCheckBoxpaintEvent() をオーバーライドして、デフォルトの描画を変更・拡張する具体的なプログラミング例をいくつか示します。これらの例を通じて、カスタム描画の基本的な方法と応用を理解できるでしょう。

例1: デフォルトの描画に加えて、カスタムテキストを追加する

この例では、標準のチェックボックスの描画を保持しつつ、その横にカスタムのテキストを追加します。

MyCustomCheckBox.h

#ifndef MYCUSTOMCHECKBOX_H
#define MYCUSTOMCHECKBOX_H

#include <QCheckBox>
#include <QPainter> // QPainter を使用するために必要
#include <QPaintEvent> // QPaintEvent を処理するために必要

class MyCustomCheckBox : public QCheckBox
{
    Q_OBJECT // Qt のシグナル/スロット機構を使うために必要

public:
    explicit MyCustomCheckBox(const QString &text = "", QWidget *parent = nullptr);

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

#endif // MYCUSTOMCHECKBOX_H

MyCustomCheckBox.cpp

#include "MyCustomCheckBox.h"

MyCustomCheckBox::MyCustomCheckBox(const QString &text, QWidget *parent)
    : QCheckBox(text, parent) // 基底クラスのコンストラクタを呼び出す
{
    // 必要に応じて追加の初期化を行う
}

void MyCustomCheckBox::paintEvent(QPaintEvent *event)
{
    // まず、基底クラスの paintEvent() を呼び出し、
    // デフォルトのチェックボックス(チェックマーク、テキストなど)を描画する
    QCheckBox::paintEvent(event);

    QPainter painter(this); // このウィジェット上で描画を行う QPainter を作成

    // QPainter の状態を保存 (ペンやブラシの設定など)
    painter.save();

    // カスタムテキストの色とフォントを設定
    painter.setPen(Qt::blue); // 青いペン
    QFont font = painter.font();
    font.setItalic(true); // イタリック体に設定
    painter.setFont(font);

    // チェックボックスのインジケーターとテキストの右側に、カスタムテキストを描画する
    // デフォルトのテキストの位置を考慮して、少し右にずらして描画する
    // 適当なオフセットを見つけるか、QStyle::itemTextRect() などで正確な領域を取得することも可能
    QRect textRect = rect(); // ウィジェット全体の矩形を取得
    textRect.adjust(width() / 2 + 10, 0, 0, 0); // 適当に右にオフセット

    // カスタムテキストを描画
    painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, " (追加情報)");

    // QPainter の状態を復元
    painter.restore();
}

使用例 (main.cpp または他のウィジェット内)

#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include "MyCustomCheckBox.h"

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

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

    MyCustomCheckBox *checkBox1 = new MyCustomCheckBox("標準の機能");
    MyCustomCheckBox *checkBox2 = new MyCustomCheckBox("特別なオプション");
    checkBox2->setChecked(true); // 初期状態でチェック済み

    layout->addWidget(checkBox1);
    layout->addWidget(checkBox2);

    window.setWindowTitle("カスタムチェックボックスの例");
    window.show();

    return a.exec();
}

解説

  1. QCheckBox::paintEvent(event); を最初に呼び出すことで、デフォルトのチェックボックスの描画(インジケーターと元のテキスト)がそのまま残ります。
  2. その後に QPainter を使って、カスタムのテキストを描画しています。
  3. painter.save()painter.restore() を使うことで、このカスタム描画で行った QPainter の設定(ペン、フォントなど)が、他の描画操作に影響を与えないようにしています。

例2: チェックボックスのインジケーター(チェックマーク)をカスタム画像に置き換える

この例では、デフォルトのチェックマークの代わりに、カスタムの画像(アイコン)をチェックボックスのインジケーターとして描画します。この場合、デフォルトのチェックボックスの描画の一部を抑制する必要があります。

MyImageCheckBox.h

#ifndef MYIMAGECHECKBOX_H
#define MYIMAGECHECKBOX_H

#include <QCheckBox>
#include <QPainter>
#include <QPaintEvent>
#include <QPixmap> // 画像を扱うために必要

class MyImageCheckBox : public QCheckBox
{
    Q_OBJECT

public:
    explicit MyImageCheckBox(const QString &text = "", QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    QPixmap checkedIcon;   // チェック済み状態のアイコン
    QPixmap uncheckedIcon; // 未チェック状態のアイコン
};

#endif // MYIMAGECHECKBOX_H

MyImageCheckBox.cpp

#include "MyImageCheckBox.h"
#include <QStyleOptionButton> // QStyleOptionButton を使用するために必要
#include <QStyle> // QStyle を使用するために必要

MyImageCheckBox::MyImageCheckBox(const QString &text, QWidget *parent)
    : QCheckBox(text, parent)
{
    // ここでカスタムアイコンをロードする
    // プロジェクトに 'checked.png' と 'unchecked.png' があることを前提とする
    // 適切なパスを指定してください
    checkedIcon.load(":/images/checked.png");   // リソースファイルからのロード例
    uncheckedIcon.load(":/images/unchecked.png"); // リソースファイルからのロード例

    // アイコンのロードが失敗した場合のフォールバックやエラー処理も検討
    if (checkedIcon.isNull() || uncheckedIcon.isNull()) {
        qWarning("Failed to load checkbox icons!");
        // デフォルトの描画をそのまま利用するなどのフォールバック処理を実装
    }
}

void MyImageCheckBox::paintEvent(QPaintEvent *event)
{
    QStyleOptionButton opt;
    initStyleOption(&opt); // QCheckBox の現在の状態を opt に初期化

    QPainter painter(this);
    painter.save();

    // 1. チェックインジケーター以外の部分(テキスト、背景、フォーカス矩形)を描画する
    // QStyle::SC_CheckBoxIndicator を描画しないように制御
    // これにより、デフォルトのチェックマークが描画されなくなる
    // この方法は、QStyle::CE_CheckBox の代わりに QStyle::CC_CheckBox を使用して、
    // 各サブコントロールを個別に描画する方がより柔軟です。

    // 通常のテキストと背景を描画
    // QStyle::CE_CheckBox を直接描画するとインジケーターも描画されるので、
    // ここでは個別のサブコントロールを描画するようにする
    style()->drawControl(QStyle::CE_CheckBoxLabel, &opt, &painter, this); // テキストを描画
    // 必要に応じて、背景やフォーカス矩形も描画
    // style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &painter, this); // 背景パネル
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); // ウィジェットの背景を描画する(多くの場合不要だが念のため)


    // 2. カスタムのチェックインジケーターを描画する
    // チェックボックスのインジケーターが描画されるべき領域を取得
    QRect indicatorRect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);

    QPixmap currentIcon;
    if (isChecked()) {
        currentIcon = checkedIcon;
    } else {
        currentIcon = uncheckedIcon;
    }

    if (!currentIcon.isNull()) {
        // アイコンをインジケーターの領域に合わせて描画
        painter.drawPixmap(indicatorRect, currentIcon.scaled(indicatorRect.size(),
                                                            Qt::KeepAspectRatio,
                                                            Qt::SmoothTransformation));
    } else {
        // アイコンがロードできなかった場合のフォールバックとして、
        // デフォルトのチェックインジケーターを描画することも可能
        // style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter, this);
    }

    painter.restore();
}

使用例 (main.cpp)

#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include "MyImageCheckBox.h"

// QRC ファイルの準備:
// 'images' というプレフィックスで 'checked.png' と 'unchecked.png' を追加する
// 例: resources.qrc
// <RCC>
//   <qresource prefix="/images">
//     <file>checked.png</file>
//     <file>unchecked.png</file>
//   </qresource>
// </RCC>

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

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

    MyImageCheckBox *checkBox1 = new MyImageCheckBox("画像でチェック");
    MyImageCheckBox *checkBox2 = new MyImageCheckBox("別の画像");
    checkBox2->setChecked(true);

    layout->addWidget(checkBox1);
    layout->addWidget(checkBox2);

    window.setWindowTitle("画像カスタムチェックボックスの例");
    window.show();

    return a.exec();
}

解説

  1. checkedIconuncheckedIcon は、QPixmap オブジェクトとしてクラスのメンバーに保持されます。これにより、paintEvent() が呼び出されるたびに画像を再ロードするのを防ぎ、パフォーマンスを向上させます。
  2. initStyleOption(&opt); で、現在のチェックボックスの状態を表す QStyleOptionButton を初期化します。
  3. style()->drawControl(QStyle::CE_CheckBoxLabel, &opt, &painter, this); を使用して、テキストのみを描画します。これにより、デフォルトのチェックインジケーターは描画されません。QStyle::CE_CheckBox を使用すると、インジケーターも描画されてしまうため、ここではより細かく制御しています。
  4. style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this); を使って、チェックインジケーターが本来描画されるべき領域を取得します。これにより、カスタム画像が正しい位置に描画されます。
  5. drawPixmap() を使って、現在のチェック状態に応じた画像を、取得したインジケーター領域に合わせて描画します。scaled() を使うことで、インジケーター領域のサイズに画像を合わせ、Qt::KeepAspectRatio でアスペクト比を維持します。

例3: チェックインジケーターの色を状態に応じて変更する(円形インジケーター)

この例では、カスタムな円形のインジケーターを描画し、その色をチェック状態やホバー状態に応じて変更します。

MyColoredCheckBox.h

#ifndef MYCOLOREDCHECKBOX_H
#define MYCOLOREDCHECKBOX_H

#include <QCheckBox>
#include <QPainter>
#include <QPaintEvent>
#include <QStyleOptionButton>
#include <QStyle>

class MyColoredCheckBox : public QCheckBox
{
    Q_OBJECT

public:
    explicit MyColoredCheckBox(const QString &text = "", QWidget *parent = nullptr);

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

#endif // MYCOLOREDCHECKBOX_H

MyColoredCheckBox.cpp

#include "MyColoredCheckBox.h"

MyColoredCheckBox::MyColoredCheckBox(const QString &text, QWidget *parent)
    : QCheckBox(text, parent)
{
    // マウスホバーイベントをトラッキングするために属性を設定
    setAttribute(Qt::WA_Hover);
}

void MyColoredCheckBox::paintEvent(QPaintEvent *event)
{
    QStyleOptionButton opt;
    initStyleOption(&opt); // QCheckBox の現在の状態を opt に初期化

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

    painter.save();

    // 1. テキストを描画する (デフォルトの描画を利用)
    style()->drawControl(QStyle::CE_CheckBoxLabel, &opt, &painter, this);

    // 2. カスタムの円形インジケーターを描画する
    QRect indicatorRect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);

    QColor baseColor;
    if (opt.state & QStyle::State_Enabled) { // 有効状態の場合
        if (opt.state & QStyle::State_MouseOver) { // マウスオーバー状態
            baseColor = Qt::darkGreen;
        } else if (opt.state & QStyle::State_On) { // チェック済み状態
            baseColor = Qt::darkBlue;
        } else { // 未チェック状態
            baseColor = Qt::darkGray;
        }
    } else { // 無効状態
        baseColor = Qt::lightGray;
    }

    // 円形インジケーターの背景
    painter.setBrush(QBrush(baseColor));
    painter.setPen(Qt::NoPen); // 枠線なし
    painter.drawEllipse(indicatorRect); // 円を描画

    // チェック済みの場合、中央に白いチェックマーク(または点)を描画
    if (opt.state & QStyle::State_On) {
        painter.setPen(QPen(Qt::white, 2)); // 白いペン、太さ2
        // シンプルなチェックマークの代わりに、中央に白い点を描画
        painter.drawPoint(indicatorRect.center());

        // より複雑なチェックマークを描画する例 (必要に応じて)
        // QPainterPath path;
        // path.moveTo(indicatorRect.x() + indicatorRect.width() * 0.25, indicatorRect.y() + indicatorRect.height() * 0.5);
        // path.lineTo(indicatorRect.x() + indicatorRect.width() * 0.45, indicatorRect.y() + indicatorRect.height() * 0.7);
        // path.lineTo(indicatorRect.x() + indicatorRect.width() * 0.75, indicatorRect.y() + indicatorRect.height() * 0.3);
        // painter.drawPath(path);
    }

    // フォーカス矩形を描画 (オプション、ネイティブの見た目を維持したい場合)
    if (opt.state & QStyle::State_HasFocus) {
        // フォーカス矩形をテキストの周りに描画したい場合
        // style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, &painter, this);

        // インジケーターの周りにフォーカスを描画する例
        QPen focusPen(Qt::darkBlue, 1, Qt::DotLine);
        painter.setPen(focusPen);
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(indicatorRect.adjusted(-2, -2, 2, 2)); // インジケーターより少し大きい矩形
    }

    painter.restore();
}

使用例 (main.cpp)

#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include "MyColoredCheckBox.h"

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

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

    MyColoredCheckBox *checkBox1 = new MyColoredCheckBox("色付きの円");
    MyColoredCheckBox *checkBox2 = new MyColoredCheckBox("もう一つの円");
    checkBox2->setChecked(true);
    MyColoredCheckBox *checkBox3 = new MyColoredCheckBox("無効な円");
    checkBox3->setEnabled(false); // 無効な状態

    layout->addWidget(checkBox1);
    layout->addWidget(checkBox2);
    layout->addWidget(checkBox3);

    window.setWindowTitle("色付きカスタムチェックボックスの例");
    window.show();

    return a.exec();
}

解説

  1. setAttribute(Qt::WA_Hover); を設定することで、マウスがウィジェットの上にあるかどうかをトラックできるようになります。これにより opt.stateQStyle::State_MouseOver フラグが適切に設定されます。
  2. painter.setRenderHint(QPainter::Antialiasing); を使うことで、円や線がギザギザにならないように滑らかに描画されます。
  3. initStyleOption(&opt); から取得した opt.state を使って、チェック状態 (QStyle::State_On) やマウスオーバー状態 (QStyle::State_MouseOver)、有効/無効状態 (QStyle::State_Enabled) を確認し、それに応じて描画色を変更しています。
  4. style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this); でインジケーター領域を取得し、その中に drawEllipse() で円を描画しています。
  5. チェック済みの場合、円の中央に白い点を描画することでチェックマークを表現しています。
  6. フォーカス矩形もカスタム描画に含めることで、デフォルトの見た目との一貫性を保ちます。

基本的な考え方

QCheckBoxpaintEvent() をオーバーライドする際は、以下のステップを踏むのが一般的です。

  1. QCheckBox を継承する新しいクラスを作成します。
  2. 新しく作成したクラスで paintEvent() をオーバーライドします。
  3. paintEvent() の中で、QPainter を使用して描画を行います。
  4. 必要に応じて、元の QCheckBox の描画ロジックを呼び出すか、完全に置き換えるかを選択します。

例1: デフォルト描画に加えてカスタムテキストを描画する

この例では、通常のチェックボックスの描画に加えて、チェック状態に応じて追加のテキストを描画します。

MyCustomCheckBox.h

#ifndef MYCUSTOMCHECKBOX_H
#define MYCUSTOMCHECKBOX_H

#include <QCheckBox>
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOptionButton> // QCheckBox の描画オプション

class MyCustomCheckBox : public QCheckBox
{
    Q_OBJECT
public:
    explicit MyCustomCheckBox(const QString &text = "", QWidget *parent = nullptr);

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

#endif // MYCUSTOMCHECKBOX_H

MyCustomCheckBox.cpp

#include "MyCustomCheckBox.h"
#include <QDebug> // デバッグ出力用

MyCustomCheckBox::MyCustomCheckBox(const QString &text, QWidget *parent)
    : QCheckBox(text, parent)
{
    // 必要に応じて、サイズヒントを調整することもできます。
    // setMinimumSize(sizeHint());
}

void MyCustomCheckBox::paintEvent(QPaintEvent *event)
{
    // 1. 基底クラスの paintEvent を呼び出す
    //    これにより、Qtのスタイルシステムがデフォルトのチェックボックスの描画を行います。
    QCheckBox::paintEvent(event);

    // 2. QPainter を初期化する
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効にする

    // 3. チェックボックスの現在の状態を取得する
    Qt::CheckState state = checkState();

    // 4. カスタム描画ロジック
    //    チェックボックスのテキスト領域を取得
    QStyleOptionButton opt;
    initStyleOption(&opt); // 現在のチェックボックスの状態をオプションに初期化

    // スタイルからテキストが描画されるべき領域を取得します
    // これを基にカスタムテキストを描画します
    QRect textRect = style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this);

    painter.setPen(Qt::blue); // カスタムテキストの色を設定
    QFont font = painter.font();
    font.setBold(true);
    painter.setFont(font);

    if (state == Qt::Checked) {
        // チェック済みの場合は「ON」と表示
        painter.drawText(textRect, Qt::AlignCenter, "ON!");
    } else if (state == Qt::Unchecked) {
        // 未チェックの場合は「OFF」と表示
        painter.drawText(textRect, Qt::AlignCenter, "OFF!");
    } else if (state == Qt::PartiallyChecked) {
        // 部分的にチェック済みの場合は「PARTIAL」と表示(三状態が有効な場合)
        painter.drawText(textRect, Qt::AlignCenter, "PARTIAL");
    }
}

使用例 (main.cpp または MainWindow.cpp)

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QWidget>
#include "MyCustomCheckBox.h"

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

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

    MyCustomCheckBox *checkBox1 = new MyCustomCheckBox("My Custom Checkbox 1");
    MyCustomCheckBox *checkBox2 = new MyCustomCheckBox("My Custom Checkbox 2");
    checkBox2->setCheckState(Qt::Checked); // 初期状態をチェック済みに設定

    // 三状態を有効にする場合
    MyCustomCheckBox *checkBox3 = new MyCustomCheckBox("My Tristate Checkbox");
    checkBox3->setTristate(true);
    checkBox3->setCheckState(Qt::PartiallyChecked);

    layout->addWidget(checkBox1);
    layout->addWidget(checkBox2);
    layout->addWidget(checkBox3);
    layout->addStretch(); // レイアウトの余白を埋める

    centralWidget->setLayout(layout);
    window.setCentralWidget(centralWidget);
    window.setWindowTitle("Custom QCheckBox Example");
    window.show();

    return a.exec();
}

解説

  • checkState() を使って現在のチェック状態を取得し、それに基づいて異なるテキストを描画しています。
  • style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this) は、テキストが描画されるべき領域(矩形)をスタイルから取得します。これにより、OSのスタイルに合わせてテキストの位置を調整できます。
  • initStyleOption(&opt) は、現在のウィジェットの状態(チェック状態、有効/無効、ホバー状態など)を QStyleOptionButton オブジェクトにコピーします。
  • QPainter を使用して、その上にカスタムテキストを描画しています。setRenderHint(QPainter::Antialiasing) は、描画を滑らかにするためによく使われます。
  • QCheckBox::paintEvent(event); を最初に呼び出すことで、標準のチェックボックスの描画(チェックインジケーターとテキスト)が保証されます。

この例では、デフォルトのチェックインジケーターの色をチェック状態に応じて変更します。この場合、インジケーターの描画部分を自分で扱う必要があります。

MyColoredCheckBox.h

#ifndef MYCOLOREDCHECKBOX_H
#define MYCOLOREDCHECKBOX_H

#include <QCheckBox>
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOptionButton>
#include <QStylePainter> // QStylePainter を使用

class MyColoredCheckBox : public QCheckBox
{
    Q_OBJECT
public:
    explicit MyColoredCheckBox(const QString &text = "", QWidget *parent = nullptr);

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

#endif // MYCOLOREDCHECKBOX_H

MyColoredCheckBox.cpp

#include "MyColoredCheckBox.h"

MyColoredCheckBox::MyColoredCheckBox(const QString &text, QWidget *parent)
    : QCheckBox(text, parent)
{
}

void MyColoredCheckBox::paintEvent(QPaintEvent *event)
{
    QStylePainter painter(this);
    QStyleOptionButton opt;
    initStyleOption(&opt); // 現在のチェックボックスの状態をオプションに初期化

    // チェックインジケーターの領域を取得
    QRect indicatorRect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);
    // テキストコンテンツの領域を取得 (これはデフォルトの描画で使用されます)
    QRect textRect = style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this);

    // 1. チェックインジケーターをカスタム描画する
    painter.save(); // QPainter の状態を保存

    // インジケーターの色を設定
    QColor indicatorColor;
    switch (checkState()) {
        case Qt::Checked:
            indicatorColor = Qt::green; // チェック済みは緑
            break;
        case Qt::Unchecked:
            indicatorColor = Qt::red;   // 未チェックは赤
            break;
        case Qt::PartiallyChecked:
            indicatorColor = Qt::yellow; // 部分的にチェック済みは黄
            break;
    }

    // 背景を塗りつぶす (インジケーターの領域全体)
    painter.fillRect(indicatorRect, indicatorColor);

    // デフォルトのチェックマークを描画する (オプション)
    // これにより、カスタムの背景の上にネイティブのチェックマークが描画されます。
    // QStyle::PE_IndicatorCheckBox はチェックマークそのものの描画です。
    // 必要ない場合はコメントアウトしてください。
    style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter);

    painter.restore(); // QPainter の状態を元に戻す

    // 2. テキストラベルとフォーカス矩形などの他の要素をデフォルトスタイルで描画する
    //    CE_CheckBoxLabel はテキストを描画します
    //    CE_CheckBoxFocusRect はフォーカス矩形を描画します
    //    これらも drawControl の一部として描画されるため、通常は個別に呼び出しません。
    //    しかし、ここではインジケーター以外はデフォルトに任せるため、分けて描画します。

    // テキストラベルを描画
    // opt.rect をテキスト領域に設定してから描画
    opt.rect = textRect;
    painter.drawControl(QStyle::CE_CheckBoxLabel, opt);

    // フォーカス矩形を描画 (ウィジェットがフォーカスを持っている場合)
    if (opt.state & QStyle::State_HasFocus) {
        QStyleOptionFocusRect focusOpt;
        focusOpt.initFrom(this);
        focusOpt.rect = style()->subElementRect(QStyle::SE_CheckBoxFocusRect, &opt, this);
        style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter);
    }
}

使用例 (main.cpp または MainWindow.cpp) (例1と同じ main.cpp を使用できますが、MyCustomCheckBoxMyColoredCheckBox に置き換えてください)

#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QWidget>
#include "MyColoredCheckBox.h" // MyCustomCheckBox の代わりにこれをインクルード

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

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

    MyColoredCheckBox *checkBox1 = new MyColoredCheckBox("Red/Green Checkbox 1");
    MyColoredCheckBox *checkBox2 = new MyColoredCheckBox("Red/Green Checkbox 2");
    checkBox2->setCheckState(Qt::Checked);

    MyColoredCheckBox *checkBox3 = new MyColoredCheckBox("Tristate Colored Checkbox");
    checkBox3->setTristate(true);
    checkBox3->setCheckState(Qt::PartiallyChecked);

    layout->addWidget(checkBox1);
    layout->addWidget(checkBox2);
    layout->addWidget(checkBox3);
    layout->addStretch();

    centralWidget->setLayout(layout);
    window.setCentralWidget(centralWidget);
    window.setWindowTitle("Colored QCheckBox Example");
    window.show();

    return a.exec();
}
  • painter.save()painter.restore() は、描画設定(色、フォントなど)が他の描画操作に影響を与えないようにするために重要です。
  • drawControl(QStyle::CE_CheckBoxLabel, opt); はテキストを描画し、drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter); はフォーカス矩形を描画します。これにより、インジケーター以外の部分はネイティブなスタイルが保たれます。
  • style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter); を呼び出すことで、Qtのスタイルシステムが提供するデフォルトのチェックマーク(チェック済みの線など)をカスタムの背景の上に描画できます。
  • QStyle::SE_CheckBoxIndicator を使ってチェックボックスのインジケーター(箱とチェックマークの部分)の領域を取得し、その領域をカスタムの色で塗りつぶしています。
  • この例では、QCheckBox::paintEvent(event); を直接呼び出すのではなく、QStylePainterQStyle を使って描画の各要素をより細かく制御しています。


void QCheckBox::paintEvent() の代替手段

主な代替手段は以下の2つです。

  1. Qt Style Sheets (QSS): CSSに似た構文を使用して、Qtウィジェットの見た目を宣言的に変更する方法です。最も一般的で推奨されるカスタマイズ方法です。
  2. QStyle のサブクラス化: Qtの描画スタイルをカスタマイズする高度な方法です。既存のスタイルをベースに、特定のウィジェットの描画ロジックの一部を変更したい場合に有効です。

それぞれについて詳しく説明します。

Qt Style Sheets (QSS) を使用する

Qt Style Sheetsは、ウェブのCSSに非常に似た強力なメカニズムで、Qtアプリケーションの見た目を定義できます。ほとんどの視覚的なカスタマイズは paintEvent() をオーバーライドすることなくQSSで実現できます。

利点

  • 再利用性: アプリケーション全体、特定のウィンドウ、または個々のウィジェットにスタイルを適用できます。
  • クロスプラットフォーム: ほとんどのQSSはプラットフォームに依存せず、一貫した見た目を実現できます。
  • 柔軟性: 擬似状態(:hover, :checked, :pressed など)やサブコントロール(::indicator, ::text など)を細かく指定して、さまざまな状態での見た目を定義できます。
  • 宣言的: コードではなくスタイルシートファイル(または文字列)で見た目を定義するため、UIとロジックが分離され、管理しやすいです。

欠点

  • 特定のスタイル(特にネイティブなスタイル)との統合が難しい場合があります。スタイルシートを適用すると、ネイティブなスタイルが完全に上書きされることがあります。
  • QPainter を使ったピクセル単位の複雑な描画(例: 独自の曲線や複雑なアニメーション)はできません。

QCheckBoxでのQSSの例

チェックボックスのインジケーター(チェックマークの部分)やテキストの色、フォントなどを変更できます。

/* すべてのQCheckBoxのインジケーターサイズを変更 */
QCheckBox::indicator {
    width: 20px;
    height: 20px;
}

/* 未チェック状態のインジケーターに画像を設定 */
QCheckBox::indicator:unchecked {
    image: url(:/images/unchecked_icon.png);
}

/* チェック済み状態のインジケーターに画像を設定 */
QCheckBox::indicator:checked {
    image: url(:/images/checked_icon.png);
}

/* ホバー時のテキスト色を変更 */
QCheckBox:hover {
    color: blue;
}

/* チェック済みで無効化されている状態のテキスト色を変更 */
QCheckBox:checked:disabled {
    color: grey;
}

/* チェックボックスのテキストフォントと色を変更 */
QCheckBox {
    font: 14pt "Arial";
    color: #333;
}

/* インジケーターが部分的にチェックされている場合の画像を設定 */
QCheckBox::indicator:indeterminate {
    image: url(:/images/partial_checked_icon.png);
}

このスタイルシートは、QApplication::setStyleSheet() または特定のウィジェットの setStyleSheet() メソッドを使って適用できます。

// アプリケーション全体に適用
qApp->setStyleSheet("QCheckBox::indicator:checked { image: url(:/images/checked_icon.png); }");

// 特定のチェックボックスに適用
myCheckBox->setStyleSheet("QCheckBox { color: red; }");

QStyle のサブクラス化(QProxyStyle)

paintEvent() をオーバーライドする代わりに、Qtの描画スタイル(QStyle)を直接カスタマイズする方法です。これは、Qtアプリケーション全体のルック&フィールを統一的に変更したい場合や、既存のスタイルの特定の描画動作だけを変更したい場合に非常に強力です。

通常は QProxyStyle を継承し、特定の描画関数(例: drawPrimitive(), drawControl(), subElementRect() など)をオーバーライドします。

利点

  • パフォーマンス: スタイルシステムは効率的な描画を考慮して設計されています。
  • ネイティブスタイルとの統合: QProxyStyle を使用することで、ベースとなるネイティブスタイルの他の部分を維持しながら、特定の描画ロジックだけを変更できます。これは paintEvent() で完全に描画を上書きするよりも、より「Qtらしい」アプローチです。
  • システム全体への影響: アプリケーション内のすべてのウィジェット(または特定の種類のウィジェット)に一貫したカスタム描画を適用できます。

欠点

  • 非常に低レベルなカスタマイズであり、単純な見た目の変更には過剰な場合があります。
  • paintEvent() をオーバーライドするよりも学習曲線が急です。QStyle の概念(QStyleOption, PrimitiveElement, ControlElement, SubElement など)を理解する必要があります。

QCheckBoxでのQStyleサブクラス化の概念

#include <QProxyStyle>
#include <QPainter>

class MyCustomStyle : public QProxyStyle
{
public:
    MyCustomStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {}

    // PE_IndicatorCheckBox(チェックマークなど)の描画をカスタマイズする
    void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const override
    {
        if (element == PE_IndicatorCheckBox) {
            const QStyleOptionButton *btnOpt = qstyleoption_cast<const QStyleOptionButton *>(option);
            if (btnOpt) {
                // チェック状態に応じて異なる色で描画
                if (btnOpt->state & QStyle::State_On) { // Qt::Checked
                    painter->fillRect(option->rect, Qt::darkGreen);
                } else if (btnOpt->state & QStyle::State_Off) { // Qt::Unchecked
                    painter->fillRect(option->rect, Qt::darkRed);
                } else if (btnOpt->state & QStyle::State_NoChange) { // Qt::PartiallyChecked
                    painter->fillRect(option->rect, Qt::darkYellow);
                }
                // 必要であれば、ここでカスタムのチェックマークを描画する
                // 例: painter->drawPixmap(...)
                // または、基底クラスの描画を呼び出して、その上に何かを描画する
                // QProxyStyle::drawPrimitive(element, option, painter, widget);
            }
        } else {
            // 他のすべての描画プリミティブは基底クラスのスタイルに任せる
            QProxyStyle::drawPrimitive(element, option, painter, widget);
        }
    }

    // CE_CheckBox(インジケーターとラベルを含むコントロール全体)の描画をカスタマイズする
    void drawControl(ControlElement element, const QStyleOption *option,
                     QPainter *painter, const QWidget *widget) const override
    {
        if (element == CE_CheckBox) {
            // CE_CheckBoxをカスタマイズする場合、PE_IndicatorCheckBox や CE_CheckBoxLabel を自分で描画する必要がある
            // 例:
            // まずはインジケーターを描画 (上記 drawPrimitive でカスタマイズされる)
            drawPrimitive(PE_IndicatorCheckBox, option, painter, widget);

            // その後、テキストラベルを描画
            const QStyleOptionButton *btnOpt = qstyleoption_cast<const QStyleOptionButton *>(option);
            if (btnOpt) {
                QStyleOptionButton labelOpt = *btnOpt;
                labelOpt.rect = subElementRect(SE_CheckBoxContents, btnOpt, widget); // テキスト領域を取得
                drawControl(CE_CheckBoxLabel, &labelOpt, painter, widget); // テキストを描画
            }
        } else {
            // 他のすべてのコントロールは基底クラスのスタイルに任せる
            QProxyStyle::drawControl(element, option, painter, widget);
        }
    }
};

// アプリケーション起動時にカスタムスタイルを設定
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // デフォルトスタイルをベースにカスタムスタイルを作成
    QStyle *baseStyle = QApplication::style();
    MyCustomStyle *customStyle = new MyCustomStyle(baseStyle);
    a.setStyle(customStyle); // アプリケーション全体に適用

    // ... ウィジェットの作成と表示 ...

    return a.exec();
}
  • drawControl() もオーバーライドできますが、これはより広範囲のコントロールの描画を制御します。もし CE_CheckBox をオーバーライドする場合、インジケーター、ラベル、フォーカス矩形などのサブ要素の描画も自分で管理する必要があることに注意してください。これは非常に複雑になる可能性があります。
  • drawPrimitive() をオーバーライドして、PE_IndicatorCheckBox(チェックボックスのインジケーター)の描画を変更しています。ここでは、チェック状態に応じて背景色を変えています。
  • MyCustomStyle クラスが QProxyStyle を継承しています。コンストラクタでベースとなるスタイル(通常は QApplication::style() で取得できる現在のネイティブスタイル)を渡します。
  • アプリケーション全体で統一された、既存のネイティブスタイルに部分的に変更を加えるような、非常に高度なカスタマイズが必要な場合は、QStyle のサブクラス化(特に QProxyStyle)が適しています。
  • QSSで実現できないような、ピクセル単位で完全にカスタムな描画や、複雑なアニメーション、独自の描画アルゴリズムが必要な場合にのみ、paintEvent() をオーバーライドすることを検討してください。
  • ほとんどのケースでは、Qt Style Sheets (QSS) を使うべきです。 背景色、ボーダー、画像、フォント、パディング、マージンなど、見た目の変更の多くはQSSで簡単に実現できます。paintEvent() のような低レベルの描画コードを書く必要がなく、UIとロジックが分離されるため、メンテナンス性も高まります。