C++ 애플리케이션에서 Qt Widgets Designer UI 파일 사용하기
Qt Widgets Designer UI 파일은 양식의 위젯 트리를 XML 형식으로 나타냅니다. 양식은 다음과 같은 방식으로 처리될 수 있습니다:
- 컴파일 시: 이 경우 폼이 컴파일 가능한 C++ 코드로 변환됩니다.
- 실행 시: XML 파일을 파싱하면서 위젯 트리를 동적으로 생성하는 QUiLoader 클래스에 의해 폼이 처리됩니다.
컴파일 시점 양식 처리
Qt Widgets Designer 를 사용하여 사용자 인터페이스 컴포넌트를 생성하고, 애플리케이션을 빌드할 때 Qt의 통합 빌드 도구인 qmake와 uic를 사용하여 해당 컴포넌트에 대한 코드를 생성합니다. 생성된 코드에는 폼의 사용자 인터페이스 객체가 포함됩니다. 이는 다음을 포함하는 C++ 구조체입니다:
- 폼의 위젯, 레이아웃, 레이아웃 항목, 버튼 그룹 및 액션에 대한 포인터.
- 부모 위젯에 위젯 트리를 구축하는 `
setupUi()`이라는 멤버 함수. - 폼의 문자열 속성 변환을 처리하는 `
retranslateUi()`라는 멤버 함수. 자세한 내용은 ‘언어 변경에 대응하기’를 참조하십시오.
생성된 코드는 애플리케이션에 포함시켜 직접 사용할 수 있습니다. 또는 이 코드를 사용하여 표준 위젯의 하위 클래스를 확장할 수도 있습니다.
컴파일 시점에 처리된 양식은 다음 방법 중 하나를 사용하여 애플리케이션에서 사용할 수 있습니다:
- 직접 방식: 컴포넌트의 자리 표시자로 사용할 위젯을 생성하고, 그 내부에 사용자 인터페이스를 구성합니다.
- 단일 상속 방식: 폼의 기본 클래스(예: `QWidget ` 또는 ` QDialog`)를 상속받은 서브클래스를 만들고, 폼 사용자 인터페이스 객체의 비공개 인스턴스를 포함합니다.
- 다중 상속 방식: 폼의 기본 클래스와 폼의 사용자 인터페이스 객체 모두를 상속받아 서브클래스를 만듭니다. 이를 통해 폼에 정의된 위젯을 서브클래스 범위 내에서 직접 사용할 수 있습니다.
이를 시연하기 위해 간단한 계산기 양식(Calculator Form) 애플리케이션을 만들어 보겠습니다. 이 애플리케이션은 원래의 계산기 양식 예제를 기반으로 합니다.
이 애플리케이션은 하나의 소스 파일( main.cpp )과 하나의 UI 파일로 구성됩니다.
Qt Widgets Designer 로 디자인된 calculatorform.ui 파일은 다음과 같습니다:

CMake 를 사용하여 실행 파일을 빌드할 때는 CMakeLists.txt 파일이 필요합니다:
cmake_minimum_required(VERSION 3.16)
project(calculatorform LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_add_executable(calculatorform
calculatorform.ui main.cpp)
set_target_properties(calculatorform PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(calculatorform PUBLIC
Qt::Core
Qt::Gui
Qt::Widgets
)이 양식은 qt_add_executable() 의 C++ 소스 파일 목록에 포함되어 있습니다. ‘ CMAKE_AUTOUIC ’ 옵션은 CMake 에 uic 도구를 실행하여 소스 파일에서 사용할 수 있는 ui_calculatorform.h 파일을 생성하도록 지시합니다.
qmake 를 사용하여 실행 파일을 빌드할 때는 .pro 파일이 필요합니다:
TEMPLATE = app
FORMS = calculatorform.ui
SOURCES = main.cpp이 파일의 특별한 점은 qmake 에 uic 로 처리할 파일을 알려주는 FORMS 선언이 포함되어 있다는 것입니다. 이 경우, calculatorform.ui 파일은 SOURCES 선언에 나열된 모든 파일에서 사용할 수 있는 ui_calculatorform.h 파일을 생성하는 데 사용됩니다.
참고: Qt Creator 를 사용하여 Calculator Form 프로젝트를 생성할 수있습니다 . 이 도구는 main.cpp, UI 및 원하는 빌드 도구에 맞는 프로젝트 파일을 자동으로 생성하며, 사용자는 이를 수정할 수 있습니다.
직접적인 방법
직접적인 방법을 사용하려면 main.cpp 파일에 ui_calculatorform.h 파일을 직접 포함시킵니다:
#include "ui_calculatorform.h"main 함수는 calculatorform.ui 파일에 정의된 사용자 인터페이스를 호스팅하는 데 사용되는 표준 QWidget 객체를 생성하여 계산기 위젯을 만듭니다.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget widget;
Ui::CalculatorForm ui;
ui.setupUi(&widget);
widget.show();
return app.exec();
}이 경우, ` Ui::CalculatorForm `는 ` ui_calculatorform.h ` 파일에 정의된 인터페이스 설명 객체로, 대화 상자의 모든 위젯과 신호 및 슬롯 간의 연결을 설정합니다.
직접적인 접근 방식은 애플리케이션에서 간단하고 독립적인 컴포넌트를 빠르고 쉽게 사용할 수 있는 방법을 제공합니다. 그러나 Qt Widgets Designer 로 생성된 컴포넌트는 종종 나머지 애플리케이션 코드와 긴밀하게 통합되어야 합니다. 예를 들어, 위에 제공된 CalculatorForm 코드는 컴파일되고 실행되지만, QSpinBox 객체들은 QLabel 와 상호작용하지 않습니다. 이는 더하기 연산을 수행하고 결과를 QLabel 에 표시하기 위한 사용자 정의 슬롯이 필요하기 때문입니다. 이를 구현하려면 단일 상속 방식을 사용해야 합니다.
단일 상속 방식
단일 상속 방식을 사용하려면 표준 Qt Widgets 위젯을 상속받아 폼의 사용자 인터페이스 객체의 비공개 인스턴스를 포함시킵니다. 이는 다음과 같은 형태를 취할 수 있습니다:
- 멤버 변수
- 포인터 멤버 변수
멤버 변수 사용
이 접근 방식에서는 Qt Widgets를 상속받아 생성자 내에서 사용자 인터페이스를 설정합니다. 이러한 방식으로 사용되는 컴포넌트는 폼에 사용된 위젯과 레이아웃을 Qt Widgets 서브클래스에 노출하며, 사용자 인터페이스와 애플리케이션 내의 다른 객체 간에 신호 및 슬롯 연결을 설정하기 위한 표준 시스템을 제공합니다. 생성된 ` Ui::CalculatorForm ` 구조체는 해당 클래스의 멤버입니다.
이 방식은 ‘Calculator Form ’ 예제에서 사용됩니다.
사용자 인터페이스를 사용할 수 있도록 하려면, Ui::CalculatorForm 를 참조하기 전에 uic 가 생성하는 헤더 파일을 포함해야 합니다:
#include "ui_calculatorform.h"프로젝트 파일을 업데이트하여 calculatorform.h 를 포함시켜야 합니다. CMake 의 경우:
qt_add_executable(calculatorform
calculatorform.cpp calculatorform.h calculatorform.ui
main.cpp
)아래 예시와 같이 include 지시어가 상대 경로를 사용하는 특정 경우에는, AUTOUIC에 의존하는 대신 qt_add_ui() 를 사용하여 ui_calculatorform.h 파일을 생성할 수 있습니다.
AUTOUIC 대신 qt_add_ui를 사용해야 하는 경우
#include "src/files/ui_calculatorform.h"qt_add_ui(calculatorform SOURCES calculatorform.ui
INCLUDE_PREFIX src/files)qmake 의 경우:
HEADERS = calculatorform.h이 서브클래스는 다음과 같이 정의됩니다:
class CalculatorForm : public QWidget
{
Q_OBJECT
public:
explicit CalculatorForm(QWidget *parent = nullptr);
private slots:
void updateResult();
private:
Ui::CalculatorForm ui;
};이 클래스의 중요한 특징은 사용자 인터페이스를 설정하고 관리하는 코드를 제공하는 비공개 ` ui ` 객체입니다.
이 하위 클래스의 생성자는 ` ui ` 객체의 ` setupUi() ` 함수를 호출하는 것만으로 대화 상자의 모든 위젯과 레이아웃을 생성하고 구성합니다. 이 작업이 완료되면 필요에 따라 사용자 인터페이스를 수정할 수 있습니다.
CalculatorForm::CalculatorForm(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
connect(ui.inputSpinBox1, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult);
connect(ui.inputSpinBox2, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult);
}on_<객체 이름> 접두사를 추가하여 일반적인 방식으로 사용자 인터페이스 위젯의 신호와 슬롯을 연결할 수 있습니다. 자세한 내용은 widgets-and-dialogs-with-auto-connect를 참조하십시오.
이 접근 방식의 장점은 상속을 간단하게 활용하여 ` QWidget` 기반의 인터페이스를 제공할 수 있다는 점과, 사용자 인터페이스 위젯 변수들을 ` ui ` 데이터 멤버 내에 캡슐화할 수 있다는 점입니다. 이 방법을 사용하면 동일한 위젯 내에서 각각 고유한 네임스페이스에 포함된 여러 사용자 인터페이스를 정의하고, 이를 오버레이(또는 조합)할 수 있습니다. 예를 들어, 이 접근 방식을 사용하여 기존 양식에서 개별 탭을 생성할 수 있습니다.
포인터 멤버 변수 사용
또는 ` Ui::CalculatorForm ` 구조체를 클래스의 포인터 멤버로 정의할 수도 있습니다. 이 경우 헤더는 다음과 같이 됩니다:
namespace Ui {
class CalculatorForm;
}
class CalculatorForm : public QWidget
...
virtual ~CalculatorForm();
...
private:
Ui::CalculatorForm *ui;
...이에 해당하는 소스 파일은 다음과 같습니다:
#include "ui_calculatorform.h"
CalculatorForm::CalculatorForm(QWidget *parent) :
QWidget(parent), ui(new Ui::CalculatorForm)
{
ui->setupUi(this);
}
CalculatorForm::~CalculatorForm()
{
delete ui;
}이 방식의 장점은 사용자 인터페이스 객체를 사전 선언할 수 있다는 점입니다. 즉, 생성된 ` ui_calculatorform.h ` 파일을 헤더에 포함시킬 필요가 없습니다. 따라서 종속 소스 파일을 재컴파일하지 않고도 양식을 변경할 수 있습니다. 이는 클래스가 바이너리 호환성 제약 조건을 따르는 경우 특히 중요합니다.
일반적으로 라이브러리나 대규모 애플리케이션의 경우 이 방식을 권장합니다. 자세한 내용은 ‘공유 라이브러리 생성’을 참조하십시오.
다중 상속 방식
Qt Widgets Designer 로 생성된 폼은 표준 QWidget 기반 클래스와 함께 서브클래스로 확장할 수 있습니다. 이 방식을 사용하면 폼에 정의된 모든 사용자 인터페이스 구성 요소에 서브클래스 범위 내에서 직접 접근할 수 있으며, connect() 함수를 사용하여 일반적인 방식으로 신호와 슬롯 연결을 설정할 수 있습니다.
다음과 같이 uic 가 calculatorform.ui 파일에서 생성한 헤더 파일을 포함시켜야 합니다:
#include "ui_calculatorform.h"클래스는 단일 상속 방식에서 사용된 것과 유사한 방식으로 정의되지만, 이번에는 QWidget 와 Ui::CalculatorForm모두로부터 상속받습니다. 다음과 같습니다:
class CalculatorForm : public QWidget, private Ui::CalculatorForm
{
Q_OBJECT
public:
explicit CalculatorForm(QWidget *parent = nullptr);
private slots:
void on_inputSpinBox1_valueChanged(int value);
void on_inputSpinBox2_valueChanged(int value);
};Ui::CalculatorForm 를 비공개(private)로 상속함으로써, 하위 클래스에서 사용자 인터페이스 객체들이 비공개로 유지되도록 합니다. 또한 이전 예제에서 ui 를 공개(public) 또는 보호(protected)로 정의했던 것과 마찬가지로, public 또는 protected 키워드를 사용하여 상속할 수도 있습니다.
이 하위 클래스의 생성자는 단일 상속 예제에서 사용된 생성자와 동일한 작업을 다수 수행합니다:
이 경우, 사용자 인터페이스에 사용된 위젯은 코드에서 직접 생성한 위젯과 동일한 방식으로 접근할 수 있습니다. 더 이상 ` ui ` 접두사를 사용하여 접근할 필요가 없습니다.
언어 변경에 대응하기
Qt는 사용자 인터페이스 언어가 변경되면 QEvent::LanguageChange 유형의 이벤트를 전송하여 애플리케이션에 알립니다. 사용자 인터페이스 객체의 retranslateUi() 멤버 함수를 호출하려면, 다음과 같이 폼 클래스에서 QWidget::changeEvent() 를 재구현합니다:
void CalculatorForm::changeEvent(QEvent *e)
{
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}런타임 폼 처리
또는 런타임에 폼을 처리하여 동적으로 생성된 사용자 인터페이스를 만들 수도 있습니다. 이는 Qt Widgets Designer 로 생성된 폼을 처리하기 위한 QUiLoader 클래스를 제공하는 QtUiTools 모듈을 사용하여 수행할 수 있습니다.
UiTools 방식
실행 시점에 폼을 처리하려면 UI 파일이 포함된 리소스 파일이 필요합니다. 또한, 애플리케이션이 QtUiTools 모듈을 사용하도록 구성되어야 합니다. 이를 위해서는 CMake 프로젝트 파일에 다음 선언문을 포함시켜 애플리케이션이 적절하게 컴파일되고 링크되도록 해야 합니다.
find_package(Qt6 REQUIRED COMPONENTS Core Gui UiTools Widgets)
target_link_libraries(textfinder PUBLIC
Qt::Core
Qt::Gui
Qt::UiTools
Qt::Widgets
)qmake 의 경우:
QT += uitoolsQUiLoader 의 경우: 클래스는 사용자 인터페이스를 생성하기 위한 폼 로더 객체를 제공합니다. 이 사용자 인터페이스는 QIODevice(예: QFile 객체)에서 가져와 프로젝트의 리소스 파일에 저장된 폼을 획득할 수 있습니다. QUiLoader::load() 함수는 파일에 포함된 사용자 인터페이스 설명을 사용하여 폼 위젯을 생성합니다.
QtUiTools 모듈의 클래스는 다음 지시문을 사용하여 포함할 수 있습니다:
#include <QtUiTools>QUiLoader::load() 함수는 Text Finder 예제의 다음 코드에서 볼 수 있듯이 호출됩니다:
static QWidget*loadUiFile(QWidget*parent)
{
QFile file(u":/forms/textfinder.ui"_s);
if (!file.open(QIODevice::ReadOnly))
qFatal("Cannot open resource file");
return QUiLoader().load(&file, parent);
}QtUiTools 를 사용하여 런타임에 사용자 인터페이스를 구축하는 클래스에서는 QObject::findChild()을 통해 양식 내의 객체를 찾을 수 있습니다. 예를 들어, 다음 코드에서는 객체 이름과 위젯 유형을 기반으로 일부 컴포넌트를 찾습니다:
ui_findButton = findChild<QPushButton*>("findButton");
ui_textEdit = findChild<QTextEdit*>("textEdit");
ui_lineEdit = findChild<QLineEdit*>("lineEdit");실행 시점에 폼을 처리하면 개발자는 UI 파일을 변경하는 것만으로도 프로그램의 사용자 인터페이스를 자유롭게 변경할 수 있습니다. 이는 접근성 지원을 위해 초대형 아이콘이나 다른 색상 구성 등을 적용하는 등, 다양한 사용자 요구에 맞춰 프로그램을 맞춤 설정할 때 유용합니다.
자동 연결
컴파일 시점 또는 런타임 폼에 대해 정의된 신호와 슬롯 간의 연결은 수동으로 설정하거나, QMetaObject 의 기능을 활용하여 신호와 적절한 이름의 슬롯 간에 연결을 자동으로 설정할 수 있습니다.
일반적으로 QDialog 에서 사용자가 입력한 정보를 수락하기 전에 처리하려면, OK 버튼의 clicked() 신호를 대화 상자의 사용자 정의 슬롯에 연결해야 합니다. 먼저 슬롯을 수동으로 연결한 대화 상자의 예제를 보여준 다음, 자동 연결을 사용하는 대화 상자와 비교해 보겠습니다.
자동 연결 기능이 없는 대화상자
다이얼로그는 이전과 동일한 방식으로 정의하지만, 이번에는 생성자 외에도 슬롯을 하나 추가합니다:
class ImageDialog : public QDialog, private Ui::ImageDialog
{
Q_OBJECT
public:
explicit ImageDialog(QWidget *parent = nullptr);
private slots:
void checkValues();
};checkValues() 슬롯은 사용자가 입력한 값을 유효성 검사하는 데 사용됩니다.
대화 상자의 생성자에서는 이전과 마찬가지로 위젯을 설정하고, ‘Cancel’ 버튼의 clicked() 신호를 대화 상자의 reject() 슬롯에 연결합니다. 또한 대화 상자가 라인 에디트의 리턴 키 이벤트 처리에 간섭하지 않도록 두 버튼 모두에서 autoDefault 속성을 비활성화합니다:
ImageDialog::ImageDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
okButton->setAutoDefault(false);
cancelButton->setAutoDefault(false);
...
connect(okButton, &QAbstractButton::clicked, this, &ImageDialog::checkValues);
}OK 버튼의 clicked() 신호를 대화 상자의 checkValues() 슬롯에 연결하며, 이 슬롯은 다음과 같이 구현합니다:
void ImageDialog::checkValues()
{
if (nameLineEdit->text().isEmpty()) {
QMessageBox::information(this, tr("No Image Name"),
tr("Please supply a name for the image."), QMessageBox::Cancel);
} else {
accept();
}
}이 사용자 정의 슬롯은 사용자가 입력한 데이터가 유효한지 확인하는 데 필요한 최소한의 작업만 수행합니다. 즉, 이미지에 이름이 지정된 경우에만 입력을 허용합니다.
자동 연결 기능을 지원하는 위젯 및 대화 상자
대화 상자에 사용자 정의 슬롯을 구현하고 생성자에서 이를 연결하는 것은 쉽지만, 대신 QMetaObject 의 자동 연결 기능을 사용하여 OK 버튼의 clicked() 신호를 하위 클래스의 슬롯에 연결할 수도 있습니다. uic 는 이를 수행하기 위해 대화 상자의 ` setupUi() ` 함수 내에 코드를 자동으로 생성하므로, 표준 규칙에 따라 이름을 지정한 슬롯을 선언하고 구현하기만 하면 됩니다:
void on_<object name>_<signal name>(<signal parameters>);참고: 폼 내 위젯의 이름을 변경할경우 슬롯 이름도 그에 따라 수정해야 하므로, 유지보수 문제가 발생할 수 있습니다. 이러한 이유로 새로운 코드에서는 이 방법을 사용하지 않는 것을 권장합니다.
이 규칙을 사용하여, ‘OK’ 버튼의 마우스 클릭에 반응하는 슬롯을 정의하고 구현할 수 있습니다:
class ImageDialog : public QDialog, private Ui::ImageDialog
{
Q_OBJECT
public:
explicit ImageDialog(QWidget *parent = nullptr);
private slots:
void on_okButton_clicked();
};신호와 슬롯의 자동 연결에 대한 또 다른 예로는 ' on_findButton_clicked() ' 슬롯을 사용하는 Text Finder가 있습니다.
우리는 ` QMetaObject`의 시스템을 사용하여 신호와 슬롯 간의 연결을 활성화합니다:
QMetaObject::connectSlotsByName(this);이를 통해 아래와 같이 슬롯을 구현할 수 있습니다:
void TextFinder::on_findButton_clicked()
{
QString searchString = ui_lineEdit->text();
QTextDocument *document = ui_textEdit->document();
bool found = false;
// undo previous change (if any)
document->undo();
if (searchString.isEmpty()) {
QMessageBox::information(this, tr("Empty Search Field"),
tr("The search field is empty. "
"Please enter a word and click Find."));
} else {
QTextCursor highlightCursor(document);
QTextCursor cursor(document);
cursor.beginEditBlock();
...
cursor.endEditBlock();
if (found == false) {
QMessageBox::information(this, tr("Word Not Found"),
tr("Sorry, the word cannot be found."));
}
}
}신호와 슬롯의 자동 연결은 위젯 설계자들이 따를 수 있는 표준 명명 규칙과 명확한 인터페이스를 모두 제공합니다. 주어진 인터페이스를 구현하는 소스 코드를 제공함으로써, 사용자 인터페이스 설계자들은 직접 코드를 작성하지 않고도 자신의 디자인이 실제로 작동하는지 확인할 수 있습니다.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.