Model and View
Qt에서는 다음 그림에서 보는 것과 같이 표와 같은 위젯에 데이터를 표시하기 위한 위젯으로 QListWidget, QTableWidget, QTreeWidget, QListView, QTableView, QTreeView, QColumnView 등 다양한 클래스를 제공한다.
QListWidget 은 QListView와 UI가 동일하다. 하지만 QListWidget 과 QListView 는 데이터를 삽입/수정/삭제 하는데 차이가 있다.
클래스 이름의 마지막에 View 대신, Widget 이라는 단어를 사용한 클래스들은 아래 그림에서 보는 것과 같이 데이터를 직접 삽입/수정/삭제 할 수 있는 멤버 함수를 제공한다.
클래스 이름 마지막에 Widget 이라는 단어가 쓰인 위젯 클래스들은 모두 직접 데이터를 삽입/수정/삭제 가 가능한 멤버 함수를 제공한다.
예를 들어 QListWidget 위젯 클래스는 insertItem( ) 함수를 이용해 데이터를 삽입할 수 있다.
하지만 각 위젯 클래스들은 사용방법이 다소 차이가 있으므로 각각의 위젯 클래스들의 사용 방법을 익혀야 한다.
그리고 QListView, QTableView, QTreeView 클래스와 같이 마지막에 View 라는 단어를 사용하는 위젯 클래스들은 각각의 멤버 함수를 사용해 데이터를 삽입/수정/삭제 하지않고 Model 클래스라는 매개체를 이용해 데이터를 삽입/수정/삭제 할 수 있다.
ListView, QTableView, QTreeView 클래스 들은 위젯에 데이터를 삽입/수정/삭제 하기 위해 각 클래스에서 제공하는 멤버 함수를 사용하지 않고 Model 클래스를 사용한다.
이 방식을 사용할 경우 QListView 를 사용하든지 QTableView 를 사용하든지 동일한 Model을 사용할 수 있다는 장점을 가지고 있다.
Qt 에서 제공하는 Model/View 는 다음 그림에서 보는 것과 같이 Delegate를 이용해 데이터를 핸들링 할 수 있다.
Model 클래스는 데이터를 관리(삽입/수정/삭제) 하기 위한 기능을 제공한다. 예를 들어 QSqlQueryModel 클래스를 이용하면 SQL 문을 직접 쿼리(QUERY) 할 수 있는 멤버 함수를 제공한다.
별도의 데이터베이스 쿼리로 가져온 데이터를 편집 후에 Model을 이용해도 되지만 QSqlQueryModel 클래스를 이용하면 직접 데이터를 삽입할 수 있다.
QTreeView 와 QListView 클래스를 이용한 예제
<widget.h>
#ifndef WIDGET_H // 헤더 중복 포함 방지 시작
#define WIDGET_H
#include <QApplication> // QApplication 클래스 포함
#include <QVBoxLayout> // QVBoxLayout 클래스 포함
#include <QSplitter> // QSplitter 클래스 포함
#include <QFileSystemModel>// QFileSystemModel 클래스 포함
#include <QTreeView> // QTreeView 클래스 포함
#include <QListView> // QListView 클래스 포함
// Widget 클래스 정의 (QWidget을 상속받음)
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr); // 생성자, 부모 위젯을 선택적으로 받을 수 있음
~Widget(); // 소멸자
};
#endif // WIDGET_H // 헤더 중복 포함 방지 끝
<widget.cpp>
#include "widget.h"
// Widget 클래스 생성자 정의, 부모 위젯을 선택적으로 받음
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// 위젯 크기 설정 (너비 600, 높이 300)
resize(600, 300);
// QSplitter 위젯 생성, 부모는 현재 Widget
QSplitter *splitter = new QSplitter(this);
// 파일 시스템 모델 생성 (파일 및 디렉터리 구조를 표시하기 위한 모델)
QFileSystemModel *model = new QFileSystemModel;
// 모델의 루트 경로를 현재 디렉터리로 설정
model->setRootPath(QDir::currentPath());
// 트리 뷰 생성, splitter의 자식으로 설정
QTreeView *tree = new QTreeView(splitter);
// 트리 뷰에 파일 시스템 모델 설정
tree->setModel(model);
// 트리 뷰의 루트 인덱스를 현재 디렉터리로 설정
tree->setRootIndex(model->index(QDir::currentPath()));
// 리스트 뷰 생성, splitter의 자식으로 설정
QListView *list = new QListView(splitter);
// 리스트 뷰에 파일 시스템 모델 설정
list->setModel(model);
// 리스트 뷰의 루트 인덱스를 현재 디렉터리로 설정
list->setRootIndex(model->index(QDir::currentPath()));
// 수직 박스 레이아웃 생성
QVBoxLayout *layout = new QVBoxLayout();
// 레이아웃에 splitter 추가
layout->addWidget(splitter);
// 현재 위젯에 레이아웃 설정
setLayout(layout);
}
// Widget 클래스 소멸자 정의
Widget::~Widget()
{
}
QListView 클래스를 이용한 예제
<widget.cpp>
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
resize(600, 300);
QStringList strList;
strList << "Monday" << "Tuesday" << "Wedneday"
<< "Thurday" << "Friday";
QAbstractItemModel *model = new QStringListModel(strList);
QListView *view = new QListView();
view->setModel(model);
QModelIndex index = model->index(3, 0);
QString text = model->data(index, Qt::DisplayRole).toString();
QLabel *lbl = new QLabel("");
lbl->setText(text);
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(view);
lay->addWidget(lbl);
setLayout(lay);
}
Widget::~Widget()
{
}
QTableView 클래스를 이용한 예제
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QAbstractItemModel>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QVBoxLayout>
#include <QDateTime>
#include <QTableView>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
};
#endif // WIDGET_H
<widget.cpp>
#include "widget.h"
#include <QDate>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QStandardItemModel *model = new QStandardItemModel(0, 3);
model->setHeaderData(0, Qt::Horizontal, QObject::tr("Subject"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Description"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Date"));
model->setVerticalHeaderItem(0, new QStandardItem("Col 1"));
model->setVerticalHeaderItem(1, new QStandardItem("Col 2"));
model->setData(model->index(0, 0), "Monitor");
model->setData(model->index(0, 1), "LCD");
model->setData(model->index(0, 2), QDate(2030, 10, 4));
model->setData(model->index(1, 0), "CPU");
model->setData(model->index(1, 1), "Samsung");
model->setData(model->index(1, 2), QDate(2030, 10, 4));
QTableView *table = new QTableView();
table->setModel(model);
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(table);
setLayout(lay);
}
Widget::~Widget()
{
}
QTableWidget 예제
<checkboxgeader.h>
#ifndef CHECKBOXHEADER_H
#define CHECKBOXHEADER_H
#include <QHeaderView>
#include <QObject>
#include <QPainter>
#include <QMouseEvent>
class CheckBoxHeader : public QHeaderView
{
Q_OBJECT
public:
CheckBoxHeader(Qt::Orientation orientation,
QWidget* parent = nullptr);
bool isChecked() const { return isChecked_; }
void setIsChecked(bool val);
signals:
void checkBoxClicked(bool state);
protected:
void paintSection(QPainter* painter,
const QRect& rect,
int logicalIndex) const;
void mousePressEvent(QMouseEvent* event);
private:
bool isChecked_;
void redrawCheckBox();
};
#endif // CHECKBOXHEADER_H
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QHeaderView>
#include <QPainter>
#include <QCheckBox>
#include <QDebug>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
public slots:
void checkBoxClicked(bool state);
};
#endif // WIDGET_H
<chekboxheader.cpp>
#include "checkboxheader.h"
CheckBoxHeader::CheckBoxHeader(Qt::Orientation orientation,
QWidget* parent)
: QHeaderView(orientation, parent)
{
isChecked_ = true;
}
void CheckBoxHeader::paintSection(QPainter* painter,
const QRect& rect,
int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
if (logicalIndex == 0)
{
QStyleOptionButton option;
option.rect = QRect(1,3,20,20);
option.state = QStyle::State_Enabled | QStyle::State_Active;
if (isChecked_)
option.state |= QStyle::State_On;
else
option.state |= QStyle::State_Off;
option.state |= QStyle::State_Off;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox,
&option,
painter);
}
}
void CheckBoxHeader::mousePressEvent(QMouseEvent* event)
{
Q_UNUSED(event)
setIsChecked(!isChecked());
emit checkBoxClicked(isChecked());
}
void CheckBoxHeader::redrawCheckBox()
{
viewport()->update();
}
void CheckBoxHeader::setIsChecked(bool val)
{
if (isChecked_ != val)
{
isChecked_ = val;
redrawCheckBox();
}
}
<widget.cpp>
#include "widget.h"
#include "ui_widget.h"
#include "checkboxheader.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->tableWidget->setRowCount(5);
ui->tableWidget->setColumnCount(2);
CheckBoxHeader* header = new CheckBoxHeader(Qt::Horizontal, ui->tableWidget);
ui->tableWidget->setHorizontalHeader(header);
connect(header, &CheckBoxHeader::checkBoxClicked,
this, &Widget::checkBoxClicked);
QStringList nameList;
nameList << "Notebook" << "Mobile" << "Desktop" << "Keyboard" << "Monitor";
for(int i = 0; i < 5 ; i++)
{
ui->tableWidget->insertRow(i);
QTableWidgetItem *dateItem = new QTableWidgetItem("2025.05.07");
dateItem->setCheckState(Qt::Checked);
ui->tableWidget->setItem(i,0, dateItem);
ui->tableWidget->setItem(i,1, new QTableWidgetItem(nameList.at(i)));
}
}
void Widget::checkBoxClicked(bool state)
{
for(int i = 0 ; i < 5 ; i++)
{
QTableWidgetItem *item = ui->tableWidget->item(i, 0);
if(state)
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
}
Widget::~Widget()
{
delete ui;
}
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}