본문으로 건너뛰기 C++ GUI | Beginner Guide to the Qt Framework

C++ GUI | Beginner Guide to the Qt Framework

C++ GUI | Beginner Guide to the Qt Framework

이 글의 핵심

Core concepts and practical notes for building C++ GUIs with Qt: first app, signals/slots, layouts, widgets, and common pitfalls.

Your first Qt application

#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();
}

Build:

Run the following in a terminal.

# Using qmake
qmake -project
qmake
make

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

Signals and slots

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

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    QPushButton button("Click me");
    
    // Signal–slot connection
    QObject::connect(&button, &QPushButton::clicked, [] {
        QMessageBox::information(nullptr, "Notice", "The button was clicked!");
    });
    
    button.show();
    
    return app.exec();
}

Layouts

Vertical layout

#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("Button 1"));
    layout->addWidget(new QPushButton("Button 2"));
    layout->addWidget(new QPushButton("Button 3"));
    
    window.show();
    
    return app.exec();
}

Horizontal layout

QHBoxLayout *layout = new QHBoxLayout(&window);
layout->addWidget(new QPushButton("Left"));
layout->addWidget(new QPushButton("Middle"));
layout->addWidget(new QPushButton("Right"));

Grid layout

Example C/C++ code.

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);

Custom widgets

#include <QApplication>
#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("Count: 0", this);
        button = new QPushButton("Increment", 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("Count: %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() {
        // Menus
        QMenu *fileMenu = menuBar()->addMenu("File");
        QMenu *editMenu = menuBar()->addMenu("Edit");
        
        // Actions
        QAction *newAction = new QAction("New", this);
        QAction *openAction = new QAction("Open", this);
        QAction *saveAction = new QAction("Save", this);
        
        // Add actions to the menu
        fileMenu->addAction(newAction);
        fileMenu->addAction(openAction);
        fileMenu->addSeparator();
        fileMenu->addAction(saveAction);
        
        // Toolbar
        QToolBar *toolbar = addToolBar("Main toolbar");
        toolbar->addAction(newAction);
        toolbar->addAction(openAction);
        toolbar->addAction(saveAction);
        
        // Connections
        connect(newAction, &QAction::triggered, this, &MainWindow::newFile);
        connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
    }
    
private slots:
    void newFile() {
        QMessageBox::information(this, "Notice", "Create new file");
    }
    
    void openFile() {
        QMessageBox::information(this, "Notice", "Open file");
    }
};

Practical examples

Example 1: Simple text editor

#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("Simple text editor");
        resize(800, 600);
    }
    
private:
    void createMenus() {
        QMenu *fileMenu = menuBar()->addMenu("File");
        
        QAction *openAction = fileMenu->addAction("Open");
        connect(openAction, &QAction::triggered, this, &TextEditor::openFile);
        
        QAction *saveAction = fileMenu->addAction("Save");
        connect(saveAction, &QAction::triggered, this, &TextEditor::saveFile);
        
        fileMenu->addSeparator();
        
        QAction *exitAction = fileMenu->addAction("Exit");
        connect(exitAction, &QAction::triggered, this, &QWidget::close);
    }
    
private slots:
    void openFile() {
        QString filename = QFileDialog::getOpenFileName(this, "Open file");
        
        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, "Save file");
        
        if (!filename.isEmpty()) {
            QFile file(filename);
            if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
                QTextStream out(&file);
                out << textEdit->toPlainText();
                file.close();
            }
        }
    }
};

Example 2: Calculator

#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);
        
        // Number buttons
        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);
            }
        }
        
        // Operator buttons
        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';
    }
};

Example 3: Image viewer

#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("Image viewer");
        resize(800, 600);
    }
    
private:
    void createMenus() {
        QMenu *fileMenu = menuBar()->addMenu("File");
        
        QAction *openAction = fileMenu->addAction("Open");
        connect(openAction, &QAction::triggered, this, &ImageViewer::openImage);
    }
    
private slots:
    void openImage() {
        QString filename = QFileDialog::getOpenFileName(
            this, "Open image", "", "Images (*.png *.jpg *.bmp)");
        
        if (!filename.isEmpty()) {
            QPixmap image(filename);
            imageLabel->setPixmap(image);
            imageLabel->resize(image.size());
        }
    }
};

Common issues

Issue 1: Q_OBJECT macro errors

Symptom: Errors mentioning moc

Cause: Classes using Q_OBJECT must be processed by the Meta-Object Compiler (moc).

Fix: Use qmake or CMake so moc runs for those headers/sources.

Issue 2: Signal–slot connection does nothing

Symptom: Clicks have no effect

Cause: Wrong signal/slot names or mismatched connection style

Fix: Prefer compile-time checked connections

// ❌ Runtime string-based check
connect(button, SIGNAL(clicked()), this, SLOT(onClicked()));

// ✅ Compile-time checked
connect(button, &QPushButton::clicked, this, &MyClass::onClicked);

Issue 3: Memory leaks

Symptom: Growing memory use

Cause: Widgets not tied to a parent hierarchy

Fix: Set a parent so Qt deletes children automatically

// ✅ With a parent, destruction is automatic
QPushButton *button = new QPushButton("Button", parentWidget);

FAQ

Q1: Qt vs other GUI frameworks?

A:

  • Qt: Cross-platform, large feature set
  • wxWidgets: Native look and feel
  • GTK: Common on Linux

Q2: Which Qt version?

A: Prefer the latest Qt 6; Qt 5 is still widely used.

Q3: Qt Creator vs Visual Studio?

A: Qt Creator is tailored for Qt workflows; Visual Studio works too with the right Qt integration.

Q4: Do I need a commercial license?

A: Open-source projects can use Qt under LGPL/GPL terms; check Qt licensing for your use case.

Q5: Qt Quick vs Qt Widgets?

A:

  • Qt Widgets: Traditional desktop UI, mature
  • Qt Quick: Declarative UI, strong for fluid and mobile-style interfaces

Q6: Where to learn Qt?

A:

  • Official docs: doc.qt.io
  • Qt Examples in the SDK
  • Books such as C++ GUI Programming with Qt

More on this topic:

See also