QT 스터디 챕터 9~16 (14제외)
9. Signal and Slot
Qt는 이벤트를 처리하기 위한 메커니즘으로 시그널(Signal)과 슬롯(Slot)을 사용한다.
예 로 어떤 버튼이 클릭했다는 행위는 Qt에서 시그널(Signal) 이라고 한다. 그리고 시그널 이 발생하면 호출 하는 함수를 슬롯(Slot) 함수라고 한다. 시그널 이라는 이벤트가 발생 하면 시그널과 연결된 슬롯 함수가 호출된다. 시그널이란 어떠한 상황에 발생하는 이벤트이다. Qt의 모든 이벤트 처리는 시그널과 슬롯 이라는 메커니즘을 사용한다.
예를 들어 Qt로 채팅 프로그램에서 A라는 사용자가 B라는 사용자에게 메시지를 보낸 다고 가정해보자
B의 입장에서 A에게로부터 메시지를 받은 행위는 시그널(Signal) 이라고 정의할 수 있다.
그리고 메시지를 받은 시그널과 연결된 슬롯(Slot) 함수를 호출한다.
Qt는 모든 이벤트 처리를 시그널과 슬롯을 사용한다. 방금 예로든 GUI 및 네트워크모듈뿐만 아니라 Qt에 서 제공하는 모든 API에서 시그널과 슬롯 이라는 이벤트 메커니즘을 사용한다. 시그널 과 슬롯은 프로그램 소스코드를 단순화 시켜주기 때문에 개발기간을 단축 시킬 수 있 으며 복잡한 프로그램 구조를 단순화 할 수 있다.
네트워크 채팅 프로그램을 Qt의 시그널 슬롯을 사용하지 않고 채팅 프로그램을 개발한 다고 가정해보자.
여러 개의 쓰레드(Thread) 구조의 프로그램을 개발해야 한다.
예를 들 어 특정 사용자가 보내는 메시지를 처리하는 쓰레드, 귓속말 처리 쓰레드, 새로운 사용자가 등록되면 알려주는 쓰레드 등 여러 개의 쓰레드를 사용해야 하기 때문에 프로그 램이 복잡해 질 수 있다.
하지만 Qt에서 시그널과 슬롯을 사용하면 쓰레드를 사용하지 않고도 간단하게 채팅 프로그램을 구현할 수 있다.
Qt에 제공하는 모든 GUI 위젯은 미리 정해진 다양한 시그널을 가지고 있다.
예를 들어 QPushButton의 click, double click, mouse over 등과 같이 다양한 시그널이 정의되어 있다.
시그널과 슬롯은 하나의 파이프라인과 같이 생각하면 된다. 하나의 시그널이 여러 개의 슬롯 함수를 호출할 수 있다. 또한 여러 개의 시그널이 하나의 슬롯을 호출할 수 있다.

시그널과 슬롯 함수를 연결하기 위한 함수는 QObject 클래스의 connect( ) 함수를 이용 해 Signal 과 Slot을 연결할 수 있다.
connect( ) 멤버 함수의 첫 번째 인자는 이벤트가 발생한 오브젝트(클래스의 인스턴스), 두 번째 인자는 오브젝트의 시그널(이벤트)을 입력한다.
예를 들어 A라는 버튼이 있으면 A라는 버튼의 오브젝트 명이 첫 번째 인자이고 두 번 째 인자는 A버튼의 클릭 또는 더블클릭이 시그널이 될 수 있다. 따라서 클릭 이벤트를 두번째 인자로 명시한다. 세 번째 인자는 시그널과 호출할 슬롯 함수 있는 오브젝트의 이름을 명시한다. 네 번째 인자는 발생한 시그널 발생 시 호출할 슬롯 함수를 명시한다.
지금까지 Signal 과 Slot 에 대한 개념에 대해 살펴보았다.
이번에는 직접 Signal 과 Slot을 이용해 예제 프로그램 작성해 보도록 하자.
- Signal 과 Slot 예제
이 예제 소스코드는 시그널이 발생하면 윈도우 상에 배치된 라벨의 텍스트를 출력하는 예제이다.
그리고 SignalSlot 이라는 클래스와 Widget 이라는 두개의 클래스가 존재한다.
다음 예제 소스코드는 SignalSlot 이라는 클래스이다.
...
class SignalSlot : public QObject
{
Q_OBJECT
public:
void setValue(int val)
{
emit valueChanged(val);
}
signals:
void valueChanged(int newValue);
private:
int m_value;
};
위의 예제에서 보는 것과 같이 signals: 키워드 하단에 valueChanged( ) 라는 함수가 있다.
이 함수가 시그널 함수이다. Signal 함수는 구현 부는 없으며 소스코드에서 보는 것과 같이 헤더에 정의 부만 구현 하면 된다. valueChanged( ) Signal 함수는 int 인자를 명시하였다. 이 인자는 시그널을 발생할 때 값을 Slot함수에게 인자로 전달할 수 있다. 여기서는 하나의 값을 사용하였다. 필요에 따라 인자를 사용하지 않거나 여러 개를 인자로 사용할 수 있다.
위의 예제 소스코드의 SignalSlot 이라는 클래스의 public 키워드에 setValue( ) 멤버 함수이다. 이 멤버 함수가 호출되면 함수 내에 emit 이라는 키워드를 사용한 소스코드를 확인할 수 있다. emit 키워드는 시그널 이벤트를 발생한다.
다음 예제 소스코드는 Widget 클래스의 헤더 부분이다.
...
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
QLabel *lbl;
public slots:
void setValue(int val);
};
Widget 클래스의 하단에 보면 public 키워드에 slots 키워드를 함께 사용한 것을 확인 할 수 있을 것이다. slots 키워드는 private 또는 public 키워드와 함께 사용할 수 있다. slots 라는 키워드를 사용하여 명시한 멤버 함수는 Slot 함수를 정의할 수 있다.
다음 예제 소스코드는 widget.cpp 소스코드이다.
#include "widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent)
{
lbl = new QLabel("", this);
lbl->setGeometry(10, 10, 250, 40);
SignalSlot myObject;
// New Style
connect(&myObject, &SignalSlot::valueChanged, this, &Widget::setValue);
/* Old Style
connect(&myObject, SIGNAL(valueChanged(int)), this, SLOT(setValue(int)));
*/
myObject.setValue(50);
}
void Widget::setValue(int val)
{
QString text = QString("Signal emmit, Value:%1").arg(val);
lbl->setText(labelText);
}
connect( ) 멤버 함수를 이용해 Signal 과 Slot 을 연결 할 수 있다. Signal 과 Slot 을 연결하는 방법에는 두 가지 방법을 제공한다. 주석으로 New Style 이라고 되어 있는 방식은 Qt 5.5 이상 버전에서 추가된 Signal과 Slot을 연결하는 방식이다. New Syntax 과 Old Style모두 사용할 수 있다. Qt 5.5 이하버전에서는 Old Style 방식만을 사용할 수 있다.
New Style 스타일은 여러 개의 인자를 사용할 수 없다.
그리고 New Style 은 Slot 함수 뿐만 아니라 일반 멤버 함수도 Slot 함수와 같이 connect( ) 함수에서 4번째 인자로 사용할 수 있다. 살펴본 예제와 같이 Signal 과 Slot함수는 다른 클래스에만 구현되어 있어야 되는 것은 아니다. 동일한 클래스에 존재하는 Signal 이 Slot 함수를 호출할 수 있다.
그리고 다음 그림에서 보는 것과 같이 Signal 은 Slot 함수 외에도 Signal을 호출할 수 있다.

cnt1 = new Counter("counter 1");
cnt2 = new Counter("counter 2");
connect(cnt1, SIGNAL(valueChagned(int)), cnt2, SIGNAL(valueChagned(int)));
connect(cnt2, SIGNAL(valueChagned(int)), cnt2, SLOT(setValue(int)));
Signal 과 Slot을 사용하는 클래스 상단에 보면 Q_OBJECT 라는 키워드를 사용한 것을 확인할 수 있다.
이 키워드는 Qt에서 Signal 과 Slot을 사용할 때 반드시 필수로 Q_OBJECT를 클래스 헤더에 보는 것과 같이 명시해야한다. Q_OBJECT를 선언하지 않고 Signal 과 Slot을 사용하는 경우가 있는데, 이럴 경우 에러가 발생하므로 주의해야 한다.
10. Qt Designer 를 이용한 GUI 설계
Qt 는 원하는 GUI 를 쉽게 빠르게 구현할 수 있도록 Qt Designer 를 제공한다.
Qt Designer 는 사용자가 GUI 상에 배치할 위젯을 마우스로 드래그 하면서 위젯을 배치할 수 있다.
Qt Creator IDE 툴이 제공되지 않던 시절에는 Qt Designer 라는 툴이 독립적으로 제공하였다.
하지만 지금은 Qt Creator IDE 툴에 통합되었다. Qt Designer 툴은 아직도 독립적인 실행 프로그램으로 제공한다.
그 이유는 아직도 Visual Studio 와 같은 IDE 툴을 이용하는 개발자를 위해서이다.

확장자가 ui 파일인 GUI 파일은 아래 소스코드와 같이 XML 포맷으로 되어 있다.
이 파일은 사용자가 마우스를 이용해 위젯을 배치하면 Qt Creator 의 Designer 툴이 자동으로 XML 로 작성한다.
그리고 빌드를 하게 되면 XML 로 되어 있는 GUI 파일을 C++ 소스코드로 변환 후 빌드 한다.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>338</width>
<height>178</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
...
Qt Designer 를 이용한 예제
다음 그림에서 보는 것과 같이 Qt Creator 를 실행한 후 메뉴에서 [File] > [New file or Project…] 메뉴를 클릭한다.
다이얼로그가 로딩되면 다이얼로그에서 좌측의 [Projects] 항목 중에서 [Application] 항목을 선택한다

그런 다음 중앙에 항목 중 [Qt Widgets Application] 을 선택하고 하단의 [Choose] 버튼을 클릭한다.
다음 화면은 프로젝트 이름과 생성되는 위치를 입력하고 [Next] 버튼을 클릭한다

다음으로 아래와 같이 Class Information 을 입력하고 하단의 [Next] 버튼을 클릭한다.




이전 그림에서 보는 것과 같이 화면 배치를 완료하였으면 각각의 Horizontal Layout 에 QSpinBox 와 QSlider 를 배치하자

Object Name 변경은 우측 하단의 항목 중 Object Name 항목을 더블 클릭하면 변경할 수 있다.


GUI 상에 위젯들을 모두 배치 하였다면 어플리케이션을 빌드 후 실행해 보자.
예제 어플리케이션이 실행 되면 마우스로 위젯의 크기를 변경해 보고 자동으로 위젯들의 크기가 변경되는 지 확인해 보자.

Designer 툴에서 배치한 각 QSlider 위젯의 눈금을 마우스로 위치를 변경하면 QSlider 의 값이 변경된다.
이 값이 변경되면 변경된 값을 QSpinBox 의 값으로 설정하는 예제이다.
아래 소스코드는 widget.h 헤더 파일이다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui
{
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
private slots:
void slider1_valueChanged(int value);
void slider2_valueChanged(int value);
void slider3_valueChanged(int value);
};
#endif // WIDGET_H
widget.h 헤더파일에서 ui 를 선언한 Object 명이 있다. 이 오브젝트 명이 Designer 툴 에서 배치한 위젯을 접근할 수 있는 접근자 이다. 예를 들어 Designer 툴에서 배치한 slider2 라는 오브젝트를 접근하기 위해서 소스코드에서 ui 오브젝트를 사용하면 된다.
아래 예제 소스코드 하단의 Slot 함수는 Designer 에서 배치한 QSlider 의 값이 변경되 면 호출되는 Slot 함수이다.
다음은 widget.cpp 소스 코드이다.
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->slider1, SIGNAL(valueChanged(int)),
this, SLOT(slider1_valueChanged(int)));
connect(ui->slider2, SIGNAL(valueChanged(int)),
this, SLOT(slider2_valueChanged(int)));
connect(ui->slider3, SIGNAL(valueChanged(int)),
this, SLOT(slider3_valueChanged(int)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::slider1_valueChanged(int value)
{
ui->spinBox1->setValue(value);
}
void Widget::slider2_valueChanged(int value)
{
ui->spinBox2->setValue(value);
}
void Widget::slider3_valueChanged(int value)
{
ui->spinBox3->setValue(value);
}
위의 예제에서 한가지 주의해야 할 점은 connect( ) 함수의 인자를 Old Style 형태로 사용했다는 점이다.
만약 New Style 을 사용하면 “no matching member function for call to ‘connect’” 라는 에러가 발생한다.
이런 에러가 발생하는 이유는 QSpinBox 에서 제공하는 valueChanged( ) 멤버 함수는 Overloaded 된 멤버 함수로 int 형과 QString 형인 두가지 멤버 함수를 제공하기 때문이다.
void valueChanged(int i)
void valueChanged(const QString &text)
위의 소스코드에서 보는 것과 같이 Overloaded 된 시그널이 있다면 Old Style 을 사용해야 한다. 그냥 웬만하면 old style을 사용해야겠다.
다음은 지금까지 작성한 예제를 실행한 화면이다

11. 다이얼로그
다이얼로그는 어플리케이션이 동작 중에 이벤트가 발생했을 때 사용자에게 메시지를 전달하기 위한 목적으로 사용된다.
그리고 사용자로부터 입력 값을 받거나 여러 개 중 하나를 선택할 수 있는 GUI를 제공한다.
다음 표는 Qt에서 제공하는 다이얼로그 중 자 주 사용되는 다이얼로그이다.

- QInputDialog
QInputDialog 클래스는 사용자로부터 값을 입력 받을 수 있다.
bool retValue;
int i = QInputDialog::getInt(this, "정수입력", "퍼센트:",25, 0, 100, 1, &retValue);
if (retValue)
qDebug("true %d", i);

QInputDialog 클래스의 getInt( ) 멤버 함수는 사용자로부터 정수 값을 입력 받을 수 있 는 다이얼로그를 제공한다.
QInputDialog 클래스의 getInt( ) 멤버 함수는 사용자로부터 정수 값을 입력 받을 수 있는 다이얼로그를 제공한다.
첫 번째 인자는 부모 클래스,
두 번째 인자는 윈도우의 타이틀 바 에 표시할 제목,
세 번째 인자는 입력 값 위젯 항목의 좌측에 표시할 항목 이름 이다.
네 번째 인자는 디폴 트 설정 값, 다섯 번째와 여섯 번째 는 사용자가 입력할 수 있는 값 의 범위이고
다섯번 째 인자는 다이얼로그의 스핀 박스의 증가 값이다. 마지막 인자는 다이얼로그에서 [OK] 또 는 [Cancel] 버튼을 클릭했는지 확인할 수 있는 값을 저장한다.
bool retValue;
double dVal = QInputDialog::getDouble(this, "실수 입력", "값 입력:",48.56, -100, 100, 2, &retValue);

QStringList items;
items << "봄" << "여름" << "가을" << "겨울";
bool ok;
QString item = QInputDialog::getItem(this, "항목선택", "계절 선택: ", items, 0, false, &ok);

QInputDialog 클래스의 getText( ) 멤버 함수는 사용자로부터 텍스트를 입력 받을 수 있 는 다이얼로그를 제공한다.

bool ok;
QString text = QInputDialog::getText(this, "텍스트 입력", "이름: ",QLineEdit::Normal, "이름 입력", &ok);
- QColorDialog
QColorDialog 클래스는 사용자가 색상표를 보고 원하는 색상을 선택하기 위한 기능을 제공한다.

QColor color;
color = QColorDialog::getColor(Qt::green, this, "컬러선택", QColorDialog::DontUseNativeDialog);
if (color.isValid())
qDebug() << Q_FUNC_INFO << "유효한 색상.";
- QFileDialog
QFileDialog 클래스는 사용자로부터 파일을 선택할 수 있는 다이얼로그를 제공한다.
특정 확장자 또는 특정 파일을 필터링하여 사용자에게 보여줄 수 있다.
QFileDialog 클래스의 getOpenFileNames( ) 멤버 함수는 디렉토리 내에 존재하는 파일을 다중 선택할 수 있는 기능을 제공한다. getExistingDirectory( ) 멤버 함수는 사용자가 디렉토리를 선택할 수 있다.
그리고 getSaveFileName( ) 멤버 함수는 사용자가 저장할 파일을 지정할 수 있다.
QFileDialog::Options options;
options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
options |= QFileDialog::DontUseNativeDialog;
QString directory = QFileDialog::getExistingDirectory(this, "파일 다이얼로그", "C:", options);
getExistingDirectory( ) 멤버 함수는 사용자로부터 디렉토리를 선택할 수 있는 기능을 제공한다.
첫 번째 인자는 부모 클래스,
두 번째 인자는 다이얼로그의 타이틀 바 제목,
세 번째 인자는 지정한 디렉토리가 디폴트로 지정하기 위해 사용하는 인자이다.
네 번째 인자는 파일 다이얼로그의 상수 값을 이용해 필터링하기 위한 옵션이다.


- QFontDialog
QFontDialog는 사용자로부터 폰트를 선택할 수 있는 다이얼로그를 제공한다.
첫 번째 인자는 사용자가 다이얼로그 하단에 위치해 있는 [OK] 버튼과 [CANCEL] 버튼 중 어떤 버튼을 클릭했는지 확인하기 위한 변수를 지정 한다.

QFontDialog는 사용자로부터 폰트를 선택할 수 있는 다이얼로그를 제공한다.
첫 번째 인자는 사용자가 다이얼로그 하단에 위치해 있는 [OK] 버튼과 [CANCEL] 버튼 중 어떤 버튼을 클릭했는지 확인하기 위한 변수를 지정 한다.
두 번째 인자는 폰트 다이얼로그 상에서 디폴트 선택으로 지정할 수 있는 폰트를 지정 한다.
bool ok;
QFont font;
font = QFontDialog::getFont(&ok, QFont( "Courier 10 Pitch" ), this);
- QProgressDialog
QProgressDialog 클래스는 사용자에게 현재 진행 사항을 보여주기 위한 목적으로 사용한다.
예를 들어 대용량 파일 복사와 같이 시간이 다소 걸리는 사항에 대해서 다이얼로 그창에 진행사항을 보여 줄 수 있다

다음은 QProgressDialog 클래스를 이용해 사용자에게 진행사항을 보여주기 위한 다이얼로그 예제 소스코드이다.
QWidget 클래스를 Base 클래스로 프로젝트를 생성한 후 widget.h 헤더 파일을 다음과 같이 작성한다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QProgressDialog>
#include <QTimer>
namespace Ui
{
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QProgressDialog *pd;
QTimer *timer;
int steps;
public slots:
void perform();
void cancel();
};
#endif // WIDGET_H
위의 예제에서 QTimer 는 1초에 한번씩 타이머 이벤트를 호출해 QProgressDialog 의 진행사항 값을 1%씩 증가하기 위해 사용하였다.
다음 소스코드는 widget.cpp 소스코드이다.
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
steps = 0;
pd = new QProgressDialog("파일 복사 진행사항", "취소", 0, 100);
connect(pd, SIGNAL(canceled()), this, SLOT(cancel()));
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(perform()));
timer->start(1000);
}
Widget::~Widget()
{
delete ui;
}
void Widget::perform()
{
pd->setValue(steps);
steps++;
if (steps > pd->maximum())
timer->stop();
}
void Widget::cancel()
{
timer->stop();
}
- QMessageBox
QMessageBox 클래스는 모달(Modal) 방식의 다이얼로그를 제공한다.
사용자에게 정보 를 전달할 수 있으며 QMessageBox 클래스가 다이얼로그 창에서 사용가능하도록 제공 하는 버튼들은 다음 표에서 보는 것과 같이 다양한 버튼을 사용할 수 있도록 제공한다.


다음 예제는 QMessageBox 클래스를 이용해 [Abort] 버튼, [Retry] 버튼, [Ignore] 버튼을 사용하기 위한 예제이다
QMessageBox::StandardButton reply;
reply = QMessageBox::critical(this,
"Critical 다이얼로그",
"디스크가 존재하지 않습니다.",
QMessageBox::Abort |
QMessageBox::Retry |
QMessageBox::Ignore);
if (reply == QMessageBox::Abort)
...
else if (reply == QMessageBox::Retry)
...

사진 설명을 입력하세요.
QMessageBox 클래스는 information( ), question( ), warning( ) 멤버 함수를 제공한다.
다음은 question( ) 멤버 함수 사용 예제이다.
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Question 다이얼로그",
"파일을 저장 하시겠습니까?",
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (reply == QMessageBox::Abort)
...
else if (reply == QMessageBox::Retry)
...

- 사용자 정의 다이얼로그 구현 예제
이번 예제는 QDialog 클래스를 상속받아 사용자가 원하는 스타일의 다이얼로그를 구현해 보도록 하자.
다음 예제소스코드는 dialog.h 헤더 파일이다.
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QLabel *lbl;
QLineEdit *edit;
QPushButton *okbtn;
QPushButton *cancelbtn;
private slots:
void slot_okbtn();
void slot_cancelbtn();
};
#endif // DIALOG_H
dialog.h 헤더 파일은 QDialog 클래스를 상속받은 클래스를 구현하기 위한 헤더파일이다.
이 다이얼로그는 이름을 입력 받을 수 있으며 버튼 두개를 배치 하였다.
다음 예제 소스코드는 dialog.cpp 전체 소스코드이다.
#include "dialog.h"
Dialog::Dialog(QWidget *parent) : QDialog(parent)
{
setWindowTitle("Custom Dialog");
lbl = new QLabel("Name");
edit = new QLineEdit("");
okbtn = new QPushButton("Confirm");
cancelbtn = new QPushButton("Cancel");
QHBoxLayout *hlay1 = new QHBoxLayout();
hlay1->addWidget(lbl);
hlay1->addWidget(edit);
QHBoxLayout *hlay2 = new QHBoxLayout();
hlay2->addWidget(okbtn);
hlay2->addWidget(cancelbtn);
QVBoxLayout *vlay = new QVBoxLayout();
vlay->addLayout(hlay1);
vlay->addLayout(hlay2);
setLayout(vlay);
}
void Dialog::slot_okbtn()
{
emit accepted();
}
void Dialog::slot_cancelbtn()
{
emit rejected();
}
Dialog::~Dialog()
{
}
slot_okbtn( ) Slot 함수는 다이얼로그의 [확인] 버튼을 클릭 시 호출된다. slot_cancelbtn( ) Slot 함수는 [취소] 버튼을 클릭하면 호출된다.
다음 예제 소스코드 main.cpp 소스코드이다.
#include <QApplication>
#include <QDebug>
#include "dialog.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog dlg;
int retVal = dlg.exec();
if (retVal == QDialog::Accepted)
{
qDebug() << Q_FUNC_INFO << "QDialog::Accepted";
}
else if (retVal == QDialog::Rejected)
{
qDebug() << Q_FUNC_INFO << "QDialog::Rejected";
}
return a.exec();
}

위의 예제에서 보는 것과 같이 Dialog 클래스의 dlg 오브젝트는 사용자가 [확인] 버튼 클릭 시 int 값 1을 리턴 한다. [취소] 버튼 클릭 시 0을 리턴 한다.
12. QMainWindow 를 이용한 GUI 구현
지금까지 Qt를 이용한 GUI 기반의 위젯을 윈도우 상에 배치하는 예제를 다루어 보았다.
지금까지 다룬 방식은 QWidget 을 이용해 한 개의 윈도우 화면만 존재하는 방식 으로 GUI를 구성하였다.
하지만 기능이 복잡하고 사용자에게 많은 기능을 제공해야 하는 경우 GUI 구현 시 QWidget 보다는 QMainWindow 를 이용해 GUI를 구현하는 것이 사용자에게 직관적인 GUI를 제공할 수 있을 것이다.
예를 들어 Menu Bar, Toolbars, Status Bar, Dock Widget, Central Widget 등으로 위젯들 을 특정 영역에 배치할 수 있다.

그리고 Qt는 *MDI(Multi Document Interface) 방식을 구현할 수 있다.
MDI 방식은 QMdiArea 클래스를 이용해 구현할 수 있다.
복잡한 윈도우 GUI를 구현해야 한다면 QMainWindow 와 더불어 QMdiArea 클래스를 함께 사용하는 것을 권장한다.

<모르는 용어정리>
MDI(Multi Document Interface)는 하나의 메인 윈도우 안에서 여러 개의 서브 윈도우(문서)를 동시에 열고 관리할 수 있는 사용자 인터페이스 방식입니다.
예를 들어, 그림 편집 프로그램이나 텍스트 편집기에서 여러 개의 파일을 동시에 열고 작업할 수 있게 해주는 방식이 MDI입니다.
QMdiArea는 Qt에서 MDI 방식을 구현하는데 사용되는 클래스입니다.
이 클래스는 QMainWindow 안에서 여러 개의 서브 윈도우(QMdiSubWindow)를 관리할 수 있도록 해줍니다.
각 서브 윈도우는 독립적으로 이동, 크기 조정, 닫기 등이 가능하며, 메인 윈도우 내에서 서로 겹치거나 타일처럼 배열될 수 있습니다.
주로 복잡한 GUI 애플리케이션에서 여러 문서를 동시에 작업해야 할 때 사용되며, 각각의 문서가 독립적으로 편집될 수 있는 환경을 제공하는 데 유용합니다.
MDI 장단점
장점은 한 번에 여러 문서를 비교하거나 동시에 작업할 수 있는 기능이 있다는 점이고,
단점으로는 많은 창이 열렸을 때 관리가 복잡해질 수 있다는 점이 있습니다
- QMdiArea 클래스를 이용한 MDI 기반의 GUI 예제
이번 예제에서는 QMdiArea 클래스를 이용해 MDI 기반의 GUI를 구현해 보도록 하자.
이 예제는 Qt를 설치하면 제공되는 예제이다. Examples 의 MDI Example 예제에 전체 소스코드 참조하면 된다.
이 예제 소스코드는 2개의 클래스로 구현되어 있다.
MainWindow 클래스는 QMainWindow 클래스를 상속받아 구현 메인 윈도우 GUI 이다.
이 클래스에는 Menu Bar, Tool Bar, Central Widget 영역 등이 구현되어 있다.
MDIMainWindow 클래스는 QTextEdit 위젯 클래스를 상속받는 위젯 클래스이다. 이 클 래스는 MainWindow 중앙 배치할 다중 에디트 위젯으로 사용한다.
즉 울트라 에디터, 노트패드 등과 같이 여러 개의 텍스트 파일을 하나의 윈도우 영역 안에서 편집할 수 있는 기능을 제공하기 위해 구현된 클래스 이다.
다음 예제 소스코드는 MainWindow 클래스의 헤더 소스코드 이다.
#include <QMainWindow>
#include "MDIMainwindow.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void newFile();
void open();
};
...
newFile( ) Slot 함수는 메뉴 바에서 [New] 메뉴를 클릭했을 때 호출된다. open( ) 함수는 [Open] 메뉴를 클릭하면 호출된다.
다음 예제는 mainwindow.cpp 소스코드이다.
#include "mainwindow.h"
#include <QMenu>
#include <QAction>
#include <QMenuBar>
#include <QToolBar>
#include <QDockWidget>
#include <QListWidget>
#include <QStatusBar>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QMenu *fileMenu;
QAction *newAct;
QAction *openAct;
newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(tr("Create a new file"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
openAct = new QAction(QIcon(":/images/open.png"), tr("&Open"), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
QToolBar *fileToolBar;
fileToolBar = addToolBar(tr("File"));
fileToolBar->addAction(newAct);
fileToolBar->addAction(openAct);
QDockWidget *dock = new QDockWidget(tr("Target"), this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QListWidget *customerList = new QListWidget(dock);
customerList->addItems(QStringList()
<< "One" << "Two" << "Three" << "Four" << "Five");
dock->setWidget(customerList);
addDockWidget(Qt::RightDockWidgetArea, dock);
setCentralWidget(new MDIMainWindow());
statusBar()->showMessage(tr("Ready"));
}
MainWindow::~MainWindow()
{
}
void MainWindow::newFile()
{
qDebug() << Q_FUNC_INFO;
}
void MainWindow::open()
{
qDebug() << Q_FUNC_INFO;
}
MainWindow 클래스의 생성자 에서는 MDI 윈도우 GUI상에서 메뉴와 툴바에 배치할 메뉴 항목을 정의한다.
그리고 각 메뉴의 클릭 시 Signal 이벤트 발생시 Slot 함수와 연결한다.
QDockWidget 은 MDI 윈도우 좌측에 위치하고 사용자가 GUI상에서 새로운 창으로 분 리할 수 있는 GUI를 제공한다.
MDIMinWindow 클래스는 MDI 윈도우에서 Child 윈도우 로 사용한다. 즉 GUI 내에 여러 개의 Child 윈도우를 제공하는 것과 같은 기능을 제공 한다.
다음은 MDIMainWindow 클래스의 헤더 소스코드 이다.
#include <QMainWindow>
#include <QObject>
class MDIMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MDIMainWindow(QWidget *parent = nullptr);
};
이 클래스 생성자 에서는 QMdiArea 클래스와 QMdiSubWindow 클래스를 이용해 MDIMainWindow 하위에 서브 윈도우로 등록할 윈도우를 생성한다.
다음 예제 소스코드는 MDIMainwindow.cpp 소스코드 이다.
#include "MDIMainwindow.h"
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPushButton>
MDIMainWindow::MDIMainWindow(QWidget *parent) : QMainWindow(parent)
{
setWindowTitle(QString::fromUtf8("My MDI"));
QMdiArea *area = new QMdiArea();
area->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QMdiSubWindow *subWindow1 = new QMdiSubWindow();
subWindow1->resize(300, 200);
QPushButton *btn = new QPushButton(QString("Button"));
subWindow1->setWidget(btn);
QMdiSubWindow *subWindow2 = new QMdiSubWindow();
subWindow2->resize(300, 200);
area->addSubWindow(subWindow1);
area->addSubWindow(subWindow2);
setCentralWidget(area);
}
newFile( ) Slot 함수는 메뉴 바에서 [New] 메뉴를 클릭했을 때 호출된다.
open( ) 함수는 [Open] 메뉴를 클릭하면 호출된다.
다음 예제는 mainwindow.cpp 소스코드이다.
#include "mainwindow.h"
#include <QMenu>
#include <QAction>
#include <QMenuBar>
#include <QToolBar>
#include <QDockWidget>
#include <QListWidget>
#include <QStatusBar>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QMenu *fileMenu;
QAction *newAct;
QAction *openAct;
newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(tr("Create a new file"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
openAct = new QAction(QIcon(":/images/open.png"), tr("&Open"), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
QToolBar *fileToolBar;
fileToolBar = addToolBar(tr("File"));
fileToolBar->addAction(newAct);
fileToolBar->addAction(openAct);
QDockWidget *dock = new QDockWidget(tr("Target"), this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QListWidget *customerList = new QListWidget(dock);
customerList->addItems(QStringList()
<< "One" << "Two" << "Three" << "Four" << "Five");
dock->setWidget(customerList);
addDockWidget(Qt::RightDockWidgetArea, dock);
setCentralWidget(new MDIMainWindow());
statusBar()->showMessage(tr("Ready"));
}
MainWindow::~MainWindow()
{
}
void MainWindow::newFile()
{
qDebug() << Q_FUNC_INFO;
}
void MainWindow::open()
{
qDebug() << Q_FUNC_INFO;
}
MainWindow 클래스의 생성자 에서는 MDI 윈도우 GUI상에서 메뉴와 툴바에 배치할 메뉴 항목을 정의한다.
그리고 각 메뉴의 클릭 시 Signal 이벤트 발생시 Slot 함수와 연결한다.
QDockWidget 은 MDI 윈도우 좌측에 위치하고 사용자가 GUI상에서 새로운 창으로 분리할 수 있는 GUI를 제공한다. MDIMinWindow 클래스는 MDI 윈도우에서 Child 윈도우 로 사용한다. 즉 GUI 내에 여러 개의 Child 윈도우를 제공하는 것과 같은 기능을 제공 한다.
다음은 MDIMainWindow 클래스의 헤더 소스코드 이다.
#include <QMainWindow>
#include <QObject>
class MDIMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MDIMainWindow(QWidget *parent = nullptr);
};
이 클래스 생성자 에서는 QMdiArea 클래스와 QMdiSubWindow 클래스를 이용해 MDIMainWindow 하위에 서브 윈도우로 등록할 윈도우를 생성한다.
다음 예제 소스코드는 MDIMainwindow.cpp 소스코드 이다.
#include "MDIMainwindow.h"
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPushButton>
MDIMainWindow::MDIMainWindow(QWidget *parent) : QMainWindow(parent)
{
setWindowTitle(QString::fromUtf8("My MDI"));
QMdiArea *area = new QMdiArea();
area->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QMdiSubWindow *subWindow1 = new QMdiSubWindow();
subWindow1->resize(300, 200);
QPushButton *btn = new QPushButton(QString("Button"));
subWindow1->setWidget(btn);
QMdiSubWindow *subWindow2 = new QMdiSubWindow();
subWindow2->resize(300, 200);
area->addSubWindow(subWindow1);
area->addSubWindow(subWindow2);
setCentralWidget(area);
}

13. Stream
여기에서 Stream 이란 데이터를 특정 변수에 Write/Read 를 쉽게 하기 위한 방법을 말한다.
예를 들어 quint32 타입(4 Bytes)의 변수 데이터를 QByteArray 에 Write/Read 해야 하는 경우 Qt 에서 제공하는 QDataStream 또는 QTextStream 을 사용하면 쉽게 핸들링 할 수 있다.
QDataStream 은 *Binary 데이터를 Write/Read 하는데 사용하며 QTextStream 은 Text 기반의 데이터를 Write/Read 하는데 사용된다. 이번 장에서는 예제를 이용해 QDataStream 과 QTextStream 사용하는 방법에 대해서 살펴보도록 하자.
<모르는 용어정리>
이진(Binary) 데이터는 0과 1로 이루어진 데이터를 의미한다.
컴퓨터는 기본적으로 모든 데이터를 0과 1의 조합으로 처리하므로, 이진 데이터는 컴퓨터가 직접 이해하고 처리할 수 있는 가장 기초적인 형태의 데이터라고 할 수 있다.
텍스트 데이터와 다르게, 이진 데이터는 사람이 쉽게 읽을 수 있는 형태가 아니고, 숫자, 이미지, 오디오 파일, 객체의 메모리 상태 등 다양한 데이터를 그대로 저장하거나 전송할 때 주로 사용된다. 예를 들어, 컴퓨터가 파일을 저장할 때 텍스트가 아닌 이미지나 비디오 같은 복잡한 데이터를 처리할 때는 이진 데이터를 사용한다.
이진 데이터는 효율적이긴 하지만, 그 데이터를 사람이 이해하기 위해서는 다시 해석하거나 변환이 필요하다.
QDataStream 같은 클래스는 이런 이진 데이터를 쉽게 저장하거나 읽을 수 있도록 도와주는 도구이다.
- QDataStream 을 이용한 예제
이번 예제에서는 QDataStream 상에 데이터를 Write/Read 하는 예제를 다루어 보도록 하자. encoding( ) 함수에서는 quint32, quint8, quint32 타입의 데이터를 QDataStream 을 이용해 QByteArray 에 저장할 것이다. 각 변수에는 123, 124, 125가 저장할 것이다. 그리고 decoding( ) 함수에서는 QByteArray를 저장된 데이터를 차례대로 읽어오는 예제를 구현할 것이다.
아래는 예제 소스코드이다.
#include <QCoreApplication>
#include <QIODevice>
#include <QDataStream>
#include <QDebug>
QByteArray encoding()
{
quint32 value1 = 123;
quint8 value2 = 124;
quint32 value3 = 125;
QByteArray outData;
QDataStream outStream(&outData, QIODevice::WriteOnly);
outStream << value1;
outStream << value2;
outStream << value3;
qDebug() << "outData size : " << outData.size() << " Bytes";
return outData;
}
void decoding(QByteArray _data)
{
QByteArray inData = _data;
quint32 inValue1 = 0;
quint8 inValue2 = 0;
quint32 inValue3 = 0;
QDataStream inStream(&inData, QIODevice::ReadOnly);
inStream >> inValue1; // 123
inStream >> inValue2; // 124
inStream >> inValue3; // 125
qDebug("[First : %d] [Second : %d] [Third : %d]", inValue1, inValue2, inValue3);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray encData = encoding();
decoding(encData);
return a.exec();
}
encoding( ) 함수에서 quint32, quint8, quint32 변수 타입에 저장된 데이터 123, 124, 125 를 QDataStream 를 이용해 QByteArray 에 저장한다. 그리고 QByteArray 에 저장한 값을 encData 에 저장 한다. 그리고 encData 를 decoding( ) 함수에 첫 번째 인자로 넘겨 준다. decoding( ) 함수에서는 첫 번째 인자로 넘겨 받은 QByteArray 값을 QDataStream을 이용해 Read 한다. 따라서 decoding( ) 함수의 마지막 라인의 qDebug( ) 를 이용해 값을 출력한다.
- QTextStream 을 이용한 예제
이번 예제에서는 이 전 예제와 비슷한 방법으로 작성된 예제이다. writeData( ) 함수에서 는 QByteArray 에 저장된 값을 QTextStream 을 이용해 Write 할 것 이다.
그리고 readData( ) 함수에서는 반대로 QByteArray 에 저장된 값을 Read 할 것이다.
다음 예제는 이번 예제의 전체 소스코드 이다.
#include <QCoreApplication>
#include <QIODevice>
#include <QTextStream>
#include <QDebug>
// 데이터를 쓰는 함수 - QByteArray를 받아서 처리
QByteArray writeData(QByteArray _data)
{
QByteArray temp = _data; // 입력받은 데이터를 임시로 저장
QByteArray outData; // 출력할 데이터를 저장할 QByteArray
QTextStream outStream(&outData, QIODevice::WriteOnly); // outData에 쓰기 전용으로 QTextStream 연결
// 입력된 데이터의 크기만큼 루프를 돌면서 각 문자를 저장
for (qsizetype i = 0; i < temp.size(); i++)
{
outStream << temp.at(i); // temp에서 각 문자를 하나씩 가져와 outStream에 기록
}
outStream.flush(); // 스트림의 남은 데이터를 모두 기록
return outData; // 기록된 데이터를 반환
}
// 데이터를 읽는 함수 - QByteArray를 받아서 처리
void readData(QByteArray _data)
{
QTextStream outStream(&_data, QIODevice::ReadOnly); // _data를 읽기 전용으로 QTextStream에 연결
QByteArray inData; // 읽은 데이터를 저장할 QByteArray
// _data의 크기만큼 루프를 돌면서 데이터를 읽음
for (qsizetype i = 0; i < _data.size(); i++)
{
char data; // 데이터를 임시로 저장할 변수
outStream >> data; // 스트림에서 한 문자씩 읽어 data에 저장
inData.append(data); // 읽은 데이터를 inData에 추가
}
// qDebug로 읽은 데이터를 출력 (문자열 형식)
qDebug("READ DATA : [%s]", inData.data());
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// writeData 함수에 "Hello world." 문자열을 넘겨줌 (12 바이트)
QByteArray retData = writeData("Hello world.");
// qDebug로 쓰여진 데이터의 크기를 출력
qDebug() << "WRITE DATA size : " << retData.size() << " Bytes";
// readData 함수로 데이터를 읽어옴
readData(retData);
return a.exec(); // 이벤트 루프 실행
}
이 코드에서:
writeData 함수는 QByteArray를 받아서 각 문자를 QTextStream을 통해 새로운 QByteArray에 기록하는 역할을 한다.
readData 함수는 QTextStream을 통해 받은 데이터를 하나씩 읽어서 QByteArray에 다시 저장하고, 그 데이터를 출력한다.
main 함수에서는 "Hello world."라는 문자열을 writeData로 전달하고, 그 결과를 readData로 출력한다.
이 구조는 데이터를 텍스트 스트림을 통해 저장하고, 다시 읽어오는 과정이 어떻게 이루어지는지를 보여준다.
15. Qt Property
Qt에서 제공하는 Property System 은 C++에서 제공하는 Property System 과 비슷하다.
Property는 객체에서 값을 설정하고 가져오는 경우에 사용된다.
예를 들어 어떤 특정 클래스에서 다음 예제 소스코드와 같이 값을 설정하거나 가져오는 경우를 살펴보자
class Person : public QObject
{
Q_OBJECT
public:
explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
m_name = n;
}
private:
QString m_name;
};
위의 예제 소스코드에서 보는 것과 같이 Person 이라는 클래스는 Public 접근자로 선 언한 getName( ) 과 setName( ) 멤버 함수를 제공한다.
getName( ) 멤버 함수는 m_name 변수 값을 리턴 한다.
setName( ) 멤버 함수는 m_name 값을 설정하는 함수이다.
Person 이라는 클래스의 오브젝트를 선언하고 다음과 같이 사용해보자.
Person goodman;
goodman.setName("Kim Dae Jin");
qDebug() << "Goodman name : " << goodman.getName();
Person 클래스 오브젝트를 선언하고 setName( ) 멤버 함수를 이용해 변수 값을 설정하였다.
다음으로는 setName( ) 멤버 함수로 선언한 값을 getName( ) 멤버 함수로 값을 얻어 와 출력하는 예제 소스코드를 봐보자
예제 소스코드에서 살펴본 방법을 Qt 예서 제공하는 Property System 으로 접근해 보도록 하자.
...
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName)
public:
explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
m_name = n;
}
private:
QString m_name;
};
...
위의 예제 소스코드에서 보는것과 같이 getName( ) 과 setName( ) 멤버 함수를 Q_PROPERTY 매크로에 등록하였다.
그리고 다음과 같이 Q_PROPERTY 매크로에 등록한 멤버 함수들을 다음과 같이 사용할 수 있다.
Person goodman;
goodman.setProperty("name", "Kim Dae Jin");
qDebug() << "Goodman name : " << goodman.getName();
QVariant myName = goodman.property("name");
qDebug() << "My name is " << myName.toString();
// [ Result ]
// Goodman name : "Kim Dae Jin"
// My name is "Kim Dae Jin"
getName( ) 와 setName( ) 멤버 함수를 접근해 name 변수 값을 얻어오거나 설정할 수 있지만 Q_PROPERTY 매크로를 사용하면 setProperty( ) 와 property( ) 를 사용해 값을 얻어오거나 설정할 수 있다.
클래스에서 Q_PROPERTY 매크로는 그다지 큰 유용성이 없어 보인다. 하지만 QML을 사 용하는 경우 매우 요긴하게 사용할 수 있다. QML 로 UI를 개발하고 기능을 C++ 로 개발한다고 했을 때 QML과 C++ 간의 데이터 를 주고 받아야 하는 경우가 빈번하게 발생한다.
이 때 QML 에서 C++의 변수의 값을 얻어오거나 변경 하고자 할 때 Q_PROPERTY 매크로를 사용할 수 있다.
Q_PROPERTY 매크로는 다음과 같이 다양한 옵션을 사용할 수 있다.
Q_PROPERTY(type name(READ getFunction[WRITE setFunction] |
MEMBER memberName[(READ getFunction |
WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
MEMBER 키워드는 READ 키워드로부터 읽어 들일 값을 지정할 수 있다. 예를 들어 위 의 예제 소스코드에서 private 에서 선언한 m_name 변수를 다음과 같이 지정할 수 있다.
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name MEMBER m_name READ getName WRITE setName)
...
Q_PROPERTY 매크로가 제공하는 키워드 중 NOTIFY 키워드는 시그널 지정할 수 있다.
다음 예제에서 보는 것과 같이 시그널을 사용할 수 있다
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name MEMBER m_name READ getName WRITE setName
NOTIFY nameChanged)
public : explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
m_name = n;
emit nameChanged(n);
}
private:
QString m_name;
signals:
void nameChanged(const QString &n);
...
- Q_PROPERTY를 이용한 예제
이번에 다룰 예제는 아래 그림에서 보는 것과 같이 [Change] 버튼을 클릭하면 Person 클래스의 setName( ) 멤버 함수를 QObject 클래스에서 제공하는 setProperty( ) 멤버 함수를 이용해 값을 변경 한다.

그리고 setName( ) 멤버 함수를 호출되면 Q_PROPERTY 매크로에서 NOTIFY 키워드로 명시한 시그널 이벤트를 발생할 것이다.
다음 예제는 widget.h 의 헤더 소스코드이다.
...
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void buttonPressed();
void nameChanged(const QString &n);
private:
Ui::Widget *ui;
Person *goodman;
};
...
[변경] 버튼 클릭하면 buttonPressed( ) Slot 함수가 호출된다.
그리고 nameChanged( ) Slot 함수는 Person 클래스의 nameChanged( ) 시그널이 발생하면 호출된다.
다음 예제 소스코드는 widget.cpp 예제 소스코드 이다.
...
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::pressed, this, &Widget::buttonPressed);
goodman = new Person();
connect(goodman, &Person::nameChanged, this, &Widget::nameChanged);
}
void Widget::buttonPressed()
{
QString name = ui->leName->text();
goodman->setProperty("name", name);
}
void Widget::nameChanged(const QString &n)
{
qDebug() << Q_FUNC_INFO << "Name Changed : " << n;
QVariant myName = goodman->property("name");
qDebug() << "My name is " << myName.toString();
}
Widget::~Widget()
{
delete ui;
}
...
nameChanged( ) Slot 함수가 호출되면 Person 클래스에서 제공하는 getName( ) 멤버 함수를 QObject 클래스에서 제공하는 property( ) 함수를 이용해 값을 얻어온다.
다음 예제 소스코드는 Person 클래스의 헤더파일이다
...
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name MEMBER m_name READ getName
WRITE setName NOTIFY nameChanged)
public:
explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
m_name = n;
emit nameChanged(n);
}
private:
QString m_name;
signals:
void nameChanged(const QString &n);
};
...
Person 클래스에서 Q_PROPERTY 매크로를 이용해 getName( ) 와 setName( ) 멤버 함수를 접근할 수 있다. 그리고 nameChanged( ) 시그널을 Q_PROPERTY 매크로의 NOTIFY 키워드로 지정하였다. 따라서 setName( ) 멤버 함수에서 emit 을 이용해 시그널 이벤트가 발생하면 Widget 클래스의 연결된 Slot 함수가 호출 된다.
어려워서 책에 있는 내용으로 따로 공부를 해보았다.
1. 기본 C++ 클래스: Getter와 Setter 함수
먼저, Qt의 Property 시스템을 이해하기 전에 getter와 setter 함수부터 알아야 합니다. 이건 간단하게 객체의 속성(변수)에 값을 설정하거나 그 값을 가져오는 함수입니다.
예시: Person 클래스
class Person : public QObject // Qt에서 QObject 클래스를 상속
{
Q_OBJECT // Qt의 기본 매크로 (나중에 설명)
public:
explicit Person(QObject *parent = nullptr); // 생성자
QString getName() const // 이름을 가져오는 함수 (Getter)
{
return m_name;
}
void setName(const QString &n) // 이름을 설정하는 함수 (Setter)
{
m_name = n;
}
private:
QString m_name; // 개인 정보 (멤버 변수)
};
이 코드에서 중요한 부분:
m_name: 클래스 내부의 멤버 변수입니다. 이 값을 getName()으로 가져오고, setName()으로 설정할 수 있습니다.
getName(): 이름을 가져오는 함수입니다.
setName(): 이름을 설정하는 함수입니다.
사용 예시:
Person goodman; // Person 클래스의 객체를 만듭니다.
goodman.setName("Kim Dae Jin"); // setName으로 이름을 설정
qDebug() << "Goodman name: " << goodman.getName(); // 이름 출력
이 예시는 단순히 getter와 setter를 이용해 Person 객체의 이름을 설정하고 출력하는 것입니다.
2. Qt의 Q_PROPERTY 시스템
Q_PROPERTY는 Qt에서 제공하는 특별한 시스템입니다. 이걸 사용하면 속성 값을 더 쉽게 다룰 수 있고, 특히 QML 같은 곳에서 유용합니다. Q_PROPERTY를 사용하면, 속성을 Qt의 기본 기능인 setProperty()와 property()로 제어할 수 있습니다.
Q_PROPERTY를 사용한 클래스:
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName) // Property 선언
public:
explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
m_name = n;
}
private:
QString m_name;
};
이 코드에서 중요한 부분:
Q_PROPERTY: 이 매크로는 name이라는 속성을 정의합니다. READ는 getter 함수를 지정하고, WRITE는 setter 함수를 지정합니다. 즉, getName()으로 값을 읽고, setName()으로 값을 설정하는 속성입니다.
3. Q_PROPERTY로 속성 다루기
Q_PROPERTY를 사용하면 속성을 직접 관리하지 않고, Qt의 setProperty()와 property() 함수를 사용할 수 있습니다.
예시:
Person goodman; // Person 객체 생성
goodman.setProperty("name", "Kim Dae Jin"); // setProperty로 이름 설정
qDebug() << "Goodman name: " << goodman.getName(); // getName으로 이름 가져옴
QVariant myName = goodman.property("name"); // property로 이름을 가져옴
qDebug() << "My name is: " << myName.toString(); // 이름 출력
여기서 setProperty()와 property()는 Q_PROPERTY로 등록된 name 속성을 제어합니다. Q_PROPERTY를 통해 클래스 내부 멤버 변수에 접근할 수 있습니다.
4. 시그널과 슬롯 (Signals and Slots) 적용
Qt에서는 시그널과 슬롯을 사용해 객체 간의 통신을 쉽게 할 수 있습니다. 예를 들어, 속성이 변경될 때 시그널을 발생시켜 다른 객체에 알릴 수 있습니다.
예시: 시그널 추가
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name MEMBER m_name READ getName WRITE setName NOTIFY nameChanged) // Property 선언
public:
explicit Person(QObject *parent = nullptr);
QString getName() const
{
return m_name;
}
void setName(const QString &n)
{
if (m_name != n) {
m_name = n;
emit nameChanged(n); // 이름이 바뀌면 시그널 발생
}
}
signals:
void nameChanged(const QString &n); // 이름 변경 시 발생하는 시그널
private:
QString m_name;
};
시그널: nameChanged라는 시그널은 setName() 함수에서 이름이 변경될 때 발생합니다.
emit: 시그널을 발생시키는 역할을 합니다.
5. 시그널과 슬롯을 이용한 버튼 클릭 예제
UI에서 버튼을 클릭할 때 Person 객체의 이름을 변경하고, 그 결과를 출력하는 예제입니다.
헤더 파일 (widget.h):
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void buttonPressed(); // 버튼 클릭 시 호출되는 슬롯
void nameChanged(const QString &n); // 이름 변경 시 호출되는 슬롯
private:
Ui::Widget *ui;
Person *goodman;
};
구현 파일 (widget.cpp):
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::pressed, this, &Widget::buttonPressed); // 버튼 클릭 연결
goodman = new Person(); // Person 객체 생성
connect(goodman, &Person::nameChanged, this, &Widget::nameChanged); // 시그널과 슬롯 연결
}
void Widget::buttonPressed()
{
QString name = ui->leName->text(); // 사용자로부터 이름 입력
goodman->setProperty("name", name); // 속성 설정
}
void Widget::nameChanged(const QString &n)
{
qDebug() << "Name Changed: " << n;
QVariant myName = goodman->property("name"); // 속성 가져오기
qDebug() << "My name is: " << myName.toString(); // 이름 출력
}
Widget::~Widget()
{
delete ui;
}
동작:
버튼을 클릭하면 buttonPressed() 슬롯이 호출됩니다.
입력된 이름을 Person 객체의 setProperty()로 설정합니다.
이름이 변경되면 nameChanged() 시그널이 발생하고, 그 결과를 출력합니다.
6. 요약
Getter/Setter: 클래스의 속성(변수)을 설정하거나 가져오는 함수.
Q_PROPERTY: Qt에서 제공하는 특별한 속성 시스템으로, setProperty()와 property() 같은 메서드를 사용해 속성을 더 쉽게 다룰 수 있음.
시그널과 슬롯: 속성 값이 변경될 때 발생하는 이벤트 시스템. 시그널은 값이 변경되었을 때 발생하고, 슬롯은 이 시그널을 받아서 처리하는 함수.
16. Model and View
Qt에서는 다음 그림에서 보는 것과 같이 표와 같은 위젯에 데이터를 표시하기 위한 위 젯으로 QListWidget, QTableWidget, QTreeWidget, QListView, QTableView, QTreeView, QColumnView 등 다양한 클래스를 제공한다.

QListWidget 은 QListView와 UI가 동일하다.
하지만 QListWidget 과 QListView 는 데이터를 삽입/수정/삭제 하는데 차이가 있다.
클래스 이름의 마지막에 View 대신, Widget 이라는 단어를 사용한 클래스들은
아래 그림에서 보는 것과 같이 데이터를 직접 삽입/수정/삭제 할 수 있는 멤버 함수를 제공한다.

클래스 이름 마지막에 Widget 이라는 단어가 쓰인 위젯 클래스들은 모두 직접 데이터 를 삽입/수정/삭제 가 가능한 멤버 함수를 제공한다. 예를 들어 QListWidget 위젯 클래 스는 insertItem( ) 함수를 이용해 데이터를 삽입할 수 있다.
하지만 각 위젯 클래스들은 사용방법이 다소 차이가 있으므로 각각의 위젯 클래스들의 사용 방법을 익혀야 한다.
그리고 QListView, QTableView, QTreeView 클래스와 같이 마지막에 View 라는 단어를 사용하는 위젯 클래스들은 각각의 멤버 함수를 사용해 데이터를 삽입/수정/삭제 하지 않고 Model 클래스라는 매개체를 이용해 데이터를 삽입/수정/삭제 할 수 있다.

이 방식을 사용할 경우 QListView 를 사용하든지 QTableView 를 사용하든지 동일한 Model을 사용할 수 있다는 장점을 가지고 있다. Qt 에서 제공하는 Model/View 는 다음 그림에서 보는 것과 같이 Delegate를 이용해 데이터를 핸들링 할 수 있다.
예를 들어 View 위젯 상에 표시된 데이터 항목 중 특정 항목을 마우스로 더블 클릭해 데이터를 수정하기 위해서 이벤트를 발생해야 하는데 Model/View 에서는 이러한 이벤트를 처리 하기 위해 Delegate를 사용할 수 있다.

Model 클래스는 데이터를 관리(삽입/수정/삭제) 하기 위한 기능을 제공한다. 예를 들어 QSqlQueryModel 클래스를 이용하면 SQL 문을 직접 쿼리(QUERY) 할 수 있는 멤버 함수를 제공한다. 따라서 별도의 데이터베이스 쿼리로 가져온 데이터를 편집 후에 Model 을 이용해도 되지만 QSqlQueryModel 클래스를 이용하면 직접 데이터를 삽입할 수 있다.
이외에도 다음 그림에서 보는 것과 같이 다양한 Model 클래스를 제공한다.

QStringListModel 클래스는 QString 데이터 타입의 단순한 데이터 리스트를 관리할수 있 는 기능을 제공한다.
QStringListModel *model = new QStringListModel();
QStringList list;
list << "Hello World" << "Qt Programming" << "Model is Good";
model->setStringList(list);
...
QStandardItemModel 클래스는 테이블 형태 또는 트리 형태와 같이 데이터를 관리할 수 있는 기능을 제공한다.
QStandardItemModel model(4, 4);
for (int row = 0; row < 4; ++row)
{
for (int column = 0; column < 4; ++column)
{
QString data = QString("row %0, column %1").arg(row).arg(column);
QStandardItem *item = new QStandardItem(data);
model.setItem(row, column, item);
}
}
...
QFileSystemModel 클래스는 파일 시스템으로부터 일어온 파일과 디렉토리를 관리할 수 있는 기능을 제공한다.
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
QTreeView *tree = new QTreeView(this);
tree->setModel(model);
...
QSqlQueryModel 클래스는 SQL 문을 이용해 데이터베이스 테이블로부터 데이터를 액세스 할 수 있는 기능을 제공하며 QSqlTableModel 클래스는 데이터베이스로부터 데이 터를 가져올 때 특정 테이블 명을 인자로 전달하면 Model에 데이터를 가져올 수 있다.
예를 들어 setTable( ) 멤버 함수의 첫 번째 인자로 테이블 명을 입력하면 데이터베이스 테이블로부터 데이터를 가져올 수 있다.
QSqlQueryModel *model = new QSqlQueryModel;
model->setQuery("SELECT name, salary FROM employee");
model->setHeaderData(0, Qt::Horizontal, tr("Name"));
model->setHeaderData(1, Qt::Horizontal, tr("Salary"));
QTableView *view = new QTableView;
view->setModel(model);
view->show();
...
Model/View 를 이용해 많은 양의 데이터를 표시할 때 다음 그림에서 보는 것과 같이 3가지 종류로 구분할 수 있다.

가장 좌측에 있는 데이터 형태라면 QListView 클래스를 사용하는 것이 적합하다.
표와 같이 표시해야 한다면 QTableView 를 사용하는 것이 적합하다.
그리고 트리 형태로 표 시해야 한다면 QTreeView를 사용하는 것이 적합하다.
- QTableView 클래스를 이용한 예제
QTableView 클래스는 아래 그림에서 보는 것과 같이 표 형태의 데이터를 표시하는데 적합한 위젯이다.

...
QStandardItemModel *model = new QStandardItemModel(0, 3);
model->setHeaderData(0, Qt::Horizontal, QObject::tr("Subject"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Description"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Date"));
model->setVerticalHeaderItem(0, new QStandardItem("Col 1"));
model->setVerticalHeaderItem(1, new QStandardItem("Col 2"));
model->setData(model->index(0, 0), "Monitor");
model->setData(model->index(0, 1), "LCD")
model->setData(model->index(0, 2), QDate(2030, 10, 4));
model->setData(model->index(1, 0), "CPU");
model->setData(model->index(1, 1), "Samsung");
model->setData(model->index(1, 2), QDate(2030, 12, 5));
QTableView *table = new QTableView();
table->setModel(model);
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(table);
setLayout(lay);
...
- QTableWidget 예제
이번 예제는 Model 을 사용하지 않고 QTableWidget 을 이용해 데이터를 삽입하는 예제를 다루어 볼 것이다.
그리고 첫 번째 Column 에 QCheckBox 위젯을 삽입하는 방법 에 대해서 알아보도록 하자.

위의 그림에서 보는 것과 같이 QTableWidget 헤더에 체크 박스가 있다.
이 체크박스를 변경하면 모든 라인의 컬럼에의 값이 헤더에 있는 체크 박스의 값과 동일하게 변경이 가능하다.
그리고 각각의 체크 박스의 값을 사용자가 변경할 수 있다. Qt는 QTableWidget 의 헤더(Header)에 원하는 모양 또는 특정 위젯이 추가된 헤더를 커스터마이징 하기 위한 방법을 제공한다.
QHeaderView 클래스를 상속받아 커스터마이징한 헤더(Header)를 QTableWidget 클래스의 setHorizontalHeader( ) 멤버 함수를 이용해 커스터마이징한 헤더를 추가 할 수 있다. 다음 예제 소스코드는 widget.cpp 소스코드이다.
...
#include "checkboxheader.h"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
ui->tableWidget->setRowCount(5);
ui->tableWidget->setColumnCount(2);
CheckBoxHeader *header;
header = new CheckBoxHeader(Qt::Horizontal, ui->tableWidget);
ui->tableWidget->setHorizontalHeader(header);
connect(header, &CheckBoxHeader::checkBoxClicked,
this, &Widget::checkBoxClicked);
QStringList nameList;
nameList << "Notebook" << "Mobile" << "Desktop" << "Keyboard" << "Monitor";
for (int i = 0; i < 5; i++)
{
ui->tableWidget->insertRow(i);
QTableWidgetItem *dateItem = new QTableWidgetItem("2021.05.07");
dateItem->setCheckState(Qt::Checked);
ui->tableWidget->setItem(i, 0, dateItem);
ui->tableWidget->setItem(i, 1, new QTableWidgetItem(nameList.at(i)));
}
}
void Widget::checkBoxClicked(bool state)
{
for (int i = 0; i < 5; i++)
{
QTableWidgetItem *item = ui->tableWidget->item(i, 0);
if (state)
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
}
...
위의 예제에서 CheckBoxHeader 클래스는 QHeaderView 클래스를 상속받아 구현한 클래스이다.
이 클래스를 헤더로 사용하기 위해서 setHorizontalHeader( ) 멤버 함수를 사용하면 된다.
그리고 checkBoxClicked( ) 는 헤더의 체크박스가 변경되면 발생한 이벤트와 연결된 Slot 함수이다.
이 Slot 함수에서는 헤더의 체크 박스의 값에 따라 첫 번째 컬럼의 체크 박스의 값을 동일하게 변경한다.
다음 예제 소스코드는 CheckBoxHeader 클래스의 헤더파일이다.
...
class CheckBoxHeader : public QHeaderView
{
Q_OBJECT
public:
CheckBoxHeader(Qt::Orientation orientation, QWidget *parent = nullptr);
bool isChecked() const { return isChecked_; }
void setIsChecked(bool val);
signals:
void checkBoxClicked(bool state);
protected:
void paintSection(QPainter *painter, const QRect &rect,
int logicalIndex) const;
void mousePressEvent(QMouseEvent *event);
private:
bool isChecked_;
void redrawCheckBox();
};
...
paintSection( ) 는 Virtual 함수이다. 헤더 영역의 마우스가 클릭되면 이 함수를 호출해 체크 박스의 값에 따라 체크박스 상태를 변경하는 기능을 제공한다.
다음 예제는 checkboxheader.cpp 소스코드이다.
#include "checkboxheader.h
CheckBoxHeader::CheckBoxHeader(Qt::Orientation orientation, QWidget *parent)
: QHeaderView(orientation, parent)
{
isChecked_ = true;
}
void CheckBoxHeader::paintSection(QPainter *painter, const QRect &rect,
int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
if (logicalIndex == 0)
{
QStyleOptionButton option;
option.rect = QRect(1, 3, 20, 20);
option.state = QStyle::State_Enabled | QStyle::State_Active;
if (isChecked_)
option.state |= QStyle::State_On;
else
option.state |= QStyle::State_Off;
option.state |= QStyle::State_Off;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
}
}
void CheckBoxHeader::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event)
setIsChecked(!isChecked());
emit checkBoxClicked(isChecked());
}
void CheckBoxHeader::redrawCheckBox()
{
viewport()->update();
}
void CheckBoxHeader::setIsChecked(bool val)
{
if (isChecked_ != val)
{
isChecked_ = val;
redrawCheckBox();
}
}
void CheckBoxHeader::setIsChecked(bool val)
{
if (isChecked_ != val)
{
isChecked_ = val;
redrawCheckBox();
}
}
mousePressEvent( ) 는 헤더 위젯 영역에 마우스가 클릭 이벤트가 발생되면 호출 되는 Virtual 함수이다.