Qt 로 만들자: 시간표
<환율계산기> 이후로 꽤 오래 쉬었다. 간간히 책에 대한 글을 올리기는 했지만, Qt 프로그램에 쓸 시간이 부족했다. 다행히 시간적 여유가 생겨 다시 시작한다. 이번에는 <시간표> 이다.
PMS 나 다이어리 수준은 아니고, 학창 시절에 필요했던 요일과 수업시간만 있는 간단한 시간표이다.
1. 요구사항
참, 간단하다. ^^
2. 코드 작성
2.1 프로젝트 작성
2.2 헤더 분석(timetable.h)
3 번째줄: closeEvent() 는 창이 닫힐 때 호출되는 이벤트 함수이다. Q_DECL_OVERRIDE 는 선언된 함수가 실제 오버라이딩 하고 있는지 검사한다. C++11 이상이라면 override 로 대체되고, 아니면 아무 일도 하지 않는다.
27 번째줄: QTableWidget 은 QTableView 에 대한 편의 클래스이다. 아이템 형식으로 테이블을 조작할 수 있다.
2.3 소스 파일(timetable.cpp)
2.3.1 생성자와 소멸자
초기화를 진행하고, 새로운 시간표를 만든다.
16 번째줄: QMetaObject::invokeMethod() 는 slot 으로 등록된 함수를 호출한다. Qt::QueuedConnection 은 Qt 에서 제공하는 여러 Connection Type 중의 하나인데, 함수를 직접 호출(Qt::DirectConnection)하는 것이 아니라, 이벤트 큐에 추가한 후, 이벤트 루프에서 이벤트를 처리할 때 함수가 실행되도록 한다. 이 함수는 나중에 멀티 쓰레드 프로그래밍을 할 때도 유용하게 쓰이는 함수이니, 꼭 기억해 두도록 하자.
2.3.2 initMenus()
"파일" 메뉴를 생성하여, 메뉴바에 추가한다.
20 번째줄: 이전과 다르게, "끝내기" 액션을 qApp->quit() 에 연결하지 않고, this->close() 에 연결하였다. 이것은 프로그램이 끝날 때, closeEvent() 에서 변경된 시간표를 저장하기 위한 것이다.
2.3.3 initWidgets()
테이블 위젯을 생성하고 센트를 위젯을 설정한다.
테이블 위젯은 엑셀같은 표를 만들 수 일도록 해준다.
9 번째 줄: QTableWidget::setSizeAdjustPolicy() 는 크기 조절 정책을 설정하는 함수로, QTableWidget::AdjustToContents 는 내용에 맞추어 크기를 조절하도록 한다.
2.3.4 newTimeTable()
새 시간표를 만든다.
6 번째 줄: QStringList 는 QList<QString> 과 같다.
14 번째 줄: QTableWidget::clear() 는 모든 내용을 지우고, 아이템을 삭제한다.
19 번째 줄: QTableWidget::setColumnsCount() 는 테이블의 세로줄을 설정한다.
20 번째 줄: QTableWidget::setRowcount() 는 테이블의 가로줄을 설정한다.
5 번째 줄: QTableWidgetItem 은 QTableWidget 의 헤더나 셀에 쓰인다.
7 번째 줄: QTableWidgetItem::setText() 는 아이템의 텍스트를 설정한다.
9 번째 줄: QTableWidget::setHorizontalHeaderItem() 은 해당 위치에 아이템을 설정한다.
12 번째 줄: QHeaderView::setContextMenuPolicy() 는 컨텍스트 메뉴를 어떻게 처리할지를 결정한다. Qt::CustomContextMenu 는 컨텍스트 메뉴를 사용자가 처리하도록 한다.
15 번째 줄: customContextMenuRequested() 시그널은 사용자가 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴를 호출했을 때 보내진다.
세로 헤더에 대한 설정 부분이다.
7 번째 줄: QTableWidget::setItem() 은 주어진 위치에 아이템을 설정한다.
2 번째 줄: QWidget::resize() 는 창의 폭과 높이를 바꾼다.
6 번째 줄: QWidget::adjustSize() 내부에 포함하고 있는 모든 자식 위젯을 보이도록 창 크기를 조절한다.
9 번째 줄: cellChanged() 시그널은 테이블의 내용이 바뀌었을 때 발생한다.
12 번째 줄: setWindowModified() 는 창의 내용이 변경되었는지를 설정한다. 변경 상태에 따라 창 제목에 * 가 표시된다.
2.3.5 openTimeTable()
파일로 저장된 시간표를 읽는다.
10 번째 줄: QFileDialog::getOpenFileName() 은 읽을 파일을 고르는 정적함수이다. 네 번째 인자는 필터를 지정하는데, 여러 필터를 지정할 때는 ;; 를 구분자로 쓴다.
4 번째 줄: QFile 은 Qt 에서 파일 제어를 위해 제공하는 클래스이다.
7 번째 줄: QFile::open() 은 파일을 주어진 모드로 연다. QIODevice::ReadOnly 는 읽기 전용이다.
12 번째 줄: QTextStream 은 주어진 장치를 텍스트 스트림으로 조작한다. 텍스트 스트림은 DOS, 윈도우, OS/2 같은 DOS 기반 운영체제에서, 저장할 때는 \n 을 \r\n 으로, 읽을 때는\n 을 \r\n 을 \n(2016/09/27)으로 바꾼다. 그리고 operator>>() 를 통해 값을 읽을 수 있다.
15 번째 줄: QTextStream::readLine() 을 한 줄을 읽는다.
newTimeTable() 에서처럼 테이블의 헤더와 셀을 설정한다. 다만, 내용은 파일에서 읽어온다.
38 번째 줄: QFile::close() 는 열린 파일을 닫는다.
크기를 조절하고, 파일 이름과 변경 상태를 설정한다.
2.3.6 saveTimeTable()
13 번째 줄: QFileDialog::getSaveFileName() 은 저장할 파일 이름을 고르는 정작함수이다.
6 번째 줄: QIODevice::WriteOnly 는 쓰기 전용을 나타내는 플래그이다.
12 번째 줄: QTextStream 은 operator<<() 로 쓰기 작업을 수행할 수 있다.
파일 이름을 설정하고, 변경상태를 초기화한다.
2.3.7 setFileName()
파일 이름을 설정하고, 창 제목을 바꾼다. [*] 는 창 내용이 수정되었을 때(setWindowModified(true)) * 로 대체된다.
11 번째 줄: QFileInfo::fileName() 은 주어진 파일 경로 중에서 파일 이름을 돌려준다.
12 번째 줄: QGuiApplication::applicationDisplayName() 은 프로그램의 이름을 돌려준다. 보통 실행 파일 이름이다.
2.3.8 modified()
시간표가 바뀌었을 때 호출되고, 창의 변경 상태를 설정한다.
2.3.9 saveModifiedTable()
시간표가 변경되었으면 물어보고 저장한다.
8 번째 줄: QWidget::isWindowModified() 는 창의 변경 상태를 알려준다.
11 번째 줄: QMessageBox::information() 은 사용자에게 정보를 보여주는 정적함수이다.
2.3.10 headerContextMenuRequested()
11 번째: QHeaderView::logicalIndexAt() 은 주어진 위치에 해당하는 헤더의 인덱스를 돌려준다.
13 번째: QHeaderView::orientation() 은 헤더의 방향을 돌려준다.
14~15 번째: QTableWidget::horizontalHeaderItem() 과 QTableWidget::verticalHeaderItem() 은 주어진 인덱스에 해당하는 아이템을 돌려준다.
18 번째: QInputDialog::getText() 는 사용자로부터 문자열을 입력받는 정적함수이다.
2.3.11 closeEvent()
closeEvent() 를 처리한다.
8 번째줄: accept() 하면 이후 과정이 이어지고, ignore() 하면 중단된다.
3. 마무리하면서...
<시간표> 를 만들어보면서 파일 입출력과 테이블 위젯에 대해 간단히 살펴보았다. 보다 확장성 있게 만들어 보면 더욱 좋을 것이다. 배경색을 다르게 하는 것 같은... 그리고 출력 기능도 추가하면 보다 완벽한 시간표 프로그램이 될 수 있을 것이다. 많은 아쉬움이 있지만, 다음으로 미루도록 하자. ^^
언제나 말하지만, Qt 도움말과 Assistant 는 꼭 챙기자. 위에 설명된 것은 극히 일부일 뿐이니까.
그리고 윈도용 Qt 5.5 의 QTableWidget 은 현재 버그가 하나 있다. 한글 입력 상태에서 셀에 입력을 하면 첫글자가 영어 알파벳으로 나온다. <시간표> 를 만드는 도중에 확인을 하여, 버그 보고하였고, 실제 버그로 확인이 되었다. 이후 버전에서는 수정될 것으로 기대한다.
다음은 <시간표> 의 실행모습이다.
전체 소스는 여기에서 확인하자.
PMS 나 다이어리 수준은 아니고, 학창 시절에 필요했던 요일과 수업시간만 있는 간단한 시간표이다.
1. 요구사항
- 요일 이름을 바꿀 수 있다.
- 수업 시간을 바꿀 수 있다.
- 시간표를 저장하고 읽을 수 있다.
참, 간단하다. ^^
2. 코드 작성
2.1 프로젝트 작성
- 프로젝트 이름 : TimeTable
- 메인 클래스 이름 :
ExchangeTimeTable - 메인 클래스 유형 : QMainWindow
2.2 헤더 분석(timetable.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /** @file timetable.h */ #ifndef TIMETABLE_H #define TIMETABLE_H #include <QMainWindow> #include <QtWidgets> /** * @brief 시간표 클래스 */ class TimeTable : public QMainWindow { Q_OBJECT public: TimeTable(QWidget *parent = 0); ~TimeTable(); protected: void closeEvent(QCloseEvent *e) Q_DECL_OVERRIDE; private: int _tableRows; ///< 시간표 세로줄 수 int _tableCols; ///< 시간표 가로줄 수 QTableWidget *_timeTable; ///< 시간표를 위한 테이블 위젯 QString _fileName; ///< 현재 파일 이름 bool _named; ///< 이름이 정해졌으면 true, 아니면 false void initMenus(); void initWidgets(); void setFileName(const QString &name); bool saveModifiedTable(); private slots: void newTimeTable(); void openTimeTable(); void saveTimeTable(); void modified(); void headerContextMenuRequested(const QPoint &pos); }; #endif // TIMETABLE_H |
3 번째줄: closeEvent() 는 창이 닫힐 때 호출되는 이벤트 함수이다. Q_DECL_OVERRIDE 는 선언된 함수가 실제 오버라이딩 하고 있는지 검사한다. C++11 이상이라면 override 로 대체되고, 아니면 아무 일도 하지 않는다.
27 번째줄: QTableWidget 은 QTableView 에 대한 편의 클래스이다. 아이템 형식으로 테이블을 조작할 수 있다.
2.3 소스 파일(timetable.cpp)
2.3.1 생성자와 소멸자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @brief TimeTable::TimeTable 생성자 * @param parent 부모 위젯 */ TimeTable::TimeTable(QWidget *parent) : QMainWindow(parent) , _tableRows(0) , _tableCols(0) , _timeTable(0) , _named(false) { initMenus(); // 메뉴 초기화 initWidgets(); // 위젯 초기화 // newTimeTable() 을 나중에 이벤트 루프에서 호출 QMetaObject::invokeMethod(this, "newTimeTable", Qt::QueuedConnection); } /** * @brief TimeTable::~TimeTable 소멸자 */ TimeTable::~TimeTable() { } |
초기화를 진행하고, 새로운 시간표를 만든다.
16 번째줄: QMetaObject::invokeMethod() 는 slot 으로 등록된 함수를 호출한다. Qt::QueuedConnection 은 Qt 에서 제공하는 여러 Connection Type 중의 하나인데, 함수를 직접 호출(Qt::DirectConnection)하는 것이 아니라, 이벤트 큐에 추가한 후, 이벤트 루프에서 이벤트를 처리할 때 함수가 실행되도록 한다. 이 함수는 나중에 멀티 쓰레드 프로그래밍을 할 때도 유용하게 쓰이는 함수이니, 꼭 기억해 두도록 하자.
2.3.2 initMenus()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @brief TimeTable::initMenus 메뉴를 초기화한다 */ void TimeTable::initMenus() { // "파일" 메뉴 생성 QMenu *fileMenu = new QMenu(tr("파일(&F)")); // "새 시간표" 액션 추가 fileMenu->addAction(tr("새 시간표(&N)"), this, SLOT(newTimeTable()), QKeySequence(QKeySequence::New)); // "열기" 액션 추가 fileMenu->addAction(tr("열기(&O)..."), this, SLOT(openTimeTable()), QKeySequence(QKeySequence::Open)); // "저장하기" 액션 추가 fileMenu->addAction(tr("저장하기(&S)"), this, SLOT(saveTimeTable()), QKeySequence(QKeySequence::Save)); // "구분자" 추가 fileMenu->addSeparator(); // "끝내기" 액션 추가 fileMenu->addAction(tr("끌내기(&x)"), this, SLOT(close()), QKeySequence(tr("Ctrl+Q"))); // "파일" 메뉴 추가 menuBar()->addMenu(fileMenu); } |
"파일" 메뉴를 생성하여, 메뉴바에 추가한다.
20 번째줄: 이전과 다르게, "끝내기" 액션을 qApp->quit() 에 연결하지 않고, this->close() 에 연결하였다. 이것은 프로그램이 끝날 때, closeEvent() 에서 변경된 시간표를 저장하기 위한 것이다.
2.3.3 initWidgets()
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * @brief TimeTable::initWidgets 위젯을 초기화한다 */ void TimeTable::initWidgets() { // 테이블 위젯 생성 _timeTable = new QTableWidget; // 내용에 맞게 크기 조절 _timeTable->setSizeAdjustPolicy(QTableWidget::AdjustToContents); // 센트럴 위젯 설정 setCentralWidget(_timeTable); } |
테이블 위젯을 생성하고 센트를 위젯을 설정한다.
테이블 위젯은 엑셀같은 표를 만들 수 일도록 해준다.
Vista 스타일 | Mac 스타일 | Fusion 스타일 |
출처: Qt 도움말(QTableWidget) |
9 번째 줄: QTableWidget::setSizeAdjustPolicy() 는 크기 조절 정책을 설정하는 함수로, QTableWidget::AdjustToContents 는 내용에 맞추어 크기를 조절하도록 한다.
2.3.4 newTimeTable()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * @brief TimeTable::newTimeTable 새 시간표를 만든다 */ void TimeTable::newTimeTable() { static const QStringList weekDays( QStringList() << tr("월") << tr("화") << tr("수") << tr("목") << tr("금") << tr("토")); // 변경된 시간표를 저장할지 물어봄 if (!saveModifiedTable()) return; _timeTable->clear(); // 테이블 초기화 _tableCols = 6; // 세로줄 수는 6 _tableRows = 8; // 가로줄 수는 8 _timeTable->setColumnCount(_tableCols); // 세로줄 수 설정 _timeTable->setRowCount(_tableRows); // 가로줄 수 설정 |
새 시간표를 만든다.
6 번째 줄: QStringList 는 QList<QString> 과 같다.
14 번째 줄: QTableWidget::clear() 는 모든 내용을 지우고, 아이템을 삭제한다.
19 번째 줄: QTableWidget::setColumnsCount() 는 테이블의 세로줄을 설정한다.
20 번째 줄: QTableWidget::setRowcount() 는 테이블의 가로줄을 설정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 가로 헤더 아이템 설정 for (int i = 0; i < _tableCols; ++i) { // 테이블 아이템 생성 QTableWidgetItem *item = new QTableWidgetItem; // 아이템 텍스트 요일로 설정 item->setText(weekDays.at(i)); // 가로 헤더 아이템 설정 _timeTable->setHorizontalHeaderItem(i, item); } // 가로 헤더 컨텍스트 메뉴 정책 설 _timeTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); // 컨텍스트 메뉴 시그널 연결 connect(_timeTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint))); |
5 번째 줄: QTableWidgetItem 은 QTableWidget 의 헤더나 셀에 쓰인다.
7 번째 줄: QTableWidgetItem::setText() 는 아이템의 텍스트를 설정한다.
9 번째 줄: QTableWidget::setHorizontalHeaderItem() 은 해당 위치에 아이템을 설정한다.
12 번째 줄: QHeaderView::setContextMenuPolicy() 는 컨텍스트 메뉴를 어떻게 처리할지를 결정한다. Qt::CustomContextMenu 는 컨텍스트 메뉴를 사용자가 처리하도록 한다.
15 번째 줄: customContextMenuRequested() 시그널은 사용자가 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴를 호출했을 때 보내진다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 세로 헤더 아이템 설정 for (int i = 0; i < _tableRows; ++i) { // 테이블 아이템 생성 QTableWidgetItem *item = new QTableWidgetItem; // 아이템 텍스트 시간으로 설정 item->setText(QString::number(i + 1)); // 세로 헤더 아이템 설정 _timeTable->setVerticalHeaderItem(i, item); } // 세로 헤더 컨텍스트 메뉴 정책 설정 _timeTable->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); // 컨텍스트 메뉴 시그널 연결 connect(_timeTable->verticalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint))); |
세로 헤더에 대한 설정 부분이다.
1 2 3 4 5 6 7 8 9 | // 테이블 셀 초기화 for (int i = 0; i < _tableRows; ++i) { for (int j = 0; j < _tableCols; ++j) { // 빈 아이템으로 설정 _timeTable->setItem(i, j, new QTableWidgetItem(QString())); } } |
7 번째 줄: QTableWidget::setItem() 은 주어진 위치에 아이템을 설정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 테이블 크기에 맞추어서 창 크기 조절 resize(_timeTable->sizeHint().width(), _timeTable->sizeHint().height()); // 내부 위젯에 맞추어서 크기 조절 adjustSize(); // 셀 내용이 바뀌면 modified() 호출 connect(_timeTable, SIGNAL(cellChanged(int,int)), this, SLOT(modified())); setFileName(tr("이름 없음")); // 새 이름은 "이름 없음" setWindowModified(false); // 변경되지 않았음 } |
2 번째 줄: QWidget::resize() 는 창의 폭과 높이를 바꾼다.
6 번째 줄: QWidget::adjustSize() 내부에 포함하고 있는 모든 자식 위젯을 보이도록 창 크기를 조절한다.
9 번째 줄: cellChanged() 시그널은 테이블의 내용이 바뀌었을 때 발생한다.
12 번째 줄: setWindowModified() 는 창의 내용이 변경되었는지를 설정한다. 변경 상태에 따라 창 제목에 * 가 표시된다.
2.3.5 openTimeTable()
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * @brief TimeTable::openTimeTable 시간표 파일을 읽는다 */ void TimeTable::openTimeTable() { // 변경된 시간표를 저장할지 물어봄 if (!saveModifiedTable()) return; QString name = QFileDialog::getOpenFileName(this, tr("시간표 열기"), QString(), tr("시간표 (*.tbl);;" "모든 파일 (*)")); |
파일로 저장된 시간표를 읽는다.
10 번째 줄: QFileDialog::getOpenFileName() 은 읽을 파일을 고르는 정적함수이다. 네 번째 인자는 필터를 지정하는데, 여러 필터를 지정할 때는 ;; 를 구분자로 쓴다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 파일을 골랐으면 if (!name.isEmpty()) { QFile f(name); // 읽기 전용으로 파일 열기 if (f.open(QIODevice::ReadOnly)) { _timeTable->clear(); // 테이블 초기화 QTextStream in(&f); // 파일을 텍스트 스트림으로 처리 in >> _tableRows >> _tableCols; // 세로줄 수와 가로줄 수 읽기 in.readLine(); // 줄바꿈 문자 읽기 |
4 번째 줄: QFile 은 Qt 에서 파일 제어를 위해 제공하는 클래스이다.
7 번째 줄: QFile::open() 은 파일을 주어진 모드로 연다. QIODevice::ReadOnly 는 읽기 전용이다.
12 번째 줄: QTextStream 은 주어진 장치를 텍스트 스트림으로 조작한다. 텍스트 스트림은 DOS, 윈도우, OS/2 같은 DOS 기반 운영체제에서, 저장할 때는 \n 을 \r\n 으로, 읽을 때는
15 번째 줄: QTextStream::readLine() 을 한 줄을 읽는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | _timeTable->setColumnCount(_tableCols); // 가로줄 수 설정 _timeTable->setRowCount(_tableRows); // 세로줄 수 설정 // 세로 헤더 아이템 읽어 설정 for (int i = 0; i < _tableCols; ++i) { QTableWidgetItem *item = new QTableWidgetItem; item->setText(in.readLine()); _timeTable->setHorizontalHeaderItem(i, item); } _timeTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); connect(_timeTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint))); // 가로 헤더 아이템 읽어 설정 for (int i = 0; i < _tableRows; ++i) { QTableWidgetItem *item = new QTableWidgetItem; item->setText(in.readLine()); _timeTable->setVerticalHeaderItem(i, item); } _timeTable->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); connect(_timeTable->verticalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint))); // 테이블 셀 설정 for (int i = 0; i < _tableRows; ++i) { for (int j = 0; j < _tableCols; ++j) { _timeTable->setItem(i, j, new QTableWidgetItem(in.readLine())); } } f.close(); // 파일 닫음 |
newTimeTable() 에서처럼 테이블의 헤더와 셀을 설정한다. 다만, 내용은 파일에서 읽어온다.
38 번째 줄: QFile::close() 는 열린 파일을 닫는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 테이블 크기에 따라 창크기 조절 resize(_timeTable->sizeHint().width(), _timeTable->sizeHint().height()); adjustSize(); // 내부 위젯에 맞추어 크기 조절 setFileName(name); // 파일 이름 설정 setWindowModified(false); // 변경되지 않았 } } } |
크기를 조절하고, 파일 이름과 변경 상태를 설정한다.
2.3.6 saveTimeTable()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * @brief TimeTable::saveTimeTable 시간표를 저장한다 */ void TimeTable::saveTimeTable() { // 파일이름이 정해지지 않았거나 내용이 변경되었으면 if (!_named || isWindowModified()) { QString name; // 정해진 이름이 있으면 그 이름을 쓰고, 아니면 파일 이름을 물어봄 name = _named ? _fileName : QFileDialog::getSaveFileName(this, tr("시간표 저장"), QString(), tr("시간표 (*.tbl);;" "모든 파일 (*)")); |
13 번째 줄: QFileDialog::getSaveFileName() 은 저장할 파일 이름을 고르는 정작함수이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | if (!name.isEmpty()) { QFile f(name); // 쓰기 모드로 파일 열기 if (f.open(QIODevice::WriteOnly)) { QTextStream out(&f); // 텍스트 스트림으로 처리 // 세로줄 수 및 가로줄 수 저장 out << _tableRows << " " << _tableCols << "\n"; // 가로 헤더 아이템 저장 for (int i = 0; i < _tableCols; ++i) out << _timeTable->horizontalHeaderItem(i)->text() << "\n"; // 세로 헤더 아이템 저장 for (int i = 0; i < _tableRows; ++i) out << _timeTable->verticalHeaderItem(i)->text() << "\n"; // 테이블 셀 저장 for (int i = 0; i < _tableRows; ++i) { for (int j = 0; j < _tableCols; ++j) out << _timeTable->item(i, j)->text() << "\n"; } f.close(); // 파일 닫기 |
6 번째 줄: QIODevice::WriteOnly 는 쓰기 전용을 나타내는 플래그이다.
12 번째 줄: QTextStream 은 operator<<() 로 쓰기 작업을 수행할 수 있다.
1 2 3 4 5 6 7 8 | setFileName(name); // 파일 이름 설정 setWindowModified(false); // 변경되지 않았음 _named = true; // 이름 정해졌음 } } } } |
파일 이름을 설정하고, 변경상태를 초기화한다.
2.3.7 setFileName()
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * @brief TimeTable::setFileName 현재 파일이름을 설정한다 * @param name 설정할 파일 이름 */ void TimeTable::setFileName(const QString &name) { _fileName = name; // 창 제목을 "파일 이름 - 프로그램이름" 형태로 표시 // [*] 는 내용이 바뀌면 * 를 표시 setWindowTitle(QFileInfo(_fileName).fileName() + " - " + qApp->applicationDisplayName() + "[*]"); } |
파일 이름을 설정하고, 창 제목을 바꾼다. [*] 는 창 내용이 수정되었을 때(setWindowModified(true)) * 로 대체된다.
11 번째 줄: QFileInfo::fileName() 은 주어진 파일 경로 중에서 파일 이름을 돌려준다.
12 번째 줄: QGuiApplication::applicationDisplayName() 은 프로그램의 이름을 돌려준다. 보통 실행 파일 이름이다.
2.3.8 modified()
1 2 3 4 5 6 7 | /** * @brief TimeTable::modified 시간표 내용이 변경되면 변경 상태를 설정한다 */ void TimeTable::modified() { setWindowModified(true); // 변경되었음 } |
시간표가 바뀌었을 때 호출되고, 창의 변경 상태를 설정한다.
2.3.9 saveModifiedTable()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * @brief TimeTable::saveModifiedTable 변경된 시간표를 저장한다 * @return Yes 또는 No 이면 true, Cancel 이면 false */ bool TimeTable::saveModifiedTable() { // 변경되었으면 if (isWindowModified()) { // 저장할지 물어봄 switch(QMessageBox::information(this, qApp->applicationDisplayName(), tr("시간표가 변경되었습니다. " "저장할까요?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel)) { case QMessageBox::Cancel: // 취소 return false; case QMessageBox::Yes: // 저장 saveTimeTable(); break; case QMessageBox::No: // 저장 안 함 default: // 경고 제거용 break; } } return true; } |
시간표가 변경되었으면 물어보고 저장한다.
8 번째 줄: QWidget::isWindowModified() 는 창의 변경 상태를 알려준다.
11 번째 줄: QMessageBox::information() 은 사용자에게 정보를 보여주는 정적함수이다.
2.3.10 headerContextMenuRequested()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** * @brief TimeTable::headerContextMenuRequested 헤더 컨텍스트 메뉴를 처리한다 * @param pos 컨텍스트 메뉴가 호출된 위치 */ void TimeTable::headerContextMenuRequested(const QPoint &pos) { // 시그널을 보낸 헤더 뷰 위젯 QHeaderView *headerView = qobject_cast<QHeaderView *>(sender()); // 컨텍스트 메뉴가 호출된 위치에 있는 아이템의 인덱스 int index = headerView->logicalIndexAt(pos); // 테이블 위젯 아이템 얻기 QTableWidgetItem *item = headerView->orientation() == Qt::Horizontal ? _timeTable->horizontalHeaderItem(index) : _timeTable->verticalHeaderItem(index); // 새 이름을 물어봄 QString newName = QInputDialog::getText(this, qApp->applicationName(), tr("새 이름"), QLineEdit::Normal, item->text()); if (!newName.isEmpty()) { item->setText(newName); // 아이템 텍스트 설정 // 테이블 크기에 따라 창 크기 조절 resize(_timeTable->sizeHint().width(), _timeTable->sizeHint().height()); adjustSize(); // 내부 위젯에 맞추어 창 크기 조절 modified(); // 변경되었음 } } |
11 번째: QHeaderView::logicalIndexAt() 은 주어진 위치에 해당하는 헤더의 인덱스를 돌려준다.
13 번째: QHeaderView::orientation() 은 헤더의 방향을 돌려준다.
14~15 번째: QTableWidget::horizontalHeaderItem() 과 QTableWidget::verticalHeaderItem() 은 주어진 인덱스에 해당하는 아이템을 돌려준다.
18 번째: QInputDialog::getText() 는 사용자로부터 문자열을 입력받는 정적함수이다.
2.3.11 closeEvent()
1 2 3 4 5 6 7 8 9 10 11 | /** * @brief TimeTable::closeEvent closeEvent() 를 처리한다 * @param e 이벤트 */ void TimeTable::closeEvent(QCloseEvent *e) { if (saveModifiedTable()) e->accept(); else e->ignore(); } |
closeEvent() 를 처리한다.
8 번째줄: accept() 하면 이후 과정이 이어지고, ignore() 하면 중단된다.
3. 마무리하면서...
<시간표> 를 만들어보면서 파일 입출력과 테이블 위젯에 대해 간단히 살펴보았다. 보다 확장성 있게 만들어 보면 더욱 좋을 것이다. 배경색을 다르게 하는 것 같은... 그리고 출력 기능도 추가하면 보다 완벽한 시간표 프로그램이 될 수 있을 것이다. 많은 아쉬움이 있지만, 다음으로 미루도록 하자. ^^
언제나 말하지만, Qt 도움말과 Assistant 는 꼭 챙기자. 위에 설명된 것은 극히 일부일 뿐이니까.
그리고 윈도용 Qt 5.5 의 QTableWidget 은 현재 버그가 하나 있다. 한글 입력 상태에서 셀에 입력을 하면 첫글자가 영어 알파벳으로 나온다. <시간표> 를 만드는 도중에 확인을 하여, 버그 보고하였고, 실제 버그로 확인이 되었다. 이후 버전에서는 수정될 것으로 기대한다.
다음은 <시간표> 의 실행모습이다.
전체 소스는 여기에서 확인하자.
댓글
댓글 쓰기