모델/뷰 튜토리얼
모든 UI 개발자는 모델뷰 프로그래밍에 대해 알아야 하며, 이 튜토리얼의 목표는 이 주제에 대해 쉽게 이해할 수 있도록 소개하는 것입니다.
표, 목록 및 트리 위젯은 GUI에서 자주 사용되는 구성 요소입니다. 이러한 위젯이 데이터에 액세스하는 방법에는 두 가지가 있습니다. 전통적인 방식은 데이터를 저장하는 내부 컨테이너를 포함하는 위젯을 사용하는 것입니다. 이 접근 방식은 매우 직관적이지만 사소하지 않은 많은 애플리케이션에서 데이터 동기화 문제가 발생합니다. 두 번째 접근 방식은 모델/뷰 프로그래밍으로, 위젯이 내부 데이터 컨테이너를 유지하지 않습니다. 위젯은 표준화된 인터페이스를 통해 외부 데이터에 액세스하므로 데이터 중복을 방지할 수 있습니다. 처음에는 복잡해 보일 수 있지만 자세히 살펴보면 이해하기 쉬울 뿐만 아니라 모델/뷰 프로그래밍의 많은 이점도 더 명확해집니다.
이 과정에서 다음과 같은 Qt가 제공하는 몇 가지 기본 기술에 대해 배우게 됩니다:
- 표준 위젯과 모델/뷰 위젯의 차이점
- 폼과 모델 사이의 어댑터
- 간단한 모델/뷰 애플리케이션 개발하기
- 사전 정의된 모델
- 다음과 같은 중급 주제
- 트리 보기
- 선택
- 델리게이트
- 모델 테스트를 사용한 디버깅
또한 모델/뷰 프로그래밍으로 새 애플리케이션을 더 쉽게 작성할 수 있는지 또는 기존 위젯도 잘 작동하는지 알아볼 수 있습니다.
이 튜토리얼에는 편집하여 프로젝트에 통합할 수 있는 예제 코드가 포함되어 있습니다. 튜토리얼의 소스 코드는 Qt의 examples/widgets/tutorials/modelview 디렉토리에 있습니다.
자세한 내용은 참조 문서를 참조하세요.
1. 소개
모델/뷰는 데이터 세트를 처리하는 위젯에서 데이터를 뷰에서 분리하는 데 사용되는 기술입니다. 표준 위젯은 데이터와 뷰를 분리하도록 설계되지 않았기 때문에 Qt에는 두 가지 유형의 위젯이 있습니다. 두 가지 유형의 위젯은 동일하게 보이지만 데이터와 상호 작용하는 방식은 다릅니다.
표준 위젯은 위젯의 일부인 데이터를 사용합니다. | |
뷰 클래스는 외부 데이터(모델)에서 작동합니다. |
1.1 표준 위젯
표준 표 위젯에 대해 자세히 살펴보겠습니다. 표 위젯은 사용자가 변경할 수 있는 데이터 요소의 2D 배열입니다. 표 위젯이 제공하는 데이터 요소를 읽고 쓰는 방식으로 표 위젯을 프로그램 흐름에 통합할 수 있습니다. 이 방법은 매우 직관적이고 많은 애플리케이션에서 유용하지만, 표준 표 위젯으로 데이터베이스 표를 표시하고 편집하는 것은 문제가 될 수 있습니다. 하나는 위젯 외부에, 다른 하나는 위젯 내부에 두 개의 데이터 복사본을 조정해야 합니다. 개발자는 두 버전을 모두 동기화할 책임이 있습니다. 이 외에도 프레젠테이션과 데이터의 긴밀한 결합으로 인해 단위 테스트를 작성하기가 더 어려워집니다.
1.2 모델/보기로 구조하기
모델/뷰는 보다 다양한 아키텍처를 사용하는 솔루션을 제공하기 위해 한 단계 더 발전했습니다. 모델/뷰는 표준 위젯에서 발생할 수 있는 데이터 일관성 문제를 제거합니다. 또한 모델/뷰를 사용하면 하나의 모델을 여러 뷰에 전달할 수 있으므로 동일한 데이터에 대해 두 개 이상의 뷰를 더 쉽게 사용할 수 있습니다. 가장 중요한 차이점은 모델/뷰 위젯은 테이블 셀 뒤에 데이터를 저장하지 않는다는 점입니다. 실제로는 데이터에서 직접 작동합니다. 뷰 클래스는 데이터의 구조를 알지 못하므로 데이터를 QAbstractItemModel 인터페이스에 맞게 만들기 위해 래퍼를 제공해야 합니다. 뷰는 이 인터페이스를 사용하여 데이터를 읽고 씁니다. QAbstractItemModel 을 구현하는 클래스의 모든 인스턴스를 모델이라고 합니다. 뷰가 모델에 대한 포인터를 받으면 해당 콘텐츠를 읽고 표시하며 편집기가 됩니다.
1.3 모델/뷰 위젯 개요
다음은 모델/뷰 위젯과 그에 해당하는 표준 위젯에 대한 개요입니다.
위젯 | 표준 위젯 (항목 기반 편의 클래스) | 모델/뷰 보기 클래스 (외부 데이터와 함께 사용) |
---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | QColumnView 트리를 목록의 계층 구조로 표시합니다. | |
![]() | QComboBox 뷰 클래스와 기존 위젯으로 모두 작동할 수 있습니다. |
1.4 폼과 모델 간에 어댑터 사용
양식과 모델 사이에 어댑터를 사용하면 유용할 수 있습니다.
테이블에 저장된 데이터를 테이블 자체 내에서 직접 편집할 수도 있지만 텍스트 필드에 있는 데이터를 편집하는 것이 훨씬 더 편합니다. 데이터 집합이 아닌 하나의 값(QLineEdit, QCheckBox...)으로 작동하는 위젯의 데이터와 뷰를 분리하는 직접적인 모델/뷰 대응이 없으므로 양식을 데이터 소스에 연결하려면 어댑터가 필요합니다.
QDataWidgetMapper 는 양식 위젯을 테이블 행에 매핑하고 데이터베이스 테이블용 양식을 매우 쉽게 작성할 수 있는 훌륭한 솔루션입니다.
어댑터의 또 다른 예는 QCompleter 입니다. Qt에는 QComboBox 과 같은 Qt 위젯에 자동 완성을 제공하기 위한 QCompleter 과 아래 그림과 같이 QLineEdit 이 있습니다. QCompleter 은 데이터 소스로 모델을 사용합니다.
2. 간단한 모델/뷰 애플리케이션
모델/뷰 애플리케이션을 개발하려면 어디서부터 시작해야 할까요? 간단한 예제부터 시작하여 단계별로 확장하는 것이 좋습니다. 이렇게 하면 아키텍처를 훨씬 쉽게 이해할 수 있습니다. 많은 개발자가 IDE를 호출하기 전에 모델/뷰 아키텍처를 자세히 이해하려고 시도하는 것은 편리하지 않은 것으로 입증되었습니다. 데모 데이터가 있는 간단한 모델/뷰 애플리케이션으로 시작하는 것이 훨씬 더 쉽습니다. 한 번 시도해 보세요! 아래 예제의 데이터를 자신의 데이터로 바꾸기만 하면 됩니다.
아래는 모델/뷰 프로그래밍의 다양한 측면을 보여주는 매우 간단하고 독립적인 7가지 애플리케이션입니다. 소스 코드는 examples/widgets/tutorials/modelview
디렉터리에서 찾을 수 있습니다.
2.1 읽기 전용 테이블
QTableView 을 사용하여 데이터를 표시하는 애플리케이션부터 시작하겠습니다. 나중에 편집 기능을 추가하겠습니다.
(파일 소스: 예제/위젯/튜토리얼/모델뷰/1_readonly/main.cpp)
// main.cpp #include <QApplication> #include <QTableView> #include "mymodel.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QTableView tableView; MyModel myModel; tableView.setModel(&myModel); tableView.show(); return a.exec(); }
일반적인 main() 함수가 있습니다:
여기 흥미로운 부분이 있습니다: MyModel의 인스턴스를 생성하고 tableView.setModel(&myModel)을 사용하여 tableView 로 포인터를 전달합니다. tableView 는 받은 포인터의 메서드를 호출하여 두 가지를 찾습니다:
- 얼마나 많은 행과 열을 표시해야 하는지.
- 각 셀에 어떤 콘텐츠를 인쇄해야 하는지.
모델은 이에 응답하기 위해 몇 가지 코드가 필요합니다.
테이블 데이터 집합이 있으므로 보다 일반적인 QAbstractItemModel 보다 사용하기 쉬운 QAbstractTableModel 으로 시작하겠습니다.
(파일 출처: examples/widgets/tutorials/modelview/1_readonly/mymodel.h)
// mymodel.h #include <QAbstractTableModel> class MyModel : public QAbstractTableModel { Q_OBJECT public: explicit MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; };
QAbstractTableModel 는 세 가지 추상 메서드를 구현해야 합니다.
(파일 출처: examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)
// mymodel.cpp #include "mymodel.h" MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) { } int MyModel::rowCount(const QModelIndex & /*parent*/) const { return 2; } int MyModel::columnCount(const QModelIndex & /*parent*/) const { return 3; } QVariant MyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) return QString("Row%1, Column%2") .arg(index.row() + 1) .arg(index.column() +1); return QVariant(); }
행과 열의 수는 MyModel::rowCount() 및 MyModel::columnCount()에서 제공합니다. 뷰에서 셀의 텍스트가 무엇인지 알아야 하는 경우 MyModel::data() 메서드를 호출합니다. 행 및 열 정보는 매개 변수 index
로 지정되고 역할은 Qt::DisplayRole 으로 설정됩니다. 다른 역할은 다음 섹션에서 다룹니다. 이 예제에서는 표시되어야 하는 데이터가 생성됩니다. 실제 애플리케이션에서 MyModel
에는 MyData
이라는 멤버가 있으며, 이 멤버는 모든 읽기 및 쓰기 작업의 대상이 됩니다.
이 작은 예제는 모델의 수동적인 특성을 보여줍니다. 모델은 언제 사용될지 또는 어떤 데이터가 필요한지 알지 못합니다. 그저 뷰가 데이터를 요청할 때마다 데이터를 제공할 뿐입니다.
모델의 데이터를 변경해야 하는 경우 어떻게 될까요? 뷰는 데이터가 변경되어 다시 읽어야 한다는 것을 어떻게 인식할까요? 모델은 변경된 셀 범위를 나타내는 신호를 내보내야 합니다. 이는 섹션 2.3에서 설명합니다.
2.2 역할로 읽기 전용 예제 확장하기
모델은 뷰에 표시되는 텍스트를 제어할 뿐만 아니라 텍스트의 모양도 제어합니다. 모델을 약간 변경하면 다음과 같은 결과를 얻을 수 있습니다:
실제로 글꼴, 배경색, 정렬 및 확인란을 설정하기 위해 data() 메서드를 제외하고는 아무것도 변경할 필요가 없습니다. 아래는 위와 같은 결과를 생성하는 data() 메서드입니다. 차이점은 이번에는 매개변수 int 역할을 사용하여 값에 따라 다른 정보를 반환한다는 것입니다.
(파일 출처: 예시/위젯/튜토리얼/모델뷰/2_포맷팅/마이모델.cpp)
// mymodel.cppQVariant MyModel::data(const QModelIndex &index, int role) const{ int row = index.row(); int col = index.column(); // 이 메서드가 호출될 때 로그 메시지를 생성합니다. qDebug() << QString("row %1, col%2, role %3") .arg(row).arg(col).arg(role); switch (role) { case Qt::DisplayRole: if (row== 0 && col== 1) return QString("<--left"); if (row== 1 && col== 1) return QString("right-->"); return QString("행%1, 열%2") .arg(행 + 1) .arg(열+1); case Qt::FontRole: if (row== 0 && col== 0) { // cell(0,0)에 대해서만 폰트 변경 QFont boldFont; boldFont.setBold(true); return boldFont; } break; case Qt::BackgroundRole: if (row== 1 && col== 2) // 셀(1,2)에 대해서만 배경 변경 return QBrush(Qt::red); break; case Qt::TextAlignmentRole: if (row== 1 && col== 1) // 셀(1,1)에 대해서만 텍스트 정렬 변경 return int(Qt::AlignRight | Qt::AlignVCenter); break; case Qt::CheckStateRole: if (row== 1 && col== 0) // 셀(1,0)에 체크박스 추가 return Qt::Checked; break; } return QVariant(); }
각 서식 지정 속성은 data() 메서드를 별도로 호출하여 모델에서 요청합니다. role
매개변수는 모델에 어떤 속성이 요청되고 있는지 알려주는 데 사용됩니다:
enum Qt::ItemDataRole | 의미 | Type |
---|---|---|
Qt::DisplayRole | text | QString |
Qt::FontRole | font | QFont |
BackgroundRole | 셀의 배경을 위한 브러시 | QBrush |
Qt::TextAlignmentRole | 텍스트 정렬 | enum Qt::AlignmentFlag |
Qt::CheckStateRole | 로 체크박스를 억제합니다 QVariant(), 로 체크박스를 설정하고 Qt::Checked | enum Qt::ItemDataRole |
Qt::ItemDataRole 열거형의 기능에 대한 자세한 내용은 Qt 이름 공간 설명서를 참조하십시오.
이제 분리된 모델을 사용하는 것이 애플리케이션의 성능에 어떤 영향을 미치는지 확인해야 하므로 뷰가 data() 메서드를 얼마나 자주 호출하는지 추적해 보겠습니다. 뷰가 모델을 호출하는 빈도를 추적하기 위해 data() 메서드에 오류 출력 스트림에 로그하는 디버그 문을 넣었습니다. 작은 예제에서는 data()가 42번 호출됩니다. 필드 위에 커서를 놓을 때마다 data()가 각 셀에 대해 7번씩 다시 호출됩니다. 따라서 data()가 호출될 때 데이터를 사용할 수 있는지 확인하고 값비싼 조회 연산을 캐시하는 것이 중요합니다.
2.3 테이블 셀 내부의 시계
여전히 읽기 전용 테이블이 있지만 이번에는 현재 시간을 표시하기 때문에 콘텐츠가 매초마다 변경됩니다.
(파일 출처: examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
QVariant MyModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); if (role == Qt::DisplayRole && row == 0 && col == 0) return QTime::currentTime().toString(); return QVariant(); }
시계를 똑딱거리게 하기 위해 뭔가 빠진 것이 있습니다. 매초마다 뷰에 시간이 변경되었으므로 다시 읽어야 한다는 것을 알려야 합니다. 이를 위해 타이머를 사용합니다. 생성자에서 간격을 1초로 설정하고 시간 초과 신호를 연결합니다.
(파일 출처: 예제/위젯/튜토리얼/모델뷰/3_changingmodel/mymodel.cpp)
MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) , timer(new QTimer(this)) { timer->setInterval(1000); connect(timer, &QTimer::timeout , this, &MyModel::timerHit); timer->start(); }
해당 슬롯은 다음과 같습니다:
(파일 출처: examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
void MyModel::timerHit() { // we identify the top left cell QModelIndex topLeft = createIndex(0,0); // emit a signal to make the view reread identified data emit dataChanged(topLeft, topLeft, {Qt::DisplayRole}); }
dataChanged() 신호를 전송하여 뷰에 왼쪽 상단 셀의 데이터를 다시 읽도록 요청합니다. dataChanged () 신호를 뷰에 명시적으로 연결하지 않았다는 점에 유의하세요. setModel () 호출 시 자동으로 발생했습니다.
2.4 열 및 행에 대한 머리글 설정
뷰 메서드를 통해 헤더를 숨길 수 있습니다: tableView->verticalHeader()->hide();
그러나 헤더 콘텐츠는 모델을 통해 설정되므로 headerData() 메서드를 다시 구현합니다:
(파일 소스: 예제/위젯/튜토리얼/모델뷰/4_헤더/mymodel.cpp)
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { switch (section) { case 0: return QString("first"); case 1: return QString("second"); case 2: return QString("third"); } } return QVariant(); }
headerData() 메서드에도 MyModel::data()에서와 같은 의미를 갖는 매개변수 역할이 있습니다.
2.5 최소 편집 예제
이 예제에서는 표 셀에 입력된 값을 반복하여 창 제목을 콘텐츠로 자동 채우는 애플리케이션을 만들어 보겠습니다. 창 제목에 쉽게 액세스할 수 있도록 QMainWindow 에 QTableView 을 넣습니다.
모델은 편집 기능의 사용 가능 여부를 결정합니다. 사용 가능한 편집 기능을 활성화하려면 모델을 수정하기만 하면 됩니다. 이는 setData() 및 flags() 가상 메서드를 다시 구현하여 수행됩니다.
(파일 소스: 예제/위젯/튜토리얼/모델뷰/5_edit/mymodel.h)
// mymodel.h #include <QAbstractTableModel> #include <QString> const int COLS= 3; const int ROWS= 2; class MyModel : public QAbstractTableModel { Q_OBJECT public: MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: QString m_gridData[ROWS][COLS]; //holds text entered into QTableView signals: void editCompleted(const QString &); };
the
2차원 배열 QString m_gridData
을 사용하여 데이터를 저장합니다. 따라서 m_gridData
은 MyModel
의 핵심이 됩니다. 나머지 MyModel
는 래퍼처럼 작동하며 m_gridData
을 QAbstractItemModel 인터페이스에 맞게 조정합니다. 또한 editCompleted()
신호를 도입하여 수정된 텍스트를 창 제목으로 전송할 수 있습니다.
(파일 출처: 예시/위젯/튜토리얼/모델뷰/5_edit/mymodel.cpp)
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { if (!checkIndex(index)) return false; //save value from editor to member m_gridData m_gridData[index.row()][index.column()] = value.toString(); //for presentation purposes only: build and emit a joined string QString result; for (int row = 0; row < ROWS; row++) { for (int col= 0; col < COLS; col++) result += m_gridData[row][col] + ' '; } emit editCompleted(result); return true; } return false; }
setData()는 사용자가 셀을 편집할 때마다 호출됩니다. index
매개변수는 어떤 필드가 편집되었는지 알려주고 value
은 편집 과정의 결과를 제공합니다. 셀에는 텍스트만 포함되어 있으므로 역할은 항상 Qt::EditRole 로 설정됩니다. 확인란이 있고 사용자 권한이 확인란을 선택할 수 있도록 설정된 경우 역할이 Qt::CheckStateRole 으로 설정된 상태로 호출됩니다.
(파일 출처: 예시/위젯/튜토리얼/모델뷰/5_편집/마이모델.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); }
flags()로 셀의 다양한 속성을 조정할 수 있습니다.
Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled 을 반환하면 편집기에 셀을 선택할 수 있음을 표시하는 것으로 충분합니다.
한 셀을 편집하여 특정 셀의 데이터보다 더 많은 데이터를 수정하는 경우 모델에서 dataChanged() 신호를 보내야 변경된 데이터를 읽을 수 있습니다.
3. 중급 주제
3.1 트리뷰
위의 예제를 트리 보기가 있는 애플리케이션으로 변환할 수 있습니다. QTableView 을 QTreeView 으로 바꾸기만 하면 읽기/쓰기 트리가 생성됩니다. 모델을 변경할 필요는 없습니다. 모델 자체에 계층 구조가 없기 때문에 트리에는 계층 구조가 없습니다.
QListView, QTableView 및 QTreeView 모두 목록, 테이블 및 트리가 병합된 모델 추상화를 사용합니다. 따라서 동일한 모델에서 여러 가지 유형의 보기 클래스를 사용할 수 있습니다.
이것이 지금까지 예제 모델의 모습입니다:
실제 트리를 표시하려고 합니다. 모델을 만들기 위해 위의 예제에서 데이터를 래핑했습니다. 이번에는 QAbstractItemModel 을 구현하는 계층적 데이터를 위한 컨테이너인 QStandardItemModel 을 사용합니다. 트리를 표시하려면 QStandardItemModel 을 텍스트, 글꼴, 체크박스 또는 브러시와 같은 항목의 모든 표준 속성을 담을 수 있는 QStandardItem으로 채워야 합니다.
(파일 출처: 예시/위젯/튜토리얼/모델뷰/6_treeview/mainwindow.cpp)
// modelview.cpp #include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QStandardItem> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third"); QStandardItem *item = standardModel->invisibleRootItem(); // adding a row to the invisible root item produces a root element item->appendRow(preparedRow); QList<QStandardItem *> secondRow = prepareRow("111", "222", "333"); // adding a row to an item starts a subtree preparedRow.first()->appendRow(secondRow); treeView->setModel(standardModel); treeView->expandAll(); } QList<QStandardItem *> MainWindow::prepareRow(const QString &first, const QString &second, const QString &third) const { return {new QStandardItem(first), new QStandardItem(second), new QStandardItem(third)}; }
QStandardItemModel 인스턴스를 생성하고 생성자에 QStandardItems 두 개를 추가하기만 하면 됩니다. 그러면 QStandardItem 은 다른 QStandardItems 을 포함할 수 있으므로 계층적 데이터 구조를 만들 수 있습니다. 노드는 뷰 내에서 축소 및 확장됩니다.
3.2 선택 항목으로 작업하기
계층 수준과 함께 창 제목에 출력하기 위해 선택한 항목의 콘텐츠에 액세스하고 싶습니다.
이제 몇 가지 항목을 만들어 보겠습니다:
(파일 소스: 예제/위젯/튜토리얼/모델뷰/7_selections/mainwindow.cpp)
#include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QItemSelectionModel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); auto *rootNode = standardModel->invisibleRootItem(); // defining a couple of items auto *americaItem = new QStandardItem("America"); auto *mexicoItem = new QStandardItem("Canada"); auto *usaItem = new QStandardItem("USA"); auto *bostonItem = new QStandardItem("Boston"); auto *europeItem = new QStandardItem("Europe"); auto *italyItem = new QStandardItem("Italy"); auto *romeItem = new QStandardItem("Rome"); auto *veronaItem = new QStandardItem("Verona"); // building up the hierarchy rootNode-> appendRow(americaItem); rootNode-> appendRow(europeItem); americaItem-> appendRow(mexicoItem); americaItem-> appendRow(usaItem); usaItem-> appendRow(bostonItem); europeItem-> appendRow(italyItem); italyItem-> appendRow(romeItem); italyItem-> appendRow(veronaItem); // register the model treeView->setModel(standardModel); treeView->expandAll(); // selection changes shall trigger a slot QItemSelectionModel *selectionModel = treeView->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChangedSlot); }
뷰는 selectionModel() 메서드를 사용하여 검색할 수 있는 별도의 선택 모델 내에서 선택 항목을 관리합니다. 슬롯을 selectionChanged() 신호에 연결하기 위해 선택 모델을 검색합니다.
(파일 출처: 예제/위젯/튜토리얼/modelview/7_selections/mainwindow.cpp)
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/) { // get the text of the selected item const QModelIndex index = treeView->selectionModel()->currentIndex(); QString selectedText = index.data(Qt::DisplayRole).toString(); // find out the hierarchy level of the selected item int hierarchyLevel = 1; QModelIndex seekRoot = index; while (seekRoot.parent().isValid()) { seekRoot = seekRoot.parent(); hierarchyLevel++; } QString showString = QString("%1, Level %2").arg(selectedText) .arg(hierarchyLevel); setWindowTitle(showString); }
treeView->selectionModel()->currentIndex()를 호출하여 선택 항목에 해당하는 모델 인덱스를 얻고, 모델 인덱스를 사용하여 필드의 문자열을 가져옵니다. 그런 다음 항목의 hierarchyLevel
을 계산하면 됩니다. 최상위 항목에는 부모가 없으며 parent() 메서드는 기본적으로 구성된 QModelIndex() 을 반환합니다. 따라서 parent() 메서드를 사용하여 반복하는 동안 수행된 단계를 계산하면서 최상위 레벨로 반복합니다.
선택 모델(위와 같이)을 검색할 수도 있지만 QAbstractItemView::setSelectionModel 으로 설정할 수도 있습니다. 이렇게 하면 선택 모델의 인스턴스 하나만 사용되므로 동기화된 선택이 있는 3개의 뷰 클래스를 가질 수 있습니다. 3개의 뷰 간에 선택 모델을 공유하려면 selectionModel()를 사용하고 setSelectionModel()를 사용하여 결과를 두 번째 및 세 번째 뷰 클래스에 할당합니다.
3.3 미리 정의된 모델
모델/뷰를 사용하는 일반적인 방법은 특정 데이터를 래핑하여 뷰 클래스에서 사용할 수 있도록 하는 것입니다. 그러나 Qt는 일반적인 기본 데이터 구조체에 대한 사전 정의된 모델도 제공합니다. 사용 가능한 데이터 구조 중 하나가 애플리케이션에 적합하다면 미리 정의된 모델을 선택하는 것이 좋습니다.
QStringListModel | 문자열 목록 저장 |
QStandardItemModel | 임의의 계층 구조 항목 저장 |
QFileSystemModel | 로컬 파일 시스템 캡슐화 |
QSqlQueryModel | SQL 결과 집합 캡슐화 |
QSqlTableModel | SQL 테이블 캡슐화 |
QSqlRelationalTableModel | 외래 키가 있는 SQL 테이블을 캡슐화합니다. |
QSortFilterProxyModel | 다른 모델 정렬 및/또는 필터링 |
3.4 델리게이트
지금까지 모든 예제에서 데이터는 셀에 텍스트 또는 체크박스로 표시되고 텍스트 또는 체크박스로 편집됩니다. 이러한 표시 및 편집 서비스를 제공하는 컴포넌트를 델리게이트라고 합니다. 뷰는 기본 델리게이트를 사용하므로 이제 막 델리게이트로 작업을 시작했을 뿐입니다. 하지만 다른 편집기(예: 슬라이더 또는 드롭다운 목록)를 사용하거나 데이터를 그래픽으로 표시하고 싶다고 상상해 보겠습니다. 별표를 사용하여 등급을 표시하는 별표 델리게이트라는 예제를 살펴보겠습니다:
이 뷰에는 기본 델리게이트를 대체하고 사용자 지정 델리게이트를 설치하는 setItemDelegate() 메서드가 있습니다. QStyledItemDelegate 에서 상속하는 클래스를 만들어 새 델리게이트를 작성할 수 있습니다. 별을 표시하고 입력 기능이 없는 델리게이트를 작성하려면 두 가지 메서드만 재정의하면 됩니다.
class StarDelegate : public QStyledItemDelegate { Q_OBJECT public: StarDelegate(QWidget *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; };
paint()는 기초 데이터의 내용에 따라 별을 그립니다. 데이터는 index.data()를 호출하여 조회할 수 있습니다. 델리게이트의 sizeHint() 메서드는 각 별의 치수를 구하는 데 사용되므로 셀은 별을 수용하기에 충분한 높이와 너비를 제공합니다.
뷰 클래스의 그리드 내에 사용자 지정 그래픽 표현으로 데이터를 표시하려는 경우 사용자 지정 델리게이트를 작성하는 것이 올바른 선택입니다. 그리드를 벗어나고 싶다면 사용자 지정 델리게이트가 아니라 사용자 지정 뷰 클래스를 사용하면 됩니다.
Qt 문서에서 델리게이트에 대한 기타 참조:
- 델리게이트 클래스
- QAbstractItemDelegate Class Reference
- QSqlRelationalDelegate Class Reference
- QStyledItemDelegate Class Reference
- QItemDelegate Class Reference
3.5 ModelTest로 디버깅하기
모델의 수동적인 특성은 프로그래머에게 새로운 도전 과제를 제공합니다. 모델의 불일치로 인해 애플리케이션이 충돌할 수 있습니다. 모델은 뷰에서 수많은 호출에 의해 영향을 받기 때문에 어떤 호출이 애플리케이션을 충돌시켰는지, 어떤 연산이 문제를 일으켰는지 파악하기 어렵습니다.
Qt Labs는 프로그래밍이 실행되는 동안 모델을 검사하는 ModelTest라는 소프트웨어를 제공합니다. 모델이 변경될 때마다 ModelTest는 모델을 검사하고 어설션으로 오류를 보고합니다. 트리 모델의 경우 계층적 특성으로 인해 미묘한 불일치가 발생할 가능성이 많기 때문에 이 기능은 특히 중요합니다.
보기 클래스와 달리 ModelTest는 범위를 벗어난 인덱스를 사용하여 모델을 테스트합니다. 즉, 애플리케이션이 ModelTest 없이 완벽하게 실행되더라도 충돌이 발생할 수 있습니다. 따라서 ModelTest를 사용할 때 범위를 벗어난 모든 인덱스도 처리해야 합니다.
4. 추가 정보의 좋은 출처
4.1 책
모델/뷰 프로그래밍은 Qt 문서뿐만 아니라 여러 좋은 책에서도 상당히 광범위하게 다루고 있습니다.
- C++ GUI 프로그래밍 with Qt 4 / Jasmin Blanchette, 마크 서머필드, 프렌티스 홀, 2판, ISBN 0-13-235416-0. 독일어로도 이용 가능: Qt 4를 사용한 C++ GUI 프로그래밍: 공식 입문서, Addison-Wesley, ISBN 3-827327-29-6
- Qt4의 책, Qt 애플리케이션 구축의 기술 / 다니엘 몰켄틴, 오픈 소스 프레스, ISBN 1-59327-147-6. Qt 4에서 번역 , 응용 프로그램 개발 입문, 오픈 소스 프레스, ISBN 3-937514-12-0.
- Qt 개발의 기초 / 요한 테린, Apress, ISBN 1-59059-831-8.
- 고급 Qt 프로그래밍 / 마크 서머필드, 프렌티스 홀, ISBN 0-321-63590-6. 이 책은 150페이지가 넘는 분량으로 모델/뷰 프로그래밍을 다룹니다.
다음 목록은 위에 나열된 처음 세 권의 책에 포함된 예제 프로그램의 개요를 제공합니다. 이 중 일부는 유사한 애플리케이션을 개발하는 데 매우 좋은 템플릿을 제공합니다.
예제 이름 | 사용된 뷰 클래스 | 사용된 모델 | 다루는 측면 | |
---|---|---|---|---|
팀 리더 | Q리스트뷰 | QStringListModel | 1장, 10장, 그림 10.6 | |
색상 이름 | QListView | QSortFilterProxyModel 적용 대상 QStringListModel | 1장, 10장, 그림 10.8 | |
통화 | QTableView | 사용자 지정 모델 기반 QAbstractTableModel | 읽기 전용 | 1장, 10장, 그림 10.10 |
도시 | QTableView | 기준 사용자 지정 모델 QAbstractTableModel | 읽기/쓰기 | 1장, 10장, 그림 10.12 |
부울 파서 | QTreeView | 기반 사용자 정의 모델 QAbstractItemModel | 읽기 전용 | 1장, 10장, 그림 10.14 |
트랙 에디터 | QTableWidget | 커스텀 편집기를 제공하는 커스텀 델리게이트 | 책 1, 10장, 그림 10.15 | |
주소록 | QListView QTableView QTreeView | 사용자 지정 모델 기반 QAbstractTableModel | 읽기/쓰기 | 책2, 8.4장 |
정렬 기능이 있는 주소록 | QSortfilterProxyModel | 정렬 및 필터 기능 소개 | Book2, 8.5장 | |
체크박스가 있는 주소록 | 모델/보기의 체크박스 소개 | Book2, 8.6장 | ||
전치된 그리드가 있는 주소록 | 사용자 지정 프록시 모델 기반 QAbstractProxyModel | 사용자 지정 모델 소개 | Book2, 8.7장 | |
드래그 앤 드롭이 있는 주소록 | 드래그 앤 드롭 지원 소개 | 책2, 8.8장 | ||
사용자 지정 편집기가 있는 주소록 | 사용자 지정 델리게이트 소개 | 책2, 8.9장 | ||
보기 | QListView QTableView QTreeView | QStandardItemModel | 읽기 전용 | 책 3, 5장, 그림 5-3 |
바델리게이트 | QTableView | 다음을 기반으로 하는 프레젠테이션용 사용자 지정 델리게이트 QAbstractItemDelegate | 책 3, 5장, 그림 5-5 | |
편집 델리게이트 | QTableView | 다음을 기반으로 하는 편집용 사용자 지정 델리게이트 QAbstractItemDelegate | 3장, 5장, 그림 5-6 | |
단일 항목 보기 | 다음을 기반으로 하는 사용자 지정 보기 QAbstractItemView | 사용자 지정 보기 | 3장, 5장, 그림 5-7 | |
listmodel | QTableView | 기반 사용자 지정 모델 QAbstractTableModel | 읽기 전용 | 3장, 5장, 그림 5-8 |
treemodel | QTreeView | 기반 사용자 지정 모델 QAbstractItemModel | 읽기 전용 | 3장, 5장, 그림 5-10 |
정수 편집 | QListView | 사용자 지정 모델 기반 QAbstractListModel | 읽기/쓰기 | 3장, 5장, 목록 5-37, 그림 5-11 |
정렬 | QTableView | QSortFilterProxyModel 적용 대상 QStringListModel | 정렬 데모 | 3장, 5장, 그림 5-12 |
4.2 Qt 문서
Qt 5.0에는 모델/뷰에 대한 19개의 예제가 포함되어 있습니다. 예제는 항목 뷰 예제 페이지에서 찾을 수 있습니다.
예제 이름 | 사용된 뷰 클래스 | 사용된 모델 | 다루는 측면 |
---|---|---|---|
주소록 | QTableView | QAbstractTableModel QSortFilterProxyModel | 하나의 데이터 풀에서 다양한 하위 집합을 생성하기 위해 QSortFilterProxyModel 사용 |
기본 정렬/필터 모델 | QTreeView | QStandardItemModel QSortFilterProxyModel | |
차트 | 사용자 지정 뷰 | QStandardItemModel | 선택 모델과 협력하는 사용자 지정 뷰 디자인하기 |
색상 편집기 팩토리 | QTableWidget | 색상을 선택할 수 있는 새로운 사용자 지정 편집기로 표준 델리게이트를 개선합니다. | |
콤보 위젯 매퍼 | QDataWidgetMapper 를 QLineEdit, QTextEdit 및 QComboBox | QStandardItemModel | QComboBox 을 뷰 클래스로 사용할 수 있는 방법을 보여줍니다. |
사용자 지정 정렬/필터 모델 | QTreeView | QStandardItemModel QSortFilterProxyModel | 고급 정렬 및 필터링을 위한 QSortFilterProxyModel 서브클래스 |
Dir View | QTreeView | QFileSystemModel | 뷰에 모델을 할당하는 방법을 보여주는 아주 작은 예제입니다. |
편집 가능한 트리 모델 | QTreeView | 사용자 지정 트리 모델 | 트리 작업에 대한 포괄적인 예제로, 기본 사용자 지정 모델을 사용하여 셀 및 트리 구조를 편집하는 방법을 보여 줍니다. |
자세히 보기 | QListView | 사용자 지정 목록 모델 | 동적으로 변경되는 모델 |
고정 컬럼 | QTableView | QStandardItemModel | |
인터뷰 | 여러 | 사용자 지정 항목 모델 | 다중 보기 |
픽셀레이터 | QTableView | 사용자 지정 테이블 모델 | 사용자 지정 델리게이트 구현 |
퍼즐 | QListView | 사용자 지정 목록 모델 | 드래그 앤 드롭으로 모델/보기 |
간단한 DOM 모델 | QTreeView | 사용자 지정 트리 모델 | 사용자 지정 트리 모델에 대한 읽기 전용 예제 |
단순 트리 모델 | QTreeView | 사용자 지정 트리 모델 | 사용자 정의 트리 모델에 대한 읽기 전용 예제 |
단순 위젯 매퍼 | QDataWidgetMapper 를 사용하여 QLineEdit, QTextEdit 및 QSpinBox | QStandardItemModel | 기본 QDataWidgetMapper 사용법 |
스프레드시트 | QTableView | 사용자 지정 델리게이트 | |
스타 델리게이트 | QTableWidget | 포괄적인 사용자 지정 델리게이트 예제입니다. |
모델/뷰 기술에 대한 참조 문서도 제공됩니다.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.