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();
}
Menus and toolbars
#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
Related posts (internal)
- C++ cross-platform GUI | Qt basics guide [#36-2]
- Modern C++ GUI: Dear ImGui for debug tools and dashboards [#36-1]
- C++ design patterns | Singleton, Factory, Observer in practice
See also
- C++ cross-platform GUI | Qt basics guide [#36-2]
- Modern C++ GUI: Dear ImGui for debug tools and dashboards [#36-1]