실습 위주다 보니, 산발적이여서 별도로 정리할려고 한다. Chat GPT롤 활용했다.
Qt 플레임워크 : C++ 기반 크로스 플랫폼 애플리케이션 프레임워크로 GUI 어플리케이션 제작에 주로 활용됨
Qt의 주요 구조
- QtCore : 비 GUI 관련 핵심 기능을 제공, 이벤트 루프, 신호-슬롯 메커니즘, 문자열 처리, FILE IO, 멀티 쓰레드 처리 등
- QtGui : GUI 요소를 그리기 위한 기능을 제공, 이벤트 처리, 2D 그래픽 처리, 텍스트 렌더링, 이미지 처리 등, QPainter, QPixmap, QImage 등이 포함된다.
- QtWidgets : 실제 GUI 위젯(버튼, 라벨, 윈도우 등)을 제공하는 모듈, QWidget, QMainWindow, QPushButton, QLabel 등 다양한 UI 요소 포함된다.
- 기타 모듈 : QtNetwork, QtMultimedia, QtSql, QtTest 등등
Qt의 주요 개념
- QObject : Qt의 거의 모든 글래스의 기본 클래스
- Signal-Slot 시스템 사용 가능 : 이벤트인 Signal(비정의 함수, argument를 그대로 반환하여, slot으로 전달)이 발생하면 Slot 함수를 실행시킨다. 비동기 적이고, 느슨한 결합 구조를 제공한다. connect() 함수를 사용
- connect(sender, &Class::signalName, receiver, &Class::slotFunction);
- Object Tree 구조 지원
- 동적 프로퍼티 추가 및 메타 오프젝트 시스템(MOC) 활용 가능
위젯 시스템(QWidget 기반 클래스들)
모든 GUI 요소는 QWidget 또는 파생 클래스
- QWidget: 모든 위젯의 기본 클래스
- QMainWindow: 메뉴, 툴바, 상태바를 포함하는 메인 윈도우
- QDialog: 모달/모달리스 대화 상자
- QPushButton, QLabel, QLineEdit: 각종 위젯 클래스
레이아웃 시스템
- 위젯의 자동 배치를 위한 레이아웃 매니저 제공
- 종류: QHBoxLayout, QVBoxLayout, QGridLayout, QFormLayout
- 코드나 디자이너 툴에서 지정 가능
프로그램 실행 흐름 : QApplication 객체 생성(이벤트 루프 관련, 시작점) -> 윈도우 객체 생성(UI 요소 생성, 시그널/슬롯 연결) -> show()(실제로 보이도록 함) -> 이벤트 루프(exec() 루프 진입)
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QPushButton button("Hello Qt!");
button.show();
return app.exec();
}
디자인의 변천(심플하고 실용적인 방향으로, 너무 예쁘지도 않게, 너무 못생기지도 않게)
UI 기획
내부구현 : 플랫폼, 자원, 시간, 인력 등
소프트웨어 내부 구현 : 데이터(static, dynamic), 메모리, 스토리지, 클래스 구조
구현, 테스트
QObject -> QWidget -> QPushButton, QLayerout, etc
QWidget은 인스턴스 생성 후 show 함수로 창을 띄울 수 있다.
기본적으로 value() <- getter 함수, setValue() <- setter 함수로 구성된다.
Qt는 다양한 위젯을 제공한다. 대부분의 위젯이 사용 방법이 같다.(객체 선언 및 생성 -> 설정 -> signal/slot 연결 etc)
QObject는 위젯들이 활용하기 좋게 QString, QChar 등 다양한 데이터 타입을 제공한다.
큰 요소 ⊃ 작은 요소 ⊃ 더 작은 요소 방식으로 프로그램 구성
예시: 빠르게 1 ~ 25까지 순서대로 클릭하는 게임
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTimer>
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
//시간 측정 객체
//현재 상태 : 대기 상태, 진행 상태(상호 교환시 발생하는 일, 해야하는 일)
int state{0}; // 0 : wait, 1 : play
void setState(int st);
int getState();
//현재 눌러야 할 버튼 번호
int targetNum;
private:
QVBoxLayout* vLayout; //canvas
QHBoxLayout* hLayout; //control
QGridLayout* gLayout; //main
QLabel* timerLabel;
QTimer* timer;
int timee;
QPushButton* startButton;
QPushButton* resetButton;
QPushButton* numButtons[25];
void initRandomNumber();
void clearNumber();
signals:
void clear();
private slots:
void startButtonPushed();
void resetButtonPushed();
void numButtonPushed();
void timerUpdate();
};
#endif // WIDGET_H
Widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//ui->setupUi(this);
/*Layout*/
vLayout = new QVBoxLayout(); //total
hLayout = new QHBoxLayout(); //start + timer + end
gLayout = new QGridLayout(); //main
vLayout->addLayout(hLayout);
vLayout->addLayout(gLayout);
setLayout(vLayout);
/*Timer*/
timer = new QTimer();
timerLabel = new QLabel();
timerLabel->setStyleSheet("font:32pt; font-weight:bold;");
timerLabel->setText("00:00");
connect(timer, &QTimer::timeout, this, &Widget::timerUpdate);
/*Button*/
startButton = new QPushButton("Start");
connect(startButton, &QPushButton::clicked, this, &Widget::startButtonPushed);
resetButton = new QPushButton("Reset");
connect(resetButton, &QPushButton::clicked, this, &Widget::resetButtonPushed);
hLayout->addWidget(startButton, 0);
hLayout->addWidget(timerLabel, 1);
hLayout->addWidget(resetButton, 2);
for(int i = 0; i < 25; i++){
numButtons[i] = new QPushButton(this); //어느 객체와 연결된 버튼인가?
gLayout->addWidget(numButtons[i], i/5, i%5);
connect(numButtons[i], &QPushButton::clicked, this, &Widget::numButtonPushed);
}
connect(this, &Widget::clear, this, &Widget::resetButtonPushed);
initRandomNumber();
setState(0);
timerLabel->setText("00:00");
}
Widget::~Widget()
{
delete startButton;
delete startButton;
for(int i = 0; i < 25; i++) delete numButtons[i];
delete hLayout;
delete gLayout;
delete vLayout;
}
void Widget::numButtonPushed(){
//현재 누른 버튼 번호 확인
//그 번호랑 targetNum 일치하는 지 확인
//일치하지 않으면 잘못눌럿으므로 아무 일도 안일어남
//일치하면 버튼 사라짐 + num++
QPushButton* btn = (QPushButton*)sender();
int num = btn->text().toInt();
if(num == targetNum){
btn->setEnabled(false);
targetNum++;
if(targetNum > 25) emit clear();
}
}
void Widget::startButtonPushed(){
setState(1);
}
void Widget::resetButtonPushed(){
setState(0);
}
void Widget::initRandomNumber(){
/*game initialization*/
timer->start();
timee = 0;
//target number init
targetNum = 1;
std::vector<int> nums(25);
std::iota(nums.begin(), nums.end(), 1);
std::srand(time(NULL));
std::random_shuffle(nums.begin(), nums.end());
for(int i = 0; i < 25; i++){
numButtons[i]->setText(QString("%1").arg(nums[i]));
//numButtons[i]->setProperty("num", nums[i]); 동적 속성 부여
numButtons[i]->setEnabled(true);
numButtons[i]->show();
}
}
void Widget::clearNumber(){
timer->stop();
timerLabel->setText(QString("%1:%2").arg(timee/1000).arg((timee%1000)/10));
for(int i = 0; i < 25; i++){
numButtons[i]->setText(" ");
numButtons[i]->setEnabled(false);
numButtons[i]->show();
}
}
void Widget::setState(int st){
state = st;
if(state == 0){
clearNumber();
}
else{
initRandomNumber();
}
}
int Widget::getState(){
return state;
}
void Widget::timerUpdate(){
timee++;
timerLabel->setText(QString("%1:%2").arg(timee/1000).arg(timee%1000));
}
순발력 게임 : 초록색이 나오면 누르기!!
헤더
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
//Ui::Widget *ui;
/*layer*/
QVBoxLayout *layout;
/*element*/
QTimer *sysTimer;
int systime;
int random_start;
int to;
QPushButton *button;
private:
int state; //0 for wait, 1 for playing, 2 for result
char buttonColor; // 'r', 'g'
private slots:
void systemTime();
void toggleColor();
void pushedButton();
void reset();
void result();
void gameover();
};
#endif // WIDGET_H
cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
//, ui(new Ui::Widget)
{
//ui->setupUi(this);
/*layer*/
layout = new QVBoxLayout();
/*element*/
sysTimer = new QTimer();
connect(sysTimer, &QTimer::timeout, this, &Widget::systemTime);
button = new QPushButton("녹색에 클릭");
button->setStyleSheet("font:32pt; color:white;background-color:red");
connect(button, &QPushButton::clicked, this, &Widget::pushedButton);
/*combine*/
layout->addWidget(button);
setLayout(layout);
/*reset*/
reset();
}
Widget::~Widget()
{
delete sysTimer;
delete button;
delete layout;
}
void Widget::systemTime(){
if(state == 1){
systime++;
if(systime == random_start){
toggleColor();
}
}
}
void Widget::toggleColor(){
if(buttonColor == 'r'){
buttonColor = 'g';
button->setStyleSheet("font:32pt; color:white; background-color:green");
}
else{
buttonColor = 'r';
button->setStyleSheet("font:32pt; color:white; background-color:red");
}
}
void Widget::pushedButton(){
if(state == 0){
state = 1;
sysTimer->start(1);
qDebug() << "0";
}
else if(state == 1){//state = 1
if(buttonColor == 'g'){
to = systime;
sysTimer->stop();
result();
qDebug() << "1g";
}
else{
sysTimer->stop();
gameover();
qDebug() << "1r";
}
}
else{
reset();
qDebug() << "2r";
}
}
void Widget::reset(){
sysTimer->stop();
systime = 0;
std::srand(time(0));
random_start = rand()%2000;
to = 0;
state = 0;
buttonColor = 'r';
button->setText("녹색에 클릭");
button->setStyleSheet("font:32pt; color:white; background-color:red");
}
void Widget::result(){
button->setText(QString("%1 ms").arg(to-random_start));
button->setStyleSheet("font:32pt; color:white; background-color:red");
toggleColor();
state = 2;
}
void Widget::gameover(){
button->setText("녹색에 누르라고!!!");
state = 2;
}
MVVM 패턴
[디자인 패턴] MVVM 패턴이란?
개념 :MVVM (Model-View-ViewModel) 패턴은 Model View, View, Model의 약자로 프로그램의 비지니스 로직과, 프레젠테이션 로직을 UI로 명확하게 분리하는 패턴입니다.데이터를 다루는 부분. 비즈니스 로직을
velog.io
QGui : MFC에서paint를 했었던 것과 매우 유사하다.
- QWidget: 사용자 정의 GUI 요소의 베이스
- QPainter: 드로잉 기능 제공 (직선, 원, 이미지, 텍스트 등)
- QImage, QPixmap: 이미지 객체 표현
Qt 파일입출력 : C++에서 제공하는 stream 기반 파일입출력 방식 사용
- QFile : 파일 열기, 읽기, 쓰기 등 핵심 클래스
- QTextStream : 텍스트 기반 스트림 입출력
- QDataStream : 이진 파일 입출력
- QFileDialog : 사용자로부터 파일을 선택하거나 저장 위치 선택
쓰레드 프로그래밍 :
주의점 : UI 쓰레드와 Work 쓰레드의 분리, critical section의 통제, 비동기 프로그래밍(signal - slot으로 구현됨)
UI 쓰레드는 중지되서는 안된다.(사용자 입장에서 당황스럽다.)
1. QThread 상속 방식
개요
QThread 클래스를 상속받아 run() 메서드를 오버라이딩하고, 그 안에서 쓰레드 작업을 수행하는 방식입니다.
예시 코드
class MyThread : public QThread {
Q_OBJECT
protected:
void run() override {
// 시간 소모적인 작업 수행
for (int i = 0; i < 5; ++i) {
qDebug() << "Thread running: " << i;
QThread::sleep(1);
}
emit workFinished();
}
signals:
void workFinished();
};
// 위젯 클래스에서 사용
MyThread* thread = new MyThread();
connect(thread, &MyThread::workFinished, this, &MainWindow::onWorkFinished);
thread->start();
장점
- 구현이 직관적이고 단순함
단점
- QThread는 쓰레드 제어용 객체로 설계되었기 때문에, 실제 작업 로직을 QThread에 직접 넣는 것은 권장되지 않음
- 확장성이 낮고 디버깅이 어려울 수 있음
2. QObject 기반 Worker 사용 방식 (권장 방식)
개요
작업 로직을 QObject 기반 클래스에 구현하고, 이를 QThread에 moveToThread()로 옮기는 방식입니다. Qt의 신호-슬롯 메커니즘을 활용하여 쓰레드 간 안전한 통신을 할 수 있습니다.
예시 코드
Worker 클래스
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
for (int i = 0; i < 5; ++i) {
qDebug() << "Working in thread: " << QThread::currentThread();
QThread::sleep(1);
}
emit workDone();
}
signals:
void workDone();
};
위젯 클래스(MainWindow 등)에서의 사용
Worker* worker = new Worker();
QThread* thread = new QThread();
worker->moveToThread(thread);
// 쓰레드가 시작되면 doWork() 호출
connect(thread, &QThread::started, worker, &Worker::doWork);
// 작업 완료 시 슬롯 호출
connect(worker, &Worker::workDone, this, &MainWindow::onWorkFinished);
// 작업 완료 후 쓰레드 정리
connect(worker, &Worker::workDone, thread, &QThread::quit);
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
장점
- UI 쓰레드와 작업 쓰레드를 명확히 분리
- 객체 간 신호/슬롯으로 쓰레드 간 통신 → race condition 방지
- 확장성과 유지보수성 뛰어남
단점
- 구조가 다소 복잡함 (그러나 일단 패턴을 익히면 재사용 가능)
위젯에서의 주의사항
- UI 요소 접근 제한: 모든 UI 위젯은 반드시 메인 쓰레드(UI 쓰레드)에서만 접근해야 합니다. 작업 쓰레드에서 UI를 직접 수정하는 경우, 크래시 혹은 undefined behavior 발생 가능.
- 신호/슬롯 방식 사용: 쓰레드 간 통신은 반드시 signal-slot 메커니즘으로 처리해야 하며, 특히 Qt::QueuedConnection 방식으로 호출되도록 유의해야 합니다 (기본적으로 다른 쓰레드 간 연결은 이 방식 사용됨).
'VEDA 복습' 카테고리의 다른 글
VEDA 24일차 - 임베디드 시스템 이해 (0) | 2025.04.17 |
---|---|
VEDA 21일차 ~ 23일차 - Qt 프로젝트(일정 관리 어플리케이션) (0) | 2025.04.16 |
VEDA 17일차 - Qt 프로그래밍 (0) | 2025.04.08 |
VEDA 16일차 - Qt (0) | 2025.04.07 |
VEDA 14일차 - C++ (0) | 2025.04.03 |