C++ 애플리케이션에서 디자이너 UI 파일 사용하기
Qt Widgets Designer UI 파일은 양식의 위젯 트리를 XML 형식으로 나타냅니다. 양식을 처리할 수 있습니다:
- 컴파일 시, 즉 양식이 컴파일할 수 있는 C++ 코드로 변환됩니다.
- 런타임 시: XML 파일을 파싱하는 동안 위젯 트리를 동적으로 구성하는 QUiLoader 클래스에 의해 양식이 처리됨을 의미합니다.
컴파일 시간 양식 처리
Qt Widgets Designer 으로 사용자 인터페이스 컴포넌트를 생성하고 Qt의 통합 빌드 도구인 qmake와 uic를 사용하여 애플리케이션이 빌드될 때 해당 컴포넌트에 대한 코드를 생성합니다. 생성된 코드에는 양식의 사용자 인터페이스 객체가 포함됩니다. 이는 다음을 포함하는 C++ 구조체입니다:
- 양식의 위젯, 레이아웃, 레이아웃 항목, 버튼 그룹 및 동작에 대한 포인터.
- 부모 위젯에서 위젯 트리를 작성하는
setupUi()
라는 멤버 함수. - 양식의 문자열 속성 번역을 처리하는
retranslateUi()
이라는 멤버 함수. 자세한 내용은 언어 변경에 반응하기를 참조하세요.
생성된 코드는 애플리케이션에 포함시켜 애플리케이션에서 바로 사용할 수 있습니다. 또는 표준 위젯의 하위 클래스를 확장하는 데 사용할 수도 있습니다.
컴파일 시간 처리된 양식은 다음 접근 방식 중 하나를 사용하여 애플리케이션에서 사용할 수 있습니다:
- 직접 접근 방식: 컴포넌트의 플레이스홀더로 사용할 위젯을 생성하고 그 안에 사용자 인터페이스를 설정합니다.
- 단일 상속 접근 방식: 양식의 기본 클래스(예:QWidget 또는 QDialog)를 서브클래싱하고 양식의 사용자 인터페이스 객체의 비공개 인스턴스를 포함합니다.
- 다중 상속 접근 방식: 양식의 기본 클래스와 양식의 사용자 인터페이스 객체를 모두 서브클래싱합니다. 이렇게 하면 양식에 정의된 위젯을 하위 클래스의 범위 내에서 직접 사용할 수 있습니다.
데모를 위해 간단한 계산기 양식 애플리케이션을 만들어 보겠습니다. 이 애플리케이션은 원래 계산기 폼 예제를 기반으로 합니다.
애플리케이션은 하나의 소스 파일( 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
이 파일의 특별한 기능은 uic
로 처리할 파일을 qmake
에 알려주는 FORMS
선언입니다. 이 경우 calculatorform.ui
파일은 SOURCES
선언에 나열된 모든 파일에서 사용할 수 있는 ui_calculatorform.h
파일을 만드는 데 사용됩니다.
참고: Qt Creator 을 사용하여 계산기 양식 프로젝트를 만들 수 있습니다. 그러면 원하는 빌드 도구에 대한 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
코드는 컴파일 및 실행되지만 추가 작업을 수행하고 결과를 QLabel 에 표시하려면 사용자 정의 슬롯이 필요하므로 QSpinBox 객체는 QLabel 와 상호 작용하지 않습니다. 이를 위해서는 단일 상속 접근 방식을 사용해야 합니다.
단일 상속 접근 방식
단일 상속 접근법을 사용하려면 표준 Qt 위젯을 서브클래싱하고 폼의 사용자 인터페이스 객체의 비공개 인스턴스를 포함합니다. 이는 다음과 같은 형태를 취할 수 있습니다:
- 멤버 변수
- 포인터 멤버 변수
멤버 변수 사용하기
이 접근 방식에서는 Qt 위젯을 서브클래싱하고 생성자 내에서 사용자 인터페이스를 설정합니다. 이러한 방식으로 사용되는 컴포넌트는 폼에 사용된 위젯과 레이아웃을 Qt 위젯 서브클래스에 노출하고 사용자 인터페이스와 애플리케이션의 다른 객체 사이에 신호 및 슬롯 연결을 위한 표준 시스템을 제공합니다. 생성된 Ui::CalculatorForm
구조체는 클래스의 멤버입니다.
이 접근 방식은 계산기 양식 예제에서 사용됩니다.
사용자 인터페이스를 사용하려면 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
파일을 생성할 수 있습니다.
#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_<개체 이름> - 접두사를 추가하여 연결할 수 있습니다. 자세한 내용은 자동 연결 기능이 있는 위젯 및 대화창을 참조하세요.
이 접근 방식의 장점은 간단한 상속을 사용하여 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
을 비공개로 상속하여 하위 클래스에서 사용자 인터페이스 객체가 비공개로 유지되도록 합니다. 또한 이전 사례에서 ui
을 공개 또는 보호로 만들었던 것과 같은 방식으로 public
또는 protected
키워드를 사용하여 상속할 수도 있습니다.
서브클래스의 생성자는 단일 상속 예제에서 사용된 생성자와 동일한 작업을 많이 수행합니다:
이 경우 사용자 인터페이스에 사용되는 위젯은 코드에서 직접 만든 위젯과 동일한 방식으로 액세스할 수 있습니다. 더 이상 ui
접두사가 필요하지 않습니다.
언어 변경에 대한 반응
Qt는 사용자 인터페이스 언어가 변경되면 QEvent::LanguageChange 유형의 이벤트를 전송하여 애플리케이션에 알립니다. 사용자 인터페이스 객체의 멤버 함수 retranslateUi()
를 호출하기 위해 다음과 같이 form 클래스에서 QWidget::changeEvent()
을 다시 구현합니다:
void CalculatorForm::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } }
런타임 양식 처리
또는 런타임에 양식을 처리하여 동적으로 생성된 사용자 인터페이스를 생성할 수도 있습니다. 이는 QUiLoader 클래스를 제공하는 QtUiTools 모듈을 사용하여 Qt Widgets Designer 로 생성된 양식을 처리할 수 있습니다.
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 += uitools
QUiLoader 클래스는 사용자 인터페이스를 구성하기 위한 폼 로더 객체를 제공합니다. 이 사용자 인터페이스는 QIODevice, 예를 들어 QFile 객체에서 검색하여 프로젝트의 리소스 파일에 저장된 양식을 가져올 수 있습니다. QUiLoader::load () 함수는 파일에 포함된 사용자 인터페이스 설명을 사용하여 양식 위젯을 구성합니다.
QtUiTools 모듈 클래스는 다음 지시어를 사용하여 포함할 수 있습니다:
#include <QtUiTools>
QUiLoader::load() 함수는 텍스트 찾기 예제에서 이 코드에 표시된 것처럼 호출됩니다:
static QWidget *loadUiFile(QWidget *parent) { QFile file(u":/forms/textfinder.ui"_s); file.open(QIODevice::ReadOnly); QUiLoader loader; return loader.load(&file, parent); }
QtUiTools 을 사용하여 런타임에 사용자 인터페이스를 구축하는 클래스에서는 QObject::findChild() 을 사용하여 양식에서 객체를 찾을 수 있습니다. 예를 들어, 다음 코드에서는 객체 이름과 위젯 유형을 기준으로 일부 컴포넌트를 찾습니다:
ui_findButton = findChild<QPushButton*>("findButton"); ui_textEdit = findChild<QTextEdit*>("textEdit"); ui_lineEdit = findChild<QLineEdit*>("lineEdit");
런타임에 양식을 처리하면 개발자는 UI 파일을 변경하는 것만으로 프로그램의 사용자 인터페이스를 자유롭게 변경할 수 있습니다. 이는 매우 큰 아이콘이나 접근성 지원을 위한 다른 색 구성표와 같이 다양한 사용자 요구에 맞게 프로그램을 사용자 지정할 때 유용합니다.
자동 연결
컴파일 시간 또는 런타임 양식에 정의된 신호 및 슬롯 연결은 QMetaObject 의 신호와 적절한 이름의 슬롯 간 연결 기능을 사용하여 수동 또는 자동으로 설정할 수 있습니다.
일반적으로 QDialog 에서 사용자가 입력한 정보를 수락하기 전에 처리하려면 확인 버튼의 클릭() 신호를 대화 상자의 사용자 지정 슬롯에 연결해야 합니다. 먼저 슬롯을 수동으로 연결한 대화 상자의 예시를 보여준 다음 자동 연결을 사용하는 대화 상자와 비교하겠습니다.
자동 연결이 없는 대화 상자
이전과 같은 방식으로 대화 상자를 정의하지만 이제 생성자 외에 슬롯을 포함합니다:
class ImageDialog : public QDialog, private Ui::ImageDialog { Q_OBJECT public: explicit ImageDialog(QWidget *parent = nullptr); private slots: void checkValues(); };
checkValues()
슬롯은 사용자가 제공한 값의 유효성을 검사하는 데 사용됩니다.
대화 상자의 생성자에서는 이전과 같이 위젯을 설정하고 취소 버튼의 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 버튼의 클릭() 신호를 하위 클래스의 슬롯에 연결할 수 있습니다. uic
은 이를 위해 대화 상자의 setupUi()
함수에서 자동으로 코드를 생성하므로 표준 규칙을 따르는 이름으로 슬롯을 선언하고 구현하기만 하면 됩니다:
void on_<object name>_<signal name>(<signal parameters>);
참고: 양식에서 위젯 이름을 변경할 때 슬롯 이름도 그에 맞게 조정해야 하므로 유지 관리 문제가 될 수 있습니다. 따라서 새 코드에서는 이 규칙을 사용하지 않는 것이 좋습니다.
이 규칙을 사용하여 확인 버튼의 마우스 클릭에 반응하는 슬롯을 정의하고 구현할 수 있습니다:
class ImageDialog : public QDialog, private Ui::ImageDialog { Q_OBJECT public: explicit ImageDialog(QWidget *parent = nullptr); private slots: void on_okButton_clicked(); };
신호 및 슬롯 자동 연결의 또 다른 예로는 on_findButton_clicked()
슬롯이 있는 텍스트 찾기를 들 수 있습니다.
저희는 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.")); } } }
신호와 슬롯의 자동 연결은 위젯 디자이너가 작업할 수 있는 표준 명명 규칙과 명시적인 인터페이스를 모두 제공합니다. 주어진 인터페이스를 구현하는 소스 코드를 제공함으로써 사용자 인터페이스 디자이너는 코드를 직접 작성하지 않고도 자신의 디자인이 실제로 작동하는지 확인할 수 있습니다.
© 2025 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.