C++ GUI | "Qt 프레임워크" 초보자 가이드

C++ GUI | "Qt 프레임워크" 초보자 가이드

이 글의 핵심

C++ GUI에 대해 정리한 개발 블로그 글입니다. #include <QApplication> #include <QPushButton>

첫 번째 Qt 애플리케이션

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    QPushButton button("Hello Qt!");
    button.resize(200, 100);
    button.show();
    
    return app.exec();
}

빌드:

# qmake 사용
qmake -project
qmake
make

# CMake 사용
find_package(Qt6 REQUIRED COMPONENTS Widgets)
target_link_libraries(myapp Qt6::Widgets)

시그널과 슬롯

#include <QApplication>
#include <QPushButton>
#include <QMessageBox>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    QPushButton button("클릭하세요");
    
    // 시그널-슬롯 연결
    QObject::connect(&button, &QPushButton::clicked,  {
        QMessageBox::information(nullptr, "알림", "버튼이 클릭되었습니다!");
    });
    
    button.show();
    
    return app.exec();
}

레이아웃

수직 레이아웃

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);
    
    layout->addWidget(new QPushButton("버튼 1"));
    layout->addWidget(new QPushButton("버튼 2"));
    layout->addWidget(new QPushButton("버튼 3"));
    
    window.show();
    
    return app.exec();
}

수평 레이아웃

QHBoxLayout *layout = new QHBoxLayout(&window);
layout->addWidget(new QPushButton("왼쪽"));
layout->addWidget(new QPushButton("중간"));
layout->addWidget(new QPushButton("오른쪽"));

그리드 레이아웃

QGridLayout *layout = new QGridLayout(&window);
layout->addWidget(new QPushButton("1"), 0, 0);
layout->addWidget(new QPushButton("2"), 0, 1);
layout->addWidget(new QPushButton("3"), 1, 0);
layout->addWidget(new QPushButton("4"), 1, 1);

커스텀 위젯

#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>

class CounterWidget : public QWidget {
    Q_OBJECT
    
private:
    int count;
    QLabel *label;
    QPushButton *button;
    
public:
    CounterWidget(QWidget *parent = nullptr) : QWidget(parent), count(0) {
        label = new QLabel("카운트: 0", this);
        button = new QPushButton("증가", this);
        
        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(label);
        layout->addWidget(button);
        
        connect(button, &QPushButton::clicked, this, &CounterWidget::increment);
    }
    
private slots:
    void increment() {
        count++;
        label->setText(QString("카운트: %1").arg(count));
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    CounterWidget widget;
    widget.show();
    
    return app.exec();
}

메뉴와 툴바

#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>

class MainWindow : public QMainWindow {
    Q_OBJECT
    
public:
    MainWindow() {
        // 메뉴 생성
        QMenu *fileMenu = menuBar()->addMenu("파일");
        QMenu *editMenu = menuBar()->addMenu("편집");
        
        // 액션 생성
        QAction *newAction = new QAction("새 파일", this);
        QAction *openAction = new QAction("열기", this);
        QAction *saveAction = new QAction("저장", this);
        
        // 메뉴에 액션 추가
        fileMenu->addAction(newAction);
        fileMenu->addAction(openAction);
        fileMenu->addSeparator();
        fileMenu->addAction(saveAction);
        
        // 툴바 생성
        QToolBar *toolbar = addToolBar("메인 툴바");
        toolbar->addAction(newAction);
        toolbar->addAction(openAction);
        toolbar->addAction(saveAction);
        
        // 시그널 연결
        connect(newAction, &QAction::triggered, this, &MainWindow::newFile);
        connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
    }
    
private slots:
    void newFile() {
        QMessageBox::information(this, "알림", "새 파일 생성");
    }
    
    void openFile() {
        QMessageBox::information(this, "알림", "파일 열기");
    }
};

실전 예시

예시 1: 간단한 텍스트 에디터

#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>

class TextEditor : public QMainWindow {
    Q_OBJECT
    
private:
    QTextEdit *textEdit;
    
public:
    TextEditor() {
        textEdit = new QTextEdit(this);
        setCentralWidget(textEdit);
        
        createMenus();
        
        setWindowTitle("간단한 텍스트 에디터");
        resize(800, 600);
    }
    
private:
    void createMenus() {
        QMenu *fileMenu = menuBar()->addMenu("파일");
        
        QAction *openAction = fileMenu->addAction("열기");
        connect(openAction, &QAction::triggered, this, &TextEditor::openFile);
        
        QAction *saveAction = fileMenu->addAction("저장");
        connect(saveAction, &QAction::triggered, this, &TextEditor::saveFile);
        
        fileMenu->addSeparator();
        
        QAction *exitAction = fileMenu->addAction("종료");
        connect(exitAction, &QAction::triggered, this, &QWidget::close);
    }
    
private slots:
    void openFile() {
        QString filename = QFileDialog::getOpenFileName(this, "파일 열기");
        
        if (!filename.isEmpty()) {
            QFile file(filename);
            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                QTextStream in(&file);
                textEdit->setText(in.readAll());
                file.close();
            }
        }
    }
    
    void saveFile() {
        QString filename = QFileDialog::getSaveFileName(this, "파일 저장");
        
        if (!filename.isEmpty()) {
            QFile file(filename);
            if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
                QTextStream out(&file);
                out << textEdit->toPlainText();
                file.close();
            }
        }
    }
};

예시 2: 계산기

#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>

class Calculator : public QWidget {
    Q_OBJECT
    
private:
    QLineEdit *display;
    double currentValue;
    char currentOp;
    
public:
    Calculator() : currentValue(0), currentOp('\0') {
        display = new QLineEdit("0", this);
        display->setReadOnly(true);
        display->setAlignment(Qt::AlignRight);
        
        QGridLayout *layout = new QGridLayout(this);
        layout->addWidget(display, 0, 0, 1, 4);
        
        // 숫자 버튼
        for (int i = 0; i < 10; i++) {
            QPushButton *btn = new QPushButton(QString::number(i), this);
            connect(btn, &QPushButton::clicked, this, &Calculator::digitClicked);
            int row = (9 - i) / 3 + 1;
            int col = (i - 1) % 3;
            if (i == 0) {
                layout->addWidget(btn, 4, 1);
            } else {
                layout->addWidget(btn, row, col);
            }
        }
        
        // 연산자 버튼
        QPushButton *addBtn = new QPushButton("+", this);
        connect(addBtn, &QPushButton::clicked, this, &Calculator::operatorClicked);
        layout->addWidget(addBtn, 1, 3);
        
        QPushButton *equalBtn = new QPushButton("=", this);
        connect(equalBtn, &QPushButton::clicked, this, &Calculator::equalClicked);
        layout->addWidget(equalBtn, 4, 3);
        
        resize(300, 400);
    }
    
private slots:
    void digitClicked() {
        QPushButton *btn = qobject_cast<QPushButton*>(sender());
        QString digit = btn->text();
        
        if (display->text() == "0") {
            display->setText(digit);
        } else {
            display->setText(display->text() + digit);
        }
    }
    
    void operatorClicked() {
        QPushButton *btn = qobject_cast<QPushButton*>(sender());
        currentOp = btn->text().at(0).toLatin1();
        currentValue = display->text().toDouble();
        display->setText("0");
    }
    
    void equalClicked() {
        double secondValue = display->text().toDouble();
        double result = 0;
        
        switch (currentOp) {
            case '+': result = currentValue + secondValue; break;
            case '-': result = currentValue - secondValue; break;
            case '*': result = currentValue * secondValue; break;
            case '/': result = currentValue / secondValue; break;
        }
        
        display->setText(QString::number(result));
        currentOp = '\0';
    }
};

예시 3: 이미지 뷰어

#include <QMainWindow>
#include <QLabel>
#include <QMenuBar>
#include <QFileDialog>
#include <QPixmap>
#include <QScrollArea>

class ImageViewer : public QMainWindow {
    Q_OBJECT
    
private:
    QLabel *imageLabel;
    QScrollArea *scrollArea;
    
public:
    ImageViewer() {
        imageLabel = new QLabel;
        imageLabel->setScaledContents(true);
        
        scrollArea = new QScrollArea;
        scrollArea->setWidget(imageLabel);
        setCentralWidget(scrollArea);
        
        createMenus();
        
        setWindowTitle("이미지 뷰어");
        resize(800, 600);
    }
    
private:
    void createMenus() {
        QMenu *fileMenu = menuBar()->addMenu("파일");
        
        QAction *openAction = fileMenu->addAction("열기");
        connect(openAction, &QAction::triggered, this, &ImageViewer::openImage);
    }
    
private slots:
    void openImage() {
        QString filename = QFileDialog::getOpenFileName(
            this, "이미지 열기", "", "Images (*.png *.jpg *.bmp)");
        
        if (!filename.isEmpty()) {
            QPixmap image(filename);
            imageLabel->setPixmap(image);
            imageLabel->resize(image.size());
        }
    }
};

자주 발생하는 문제

문제 1: Q_OBJECT 매크로 에러

증상: moc 관련 에러

원인: Q_OBJECT 매크로 사용 시 moc 실행 필요

해결법: qmake 또는 CMake 사용

문제 2: 시그널-슬롯 연결 안됨

증상: 클릭해도 반응 없음

원인: 잘못된 시그널/슬롯 이름

해결법: 컴파일 타임 체크 사용

// ❌ 런타임 체크
connect(button, SIGNAL(clicked()), this, SLOT(onClicked()));

// ✅ 컴파일 타임 체크
connect(button, &QPushButton::clicked, this, &MyClass::onClicked);

문제 3: 메모리 누수

증상: 메모리 증가

원인: 위젯 삭제 안함

해결법: 부모 위젯 설정

// ✅ 부모 설정하면 자동 삭제
QPushButton *button = new QPushButton("버튼", parentWidget);

FAQ

Q1: Qt vs 다른 GUI 프레임워크?

A:

  • Qt: 크로스 플랫폼, 풍부한 기능
  • wxWidgets: 네이티브 룩앤필
  • GTK: 리눅스 중심

Q2: Qt 버전은?

A: Qt 6 최신 버전 사용 권장 (Qt 5도 여전히 많이 사용)

Q3: Qt Creator vs Visual Studio?

A: Qt Creator가 Qt 개발에 최적화되어 있습니다.

Q4: 상용 라이선스 필요한가요?

A: 오픈소스 프로젝트는 무료 (LGPL/GPL)

Q5: Qt Quick vs Qt Widgets?

A:

  • Qt Widgets: 전통적, 안정적
  • Qt Quick: 모던, 모바일 친화적

Q6: Qt 학습 리소스는?

A:

  • 공식 문서: doc.qt.io
  • Qt Examples
  • “C++ GUI Programming with Qt” 책

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ 크로스 플랫폼 GUI | Qt 기초 완벽 가이드 [#36-2]
  • C++ 현대적인 C++ GUI: Dear ImGui로 디버깅 툴·대시보드 만들기 [#36-1]
  • C++ 디자인 패턴 | “싱글톤/팩토리/옵저버” 실전 가이드

관련 글

  • C++ 크로스 플랫폼 GUI | Qt 기초 완벽 가이드 [#36-2]
  • C++ 현대적인 C++ GUI: Dear ImGui로 디버깅 툴·대시보드 만들기 [#36-1]