Qt QHeaderView에 체크박스 구현하기

QTableView의 헤더 영역에 체크박스를 배치해야 할 때, Qt는 기본 API를 제공하지 않는다. 이 글에서는 QHeaderView를 커스터마이징하여 체크박스를 추가하는 세 가지 방식을 살펴본다.

방식 1: paintSection 재정의

가장 일반적인 접근법은 paintSectionmousePressEvent를 오버라이드하는 것이다.

CustomHeader.h

#ifndef CUSTOM_HEADER_H
#define CUSTOM_HEADER_H

#include <QHeaderView>
#include <QPainter>
#include <QMouseEvent>

class CustomHeader : public QHeaderView
{
    Q_OBJECT
public:
    explicit CustomHeader(Qt::Orientation direction, QWidget* container = nullptr);

signals:
    void toggled(bool checked);

protected:
    void paintSection(QPainter* drawer, const QRect& area, int sectionIdx) const override;
    void mousePressEvent(QMouseEvent* evt) override;

private:
    bool checkedStatus;
    mutable QRect hitTarget;
};

#endif

CustomHeader.cpp

#include "CustomHeader.h"

CustomHeader::CustomHeader(Qt::Orientation direction, QWidget* container)
    : QHeaderView(direction, container)
    , checkedStatus(false)
{
}

void CustomHeader::paintSection(QPainter* drawer, const QRect& area, int sectionIdx) const
{
    drawer->save();
    QHeaderView::paintSection(drawer, area, sectionIdx);
    drawer->restore();

    if (sectionIdx != 0) return;

    QStyleOptionButton cfg;
    cfg.rect = area;
    cfg.state = checkedStatus ? QStyle::State_On : QStyle::State_Off;

    hitTarget = area;
    style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &cfg, drawer);
}

void CustomHeader::mousePressEvent(QMouseEvent* evt)
{
    if (hitTarget.contains(evt->pos())) {
        checkedStatus = !checkedStatus;
        emit toggled(checkedStatus);
        viewport()->update();
    }
    QHeaderView::mousePressEvent(evt);
}

핵심 포인트:

  • paintSection는 각 섹션별 그리기 담당
  • sectionIdx == 0으로 첫 번째 열에만 체크박스 표시
  • QStyle::PE_IndicatorCheckBox로 시스템 스타일의 체크박스 렌더링
  • 클릭 영역 판별을 위해 hitTarget 저장

방식 2: 독립 위젯 배치

실제 QCheckBox 위젯을 헤더 내부에 배치하는 방법이다.

class WidgetHeader : public QHeaderView
{
    Q_OBJECT
public:
    explicit WidgetHeader(Qt::Orientation dir, QWidget* parent = nullptr)
        : QHeaderView(dir, parent)
    {
        selector = new QCheckBox("전체선택", this);
        connect(selector, &QCheckBox::clicked, this, &WidgetHeader::stateChanged);
    }

protected:
    void updateGeometries() override
    {
        selector->move(sectionPosition(0) + 20, 5);
    }

private:
    QCheckBox* selector;
};

문제점: 수평 스크롤 시 체크박스가 고정되지 않고 따라오는 현상 발생. 스크롤 영역 밖으로 나간 컬럼 위에 계속 표시된다.

방식 3: 커스텀 아이콘과 텍스트 조합

방식 1을 개선하여 직접 그린 아이콘과 텍스트를 조합한다.

void CustomHeader::paintSection(QPainter* drawer, const QRect& area, int sectionIdx) const
{
    drawer->save();
    QHeaderView::paintSection(drawer, area, sectionIdx);
    drawer->restore();

    if (sectionIdx != 0) return;

    QStyleOptionButton cfg;
    cfg.iconSize = QSize(16, 16);
    cfg.text = "전체선택";
    cfg.palette.setBrush(QPalette::WindowText, Qt::white);

    QFont ft("Microsoft YaHei", 14);
    drawer->setFont(ft);

    cfg.rect = QRect(area.x() + 20, area.y(), area.width(), area.height());

    if (checkedStatus) {
        cfg.state = QStyle::State_On;
        cfg.icon = QIcon(":/icons/checked.svg");
    } else {
        cfg.state = QStyle::State_Off;
        cfg.icon = QIcon(":/icons/unchecked.svg");
    }

    style()->drawControl(QStyle::CE_CheckBoxLabel, &cfg, drawer);
}

차이점:

  • QStyle::CE_CheckBoxLabel 사용으로 아이콘과 텍스트 동시 렌더링
  • 커스텀 아이콘으로 체크/언체크 상태 시각화
  • 폰트와 색상 직접 지정 가능

방식 비교

기준paintSection위젯 배치커스텀 아이콘
반응 속도빠름지연 있음빠름
스크롤 대응정상문제 발생정상
스타일 커스터마이징제한적자유로움자유로움
구현 복잡도중간간단중간

실무에서는 방식 1 또는 3을 권장하며, 특히 커스텀 아이콘이 필요한 경우 방식 3이 적합하다.

태그: Qt QHeaderView QTableView QStyle QPainter

6월 20일 23:45에 게시됨