취미생활
[C++11 Qt] QTableView 로 틀 고정 기능 구현하기 본문
아래는 깃허브 주소
작년에 프로젝트 투입되면서 틀 고정 기능이 필요해서 구현할 일이 있었는데,
그때 구현해뒀다가 성능 이슈 때문에 사용하지 못한 코드다.
당시에는 QTreeWidget을 사용하면서 각 셀마다 paintEvent를 override한 QObject를 올려두어서 그런지
해당 기능을 사용했을 때 굉장히 느려지는 현상이 발생했다.
다행히 다른 방식으로 해결했지만,
그때 사용하지 못한 코드를 올려보려고 한다.
나는 쓸 일이 없었지만,
누군가에겐 꼭 도움이 되었으면 좋겠다.
구조
Qt는 MVC 구조를 가지고 있다.
각 이니셜은 Model-View-Controller 를 뜻 하는데,
여기서 우리가 주목할 점은 Model, View 부분이다.
Model은 데이터를 실제로 가지고 있는 데이터 영역이고 View 는 이를 UI로 뿌려주는 역할이니
우리가 만약 틀 고정을 해야 한다면 공통으로 사용할 Model 한 개와 2 개의 Table View 가 필요하다.
한 개는 고정될 틀이 되어 줄 TableView, 나머지는 고정되지 않은 Column을 보여 줄 TableView가 된다.
코드 설명
코드 전문은 위 gtihub에 있다.
#ifndef GRAPHTREE_H
#define GRAPHTREE_H
#include <QTableView>
#include <QDebug>
class frozenTableView : public QTableView
{
public:
explicit frozenTableView(QWidget *parent = 0);
~frozenTableView();
// setter/getter
void setModel(QAbstractItemModel *model);
int lastFrozenColumn() const;
void setLastFrozenColumn(int lastFrozenColumn);
protected:
void resizeEvent(QResizeEvent *event) override;
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
void paintEvent(QPaintEvent *event);
private:
QTableView *m_frozenView;
void initFrozenView();
void updateFrozenViewGeometry();
void updateFrozenView();
void updateFrozenColumn();
private slots:
void updateSectionWidth(int logicalIndex, int oldSize, int newSize);
int m_lastFrozenColumn;
};
#endif // GRAPHTREE_H
헤더 전문이다.
QTableView를 상속받은 frozenTableView 클래스이다.
멤버 변수로 틀 고정에 사용할 TableView 객체 포인터를 갖는다.
참고로 TableView 포인터는 생성자에서 메모리를 할당해준다.
많은 걸 볼 필요 없고 아래 함수 4개만 보면 된다.
initFrozenView, updateFrozenViewGeometry, updateFrozenView, updateFrozenColumn
void frozenTableView::initFrozenView()
{
if(model() == nullptr)
{
qDebug() << QString("[%1:%2] model is not added").arg(__func__).arg(__LINE__);
}
// disable frozen table scroll bar
QHeaderView *header = m_frozenView->horizontalHeader();
m_frozenView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_frozenView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_frozenView->verticalHeader()->hide();
//set model
m_frozenView->setModel(model());
m_frozenView->setSelectionModel(selectionModel());
//disable focus, resize, selection
m_frozenView->setFocusPolicy(Qt::NoFocus);
header->setSectionResizeMode(QHeaderView::Fixed);
header->setSelectionMode(QAbstractItemView::NoSelection);
viewport()->stackUnder(m_frozenView);
m_frozenView->setStyleSheet("QTableView {background-color: #8EDE21;"
"selection-background-color: #22DE21;"
"gridline-color : gray;"
"border : 1px;}"
"QTableView::item {height : 50px;}");
//disable moveable, clickable, auto scroll
header->setSectionsMovable(false);
header->setSectionsClickable(false);
setAutoScroll(false);
m_frozenView->setSelectionBehavior(QAbstractItemView::SelectRows);
//set scrollMode
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
m_frozenView->setVerticalScrollMode(ScrollPerPixel);
updateFrozenViewGeometry();
updateFrozenColumn();
}
initFrozenView 함수는 frozenView property를 설정해주는 함수다.
아무래도 UI위에 새로운 UI를 그리는 것이기 때문에 설정 값을 하드픽스 해줘야 했다.
이중에서도 중요한 건 아래 두 줄이었다.
m_frozenView->setModel(model());
m_frozenView->setSelectionModel(selectionModel());
현재 frozenTableView에서 사용 중인 model 을 멤버 객체인 m_frozenView 에서도 같이 사용하겠다는 의미다.
model() 함수는 현재 frozenTableView 에서 사용 중인 model 객체의 포인터를 리턴하고
이 값을 다시 m_frozenView의 model 로 설정해준다.
selectionModel도 동일하게 설정한다.
void frozenTableView::updateFrozenViewGeometry()
{
const int x = frameWidth();
const int y = frameWidth();
int w = 0;
int h = viewport()->height() + horizontalHeader()->height();
for(int col = 0; col < m_lastFrozenColumn; ++col)
{
w += m_frozenView->columnWidth(col);
}
m_frozenView->setGeometry(x, y, w, h);
}
updateFrozenViewGeometry 함수는 현재 frozenTableView 객체의 x, y 값을 가져와 멤버 객체 m_frozenView에 적용해준다.
이 코드가 필요한 이유는 Qt 내부에서 frozenTableView 의 위치가 변경될 수 있기 때문이다.
이중에서도 주목해야 할 점은 w 인데,
마지막 frozenColumn index 까지 각 Column의 width를 더해서 계산해줘야 한다.
그리고 왜 했는 지 기억 안나는 무책임한 코드도 있는데,
h 값 계산에서 viewprot()->height를 왜 더했었는 지 기억이 잘 안난다.
이게 아마 frozenTable 레퍼런스에 있었던 것 같은데 확인을 한 번 해봐야 할 듯
void frozenTableView::updateFrozenColumn()
{
for(int col = 0; col < model()->columnCount(); ++col)
{
if(col < m_lastFrozenColumn) m_frozenView->setColumnHidden(col, false);
else m_frozenView->setColumnHidden(col, true);
}
if(m_lastFrozenColumn > 0) m_frozenView->show();
}
updateFrozenColumn 함수는 멤버 변수인 m_lastFrozenColumn 값에 따라
m_frozenView 의 Column 을 hide/show 하는 함수다.
원래는 Initialize 함수에서 실행하도록 만든 기능이었는데,
m_lastFrozenColumn 변수를 고정 값에서 멤버 변수로 분리하면서 추가했다.
void frozenTableView::updateFrozenView()
{
updateFrozenColumn();
updateFrozenViewGeometry();
}
updateFrozenView 함수는 updateFrozenColumn, updateFrozenViewGeometry를 실행해주는 함수다.
필수 함수는 아니었는데 정리하다보니 이게 더 쓰기 편할 것 같아서 추가했다.
물론 지워도 상관은 없다.
참고로 이 함수는 paintEvent 함수에서 실행된다.
3. 후기
되게 간단한 기능이긴 한데
처음 개발할 때는 이런 구조에 대한 이해가 없어서 조금 애먹었다.
참고 자료 중에 파이썬으로 구현한 코드도 있어서 참고했었는데 레퍼런스에 넣어둘 테니
Python 으로 Pyqt 구현 중인 사람들은 참고하면 좋을 듯
레퍼런스
Qt 공식 레퍼런스
https://doc.qt.io/qt-6/qtwidgets-itemviews-frozencolumn-example.html
PyQt 구현 글
https://zbaekhk.blogspot.com/2021/02/pyqt5-qtableview-frozen-columns.html
'컴퓨터 > C++' 카테고리의 다른 글
[C++] 람다 함수로 std::thread 돌리기 (0) | 2023.03.29 |
---|---|
[C++] Map 과 함수 포인터를 이용한 함수 호출 아이디어 (2) | 2022.11.16 |
[C++11] std::function 템플릿으로 함수 포인터 대체하기 (0) | 2022.11.16 |
[C++11] 중괄호로 STL 자료구조 초기화 간편하게 하기 (0) | 2022.11.16 |
[C++] 구조체 비트 필드로 비트 단위 변수 사용하기 (0) | 2022.09.05 |