들어가며
이전 글에서 QML의 이론적 배경을 정리했다.
런타임 리플렉션을 활용한 pub/sub 구조와,
프레임 기반 렌더링을 통한 최적화 개념을 살펴보며
QML이 QWidget보다 충분히 경쟁력 있는 기술이라는 인상을 받았다.
이번 글에서는 이론에 그치지 않고,
실제로 QML을 지원하는 Qt프로젝트를 만들어 보며
실무 프로젝트에 도입할 수 있을지 검토한 과정을 기록해 보려 한다.
이전 글 : 2026.02.02 - [Qt] - Qt QML 내부 동작 원리 이해하기 ㅡ Property Binding, Event Loop, Scene Graph
Qt QML 내부 동작 원리 이해하기 ㅡ Property Binding, Event Loop, Scene Graph
들어가며QtFramework에 대해 공부하며 QEventLoop, QObjec, moc(Meta Object Compiler)에 대해 공부했다.이 글은 지금까지의 내용을 배경지식을 활용하여 QML에 대해 공부해 보려 한다.2026.01.23 - [Qt] - Qt Event Loop
prejudice.tistory.com
설치 과정에서 만난 문제
QML Project는 기본적으로 CMake 기반으로 구성된다.
Qt Creator에서 Quick Application 템플릿으로 프로젝트를 생성하자마자
다음과 같은 CMake 에러를 마주쳤다.
Failed to scan target [] for QML imports: 1

처음에는 사용 중인 컴파일러가 QML을 지원하지 않는 문제라고 생각했다.
Qt에서 제공하는 최신 MinGW를 새로 설치해도 동일한 에러가 발생하면서 다른 원인이 있는 것을 알았다.
결론적으로 문제의 원인은 프로젝트 경로에 포함된 한글이었다.
나는 다음과 같은 경로에서 프로젝트를 생성했다.
~/OneDriver/문서/qml_test
QML 빌드 과정에서 사용되는 qmlimportscanner가 한글 경로를 정상적으로 처리하지 못해 발생한 문제였다.
프로젝트 경로를 변경하자 프로젝트가 정상적으로 생성됐다.
QML 프로젝트 생성
QML을 사용하는 프로젝트는 Qt Creator에서
Qt Quick Application 템플릿을 이용하면 쉽게 생성할 수 있다.
이 방식으로 생성하면 QML 엔진, Scene Graph, CMake 설정까지 기본 구성이 자동으로 준비된다.

QML 객체 생성 (C++ → QML)
이전 글에서 배운 대로 QML객체는 QML타입 + Property + Binding + Signal로 구성된다.
C++ Class를 QML객체로 만들기 위해 필요한 과정은 다음과 같다.
- QObject 상속 및 Q_OBJECT매크로 선언
- Q_ELEMENT로 class가 QML타입임을 명시 (Qt6부터 지원)
- Q_PROPERTY로 바인딩 가능한 프로퍼티 선언
Qt Creator의 클래스 생성 기능을 이용해 ProgramStatus 클래스를 생성했다.

CMakeLists.txt를 살펴보면,
해당 클래스가 qt_add_qml_moculde()에 자동으로 포함된 것을 확인할 수 있다.
qt_add_qml_module(appqml_test
URI qml_test
VERSION 1.0
QML_FILES
Main.qml
SOURCES ProgramStatus.h ProgramStatus.cpp
)
QML객체 설계
프로그램 상태를 표현하기 위해 다음과 같은 요구사항을 가정했다.
- 온라인 상태 여부(bool)
- 실행 후 경과 시간 (초 단위)
이를 위해 ProgramStatus 클래스를 다음과 같이 설계했다.
런타임 리플렉션으로 실행될 signal을 추가하고,
Q_PROPERTY매크로를 통해 signal과 함수를 바인딩한다.
#ifndef PROGRAMSTATUS_H
#define PROGRAMSTATUS_H
#include <QObject>
#include <QQmlEngine>
#include <QElapsedTimer>
class QTimer;
class ProgramStatus : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
Q_PROPERTY(int elapsedSeconds READ elapsedSeconds NOTIFY elapsedSecondsChanged)
public:
explicit ProgramStatus(QObject *parent = nullptr);
bool online() const { return m_online; }
int elapsedSeconds() const;
signals:
void onlineChanged();
void elapsedSecondsChanged();
private:
bool m_online = false;
QElapsedTimer m_elapsedTimer;
QTimer* m_timer = nullptr;
};
#endif // PROGRAMSTATUS_H
생성자에서 1초마다 elapsedSecondsChanged()를 emit 하도록 만들고,
바인딩된 elapsedSeconds함수에서는 경과시간을 반환하도록 기능을 구현해 보자.
#include "ProgramStatus.h"
#include <QTimer>
ProgramStatus::ProgramStatus(QObject *parent)
: QObject{parent}
{
m_online = true;
m_timer = new QTimer(this);
m_timer->setInterval(1000);
m_elapsedTimer->start();
connect(m_timer, &QTimer::timeout, this, [this]() {
emit elapsedSecondsChanged();
});
m_timer->start();
}
int ProgramStatus::elapsedSeconds() const
{
return m_elapsedTimer.elapsed() / 1000;
}
문법에 문제가 없다면, 빌드 과정에서
moc를 통해 메타 객체 코드가 자동으로 생성된다.

Qt 5와 Qt6의 QML 타입 등록 차이
Qt 6에서는 QML_ELEMENT 매크로를 통해
C++ 클래스를 QML 타입으로 선언할 수 있다.
반면 Qt 5에서는 다음과 같은 방식으로 직접 등록했다.
*여기서 (..., 1, 0 ,...) 은 QML 모듈의 버전(major, minor)을 의미하며,qmlRegisterType<ProgramStatus>("MyModule", 1, 0, "MyObject");
QML에서는 다음과 같이 사용된다.
import MyModule 1.0
QML타입 사용 (.qml)
우선 QWidget개념이 익숙했던 나에게, 이해하기 제일 까다로웠던 부분이다.
매타 오브젝트 코드를 만들었다면 .qml 확장자를 가지는 타입에서 사용해 보자.
CMakeLists.txt에 정의된 URI를 이용해
QML 파일에서 C++객체를 사용할 수 있다.
ProgramStatus 객체를 stat이라는 id로 생성하고 값을 출력하는 기능을 추가했다.
내부적으로는 다음과 같은 코드가 실행되고 있을 것을 상상해 볼 수 있다.
QMetaProperty::read( ProgramStatus, "online", Qt::DirectConnection );
import QtQuick
import qml_test 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
ProgramStatus {
id: stat
}
Text {
anchors.centerIn: parent
text: stat.online? "Online" : "Offline"
color: stat.online ? "green" : "red"
}
Text {
text: "Elapsed time: " + stat.elapsedSeconds + "sec"
font.pixelSize: 20
}
}

QML 내부 동작 흐름
프로그램을 만들고 나서 한 가지 의문이 들었다.
- elapsedSecondsChanged 시그널은 1초마다 발생한다.
- 그런데 onlineChanged 시그널은 호출한 적이 없는데 UI에는 값이 표시된다.
이 해답은 QML 객체 생성 시점의 동작 방식에 있었다.
ProgramStatus {
id: stat
}
이 코드가 실행될 때 QML 엔진은 내부적으로는 다음과 같은 과정을 거친다.
- ProgramStatus C++ 객체 생성
- online 프로퍼티를 UI에 바인딩
- 초기 값 읽기 → online() 호출
- UI에 초기 상태 반영
따라서 생성자가 아닌 곳에서 m_online 상태를 변경하면 UI에 적용되지 않는다는 주의점과,
UI에 반영하기 위해서는 onlineChanged 시그널이 필요하다는 사실을 알 수 있었다.
회고
지금까지 QWidget 기반과 MFC 위주로 개발해 온 나에게
선언형 UI인 QML은 상당히 새로운 경험이었다.
실제 프로젝트에 도입할 수 있을지를 고민하며 연습해 본 결과,
다음과 같은 장단점을 찾을 수 있었다.
- 프레임 기반 렌더링으로 UI 성능 최적화에 유리함
- 선언형 구조로 UI 의도 명확
- QML로직이 무거워지지 않도록 설계 주의 필요
- QObject 의존도가 높아 구조 분리가 중요
- 복잡한 UI에서 Designer 사용이 불편
- Qt5 이상 환경 필요
그럼에도 불구하고,
래거시 프로젝트에 QML 부분도입이 가능하다는 점,
선언형 UI라는 선택지가 늘어났다는 점은
앞으로의 개발에 큰 도움이 될 것이라 느꼈다.
부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.
직접 공부해서 다음 글로 정리해 보려고 합니다.
'Qt' 카테고리의 다른 글
| QML Stopwatch 구현 따라 하기 - MVVM 구조로 설계한 Qt 아키텍처 (0) | 2026.02.20 |
|---|---|
| Qt QML 내부 동작 원리 이해하기 ㅡ Property Binding, Event Loop, Scene Graph (0) | 2026.02.02 |
| Qt QObject와 moc의 동작 원리 이해하기 ㅡ Signal/Slot과 런타임 리플렉션 (0) | 2026.01.28 |
| Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지 (0) | 2026.01.23 |
| Qt IPC 성능 비교 실험기 ㅡ QSharedMemory, QLocalSocket, QTcpSocket, QRemoteObject (1) | 2026.01.19 |