Qt로 프로그래밍 시작하기

크로스 플랫폼 GUI 툴킷 Qt의 세계에 오신 것을 환영합니다. 이 문서에서는 간단한 메모장을 짜면서 Qt에 대하여 알아 볼 것입니다. 이 문서를 읽고 나면 다른 부분의 미리보기나 API 문서를 더 쉽게 볼 수 있을 것이고, 개발하려는 프로그램에 필요한 정보를 더 빨리 찾을 수 있을 것입니다.

작은 메모장

이 첫 예제에서는 창 하나를 열고, 그 안에 텍스트 편집기를 만들고 보여 줄 것입니다. GUI를 사용하는 가장 간단한 Qt 프로그램입니다.

코드:

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.  
  8.     QTextEdit textEdit;
  9.     textEdit.show();
  10.  
  11.     return app.exec();
  12. }

한 줄 한 줄 코드를 읽어 봅시다. 맨 첫 두 줄에서는 이 예제에 필요한 QApplication [doc.qt.nokia.com]QTextEdit [doc.qt.nokia.com] 헤더 파일을 불러옵니다. 모든 Qt 클래스는 그 클래스 이름과 같은 이름으로 된 헤더 파일에 포함되어 있습니다.

여섯번째 줄에서는 QApplication [doc.qt.nokia.com] 개체를 만듭니다. 이 개체는 프로그램 전역 자원을 관리하며 GUI가 있는 Qt 프로그램을 실행시키는 데 필요합니다. Qt는 명령행 인자를 지원하므로 argv와 args가 필요합니다.

여덟번째 줄에서는 QTextEdit [doc.qt.nokia.com] 개체를 만듭니다. 텍스트 편집기는 GUI의 구성 요소이며, Qt에서는 GUI의 구성 요소를 위젯이라고 부릅니다. 위젯의 예를 들면 스크롤 바, 레이블, 라디오 단추 등이 있습니다. 대화 상자나 프로그램 주 창처럼 다른 위젯을 포함할 수도 있습니다.

아홉번째 줄에서는 텍스트 편집기에 창틀을 씌워 화면에 표시합니다. 모든 위젯은 다른 위젯을 포함하는 컨테이너의 역할을 합니다. 예를 들어 QMainWindow [doc.qt.nokia.com]와 같은 위젯은 도구 모음, 메뉴, 상태 표시줄, 다른 위젯을 포함합니다. 그래서 위젯 하나를 한 창에 표시할 수 있습니다. 기본적으로 위젯은 표시되지 않으므로, show() [doc.qt.nokia.com] 함수를 호출하면 위젯이 나타납니다.

열한번째 줄에서는 QApplication [doc.qt.nokia.com]의 이벤트 루프를 시작합니다. Qt 프로그램이 실행될 때에는 이벤트가 프로그램의 위젯으로 전달됩니다. 이벤트의 예는 마우스 입력, 키보드 입력 등입니다. 텍스트 편집기 위젯에 텍스트를 입력할 때, 키보드 입력 이벤트가 발생하며 입력된 텍스트가 표시됩니다.

프로그램을 실행하려면 명령 프롬프트를 열고, .cpp 파일이 들어 있는 디렉터리로 이동하십시오. 다음 셸 명령을 입력하면 프로그램을 빌드합니다.

  1. qmake -project
  2. qmake
  3. make

이 명령을 실행하면 part1 디렉터리에 실행 파일이 생깁니다. (Windows 환경에서는 make 대신 nmake를 입력해야 할 수도 있습니다. 이 경우 part1/debug 또는 part1/release 폴더에 실행 파일이 생깁니다.) qmake는 Qt의 빌드 도구이며, 설정 파일을 인자로 받습니다. -project 인자를 주었을 때에는 프로젝트 설정 파일을 생성합니다. .pro 확장자를 가지는 설정 파일이 있으면 qmake는 프로그램을 빌드하는 Makefile을 생성합니다. .pro 파일을 만드는 법은 나중에 배울 것입니다.

더 배우기

종료 버튼 추가하기

진짜 프로그램은 위젯 하나만으로 만들 수 없습니다. 이번에는 텍스트 편집기에 QPushButton [doc.qt.nokia.com]을 추가해 볼 것입니다. 이 단추를 누르면 메모장을 끝냅니다.

코드를 봅시다.

  1. #include <QtGui>
  2.  
  3. int main(int argv, char **args)
  4. {
  5.     QApplication app(argv, args);
  6.  
  7.     QTextEdit textEdit;
  8.     QPushButton quitButton("Quit");
  9.  
  10.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  11.  
  12.     QVBoxLayout layout;
  13.     layout.addWidget(&textEdit);
  14.     layout.addWidget(&quitButton);
  15.  
  16.     QWidget window;
  17.     window.setLayout(&layout);
  18.  
  19.     window.show();
  20.  
  21.     return app.exec();
  22. }

첫 번째 줄에는 QtGui [doc.qt.nokia.com]가 있으며, 모든 Qt GUI 클래스를 포함합니다.

열 번째 줄은 Qt의 시그널과 슬롯을 사용하여, *닫기 단추*를 눌렀을 때 프로그램이 끝나도록 합니다. 슬롯은 런타임에서 이름을 문자열로서 호출할 수 있는 함수입니다. 시그널은 호출되었을 때 연결되어 있는 슬롯을 부르는 함수로, 시그널을 슬롯에 연결하고, 시그널을 내보낸다는 말을 사용합니다.

quit() [doc.qt.nokia.com]QApplication [doc.qt.nokia.com] 의 슬롯으로, 프로그램을 종료합니다. clicked() [doc.qt.nokia.com]QPushButton [doc.qt.nokia.com]의 시그널로, 눌렀을 때 내보냅니다. 정적 함수 QObject::connect [doc.qt.nokia.com]()는 시그널과 슬롯을 연결합니다. SIGNALSLOT 매크로는 연결할 시그널과 슬롯의 함수를 인자로 받는 매크로입니다. 시그널을 보내고 받는 개체의 포인터도 전달해야 합니다.

열 두번째 줄은 QVBoxLayout [doc.qt.nokia.com]을 만듭니다. 앞에서 이야기했듯이 위젯에는 또 다른 위젯이 들어갈 수 있습니다. 자식 위젯의 크기와 위치를 수동으로 지정할 수도 있으나, 레이아웃을 사용하면 더 쉽습니다. 레이아웃 위젯은 자식 위젯의 크기와 위치를 관리합니다. 여기에서 사용한 QVBoxLayout [doc.qt.nokia.com]은 자식 위젯을 수직으로 배치합니다.

13, 14번째 줄은 레이아웃에 텍스트 편집기와 단추를 추가합니다. 17번째 줄은 위젯에 레이아웃을 지정합니다.

더 배우기

QWidget 상속받기

프로그램을 끝낼 때 사용자가 진짜로 프로그램을 끝내고 싶은 지 물어봐야 할 때도 있습니다. 이 예제에서는 QWidget [doc.qt.nokia.com]의 하위 클래스를 만들고 *닫기 단추*에 연결할 슬롯을 정의합니다.

코드를 봅시다:

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4.  
  5. public:
  6.     Notepad();
  7.  
  8. private slots:
  9.     void quit();
  10.  
  11. private:
  12.     QTextEdit *textEdit;
  13.     QPushButton *quitButton;
  14. };

클래스 정의의 맨 위에는 Q_OBJECT 매크로가 들어가야 하며, 이는 이 클래스가 QObject임을 알려 줍니다. 이 매크로를 사용하려면 QObject [doc.qt.nokia.com]를 상속받아야 합니다. QObject [doc.qt.nokia.com]는 표준 C++ 클래스에 일부 기능을 추가합니다. 예를 들어 클래스 이름과 슬롯 이름을 실행 시간에 조회할 수 있습니다. 슬롯의 인자도 조회할 수 있으며 호출할 수 있습니다.

13번째 줄에서는 슬롯 quit()를 정의합니다. slots 매크로를 사용하여 정의할 수 있습니다. quit() 슬롯은 인자가 없는 시그널에 연결할 수 있습니다.

GUI를 구성하고 main() 함수에서 슬롯을 연결하는 대신, Notepad 클래스의 생성자를 사용할 것입니다.

  1. Notepad::Notepad()
  2. {
  3.     textEdit = new QTextEdit;
  4.     quitButton = new QPushButton(tr("Quit"));
  5.  
  6.     connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.  
  8.     QVBoxLayout *layout = new QVBoxLayout;
  9.     layout->addWidget(textEdit);
  10.     layout->addWidget(quitButton);
  11.  
  12.     setLayout(layout);
  13.  
  14.     setWindowTitle(tr("Notepad"));
  15. }

클래스 정의에서 볼 수 있듯이, QObject [doc.qt.nokia.com], quitButton)를 참조할 때에는 포인터를 사용합니다. 규칙으로, 항상 QObject [doc.qt.nokia.com]를 힙에 할당하고 절대 복사하지 마십시오.

이제 사용자에게 보이는 문자열을 tr()로 감쌀 것입니다. 영어와 한국어처럼 한 개 이상의 언어로 프로그램을 표시할 때 필요합니다. 여기에서는 다루지 않겠지만, ‘더 보기’에서 Qt Linguist에 대해서 더 알아보십시오.

더 보기

.pro 파일 만들기

이 예제에서는 qmake의 -project 옵션 대신 직접 .pro 파일을 만들 것입니다.

  1.        HEADERS =  notepad.h
  2.        SOURCES =  notepad.cpp \
  3.                   main.cpp

다음 셸 명령을 실행하면 빌드할 수 있습니다.

  1.        qmake
  2.        make

QMainWindow 사용하기

많은 프로그램은 QMainWindow [doc.qt.nokia.com]를 사용합니다. 자체 레이아웃을 가지고 있어서 메뉴 표시줄, 도킹 위젯, 도구 모음, 상태 표시줄 등을 포함할 수 있습니다. QMainWindow [doc.qt.nokia.com]의 중심 영역에는 아무 위젯이나 배치할 수 있으며, 여기에 텍스트 편집 컨트롤을 추가할 것입니다.

새로운 Notepad 클래스 정의를 봅시다.

  1. #include <QtGui>
  2.  
  3. class Notepad : public QMainWindow
  4. {
  5.     Q_OBJECT
  6.  
  7. public:
  8.     Notepad();
  9.  
  10.     private slots:
  11.         void open();
  12.         void save();
  13.         void quit();
  14.  
  15.     private:
  16.         QTextEdit *textEdit;
  17.  
  18.         QAction *openAction;
  19.         QAction *saveAction;
  20.         QAction *exitAction;
  21.  
  22.         QMenu *fileMenu;
  23. };

문서를 열고 저장하는 슬롯을 더 추가하였으며, 다음 절에서 다룰 것입니다.

주 창에서는 여러 위젯에서 한 슬롯을 호출할 수도 있습니다. 예를 들면 메뉴 항목과 도구 모음 항목입니다. 이를 더 쉽게 처리하기 위해서, Qt는 QAction [doc.qt.nokia.com]을 제공합니다. 여러 위젯에 포함시킬 수 있으며, 슬롯에 연결할 수 있습니다. QMenu [doc.qt.nokia.com]QToolBar [doc.qt.nokia.com]는 같은 QAction [doc.qt.nokia.com]을 사용하여 메뉴 항목과 도구 모음 단추를 만들 수 있습니다. 어떻게 사용하는지 잠깐 알아볼 것입니다.

앞에서 다룬 것처럼 Notepad 클래스의 생성자로 GUI를 설정합니다.

  1. Notepad::Notepad()
  2. {
  3.     saveAction = new QAction(tr("&Open"), this);
  4.     saveAction = new QAction(tr("&Save"), this);
  5.     exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.     connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.     connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.     fileMenu = menuBar()->addMenu(tr("&File"));
  12.     fileMenu->addAction(openAction);
  13.     fileMenu->addAction(saveAction);
  14.     fileMenu->addSeparator();
  15.     fileMenu->addAction(exitAction);
  16.  
  17.     textEdit = new QTextEdit;
  18.     setCentralWidget(textEdit);
  19.  
  20.     setWindowTitle(tr("Notepad"));
  21. }

QAction [doc.qt.nokia.com]은 위젯에 나타날 텍스트를 인자로 하여 생성됩니다. 이 경우에는 메뉴 항목 텍스트입니다. 도구 모음에 추가할 동작이라면 아이콘 [doc.qt.nokia.com]을 추가할 수 있습니다.

메뉴 항목이 눌리면 동작을 호출하며, 연결된 슬롯이 실행됩니다.

더 보기

저장하고 불러오기

이 예제에서는 앞 절에서 추가한 open()과 save() 슬롯의 기능을 구현할 것입니다.

open() 슬롯의 코드입니다.

  1. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::ReadOnly)) {
  7.         QMessageBox::critical(this, tr("Error"),
  8.         tr("Could not open file"));
  9.         return;
  10.     }
  11.     QString contents = file.readAll().constData();
  12.     textEdit->setPlainText(contents);
  13.     file.close();
  14. }

첫 단계는 사용자가 열 파일 이름을 물어보는 것입니다. Qt의 QFileDialog [doc.qt.nokia.com] 클래스는 사용자가 파일을 선택할 수 있는 대화 상자를 표시합니다. 위 그림은 쿠분투에서 실행한 파일 대화 상자입니다. 정적 함수 getOpenFileName() [doc.qt.nokia.com]은 모달 파일 대화상자를 표시하며, 사용자가 파일을 선택할 때까지 값을 반환하지 않습니다. 사용자가 선택을 취소하면 빈 문자열, 파일을 선택하면 파일의 경로를 반환합니다.

파일 이름이 있으면 open() [doc.qt.nokia.com]으로 파일을 열 것입니다. 파일을 열 수 있으면 true를 반환합니다. 오류 처리에 대해서 알아 보려면 ‘더 보기’를 참고하십시오. 파일을 열 수 없으면 QMessageBox [doc.qt.nokia.com]를 사용하여 오류 메시지를 표시합니다. 더 자세한 사항은 QMessageBox [doc.qt.nokia.com] 클래스 문서를 참고하십시오.

실제 데이터를 읽는 역할은 readAll() [doc.qt.nokia.com] 함수가 담당합니다. 이 함수는 파일의 모든 데이터를 QByteArray [doc.qt.nokia.com]에 저장합니다. constData() [doc.qt.nokia.com] 함수는 배열에 들어 있는 데이터를 const char * 형태로 반환하며, 이는 QString [doc.qt.nokia.com]의 생성자에 인자로 넘겨줄 수 있습니다. 이제 텍스트 편집기에 파일 내용을 보여 주며, 파일을 닫기 위해서 close() [doc.qt.nokia.com] 함수를 호출합니다.

이제 save() 슬롯을 봅시다.

  1. QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::WriteOnly)) {
  7.     // error message
  8.     } else {
  9.         QTextStream stream(&file);
  10.         stream << textEdit->toPlainText();
  11.         stream.flush();
  12.         file.close();
  13.     }
  14. }

텍스트 편집기의 내용을 파일로 쓸 때에는 QFile [doc.qt.nokia.com]을 둘러싸는 QTextStream [doc.qt.nokia.com] 클래스를 사용합니다. QFile [doc.qt.nokia.com]write() [doc.qt.nokia.com] 함수는 생(raw) 데이터(char *)만 받아들이며, QTextStream은 QString을 파일에 바로 쓸 수 있습니다.

더 보기
  • 파일과 I/O 장치: QFile [doc.qt.nokia.com], QIODevice [doc.qt.nokia.com]