QTableView의 헤더 영역에 체크박스를 배치해야 할 때, Qt는 기본 API를 제공하지 않는다. 이 글에서는 QHeaderView를 커스터마이징하여 체크박스를 추가하는 세 가지 방식을 살펴본다.
방식 1: paintSection 재정의
가장 일반적인 접근법은 paintSection와 mousePressEvent를 오버라이드하는 것이다.
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이 적합하다.