◆ CP12.QMainWindow를 이용한 GUI구현
- 지금까지 다룬 방식은 QWidget을 이용해 한 개의 윈도우 환면만 존재하는 방식으로 GUI를 구성하였다. 하지만 기능이 복잡하고 사용자게엑 많은 기능을 제공해야 하는 경우 GUI 구현 시 QWidget 보다는 QMainWindow를 이용해 GUI를 구현하는 것이 사용자에게 직관적인 GUI를 제공할 수 있다.
- QT는 MDI(Multi Document Interface) 방식을 구현할 수 있다. MDI 방식은 QMdiArea 클래스를 이용해 구현할 수 있다.
◇ QMdiArea 클래스를 이용한 MDI 기반의 GUI 예제
- 이 예제의 소스코드는 2개의 클래스로 구현되어 있다. MainWindow 클래스는 QMainWindow 클래스를 상속받는 위젯 클래스이다. 이 클래스는 MainWindow 중앙 배치할 다중 에디트 위젯으로 사용한다.즉 여러개의 텍스트 파일을 하나의 윈도우 영역 안에서 편집할 수 있는 기능을 제공하기 위해 구현된 클래스이다.
- MainWindow 헤더
#ifndef MAINWINDOW_H // MAINWINDOW_H가 정의되지 않았다면...
#define MAINWINDOW_H // 이 파일 포함 시작.
#include <QMainWindow> // QMainWindow 헤더 포함, 주 창 클래스를 위한 기본 클래스.
#include "MDIMainwindow.h" // MDI 창에 필요한 헤더 포함.
class MainWindow : public QMainWindow // MainWindow 클래스, QMainWindow 상속.
{
Q_OBJECT // 시그널과 슬롯 사용 선언.
public:
MainWindow(QWidget *parent = nullptr); // 생성자, 부모 위젯을 받음, 기본값은 nullptr.
~MainWindow(); // 소멸자.
private: // 프라이빗 섹션, 외부 접근 불가.
private slots: // 슬롯 섹션, 시그널에 반응하는 함수들.
void newFile(); // 새 파일 생성 함수.
void open(); // 파일 열기 함수.
};
#endif // MAINWINDOW_H // 헤더 파일 끝.
- newFile() Slot 함수는 메뉴 바에서 [New] 메뉴를 클릭했을 때 호출된다. open() 함수는 [OPEN] 메뉴를 클릭하면 호출된다.
- mainwindow.cpp 소스코드
#include "mainwindow.h" // MainWindow 헤더 포함.
#include <QMenu> // QMenu 헤더 포함, 메뉴 생성에 필요.
#include <QAction> // QAction 헤더 포함, 메뉴 액션 생성에 필요.
#include <QMenuBar> // QMenuBar 헤더 포함, 메뉴 바 생성에 필요.
#include <QToolBar> // QToolBar 헤더 포함, 도구 모음 생성에 필요.
#include <QDockWidget> // QDockWidget 헤더 포함, 도킹 가능한 위젯 생성에 필요.
#include <QListWidget> // QListWidget 헤더 포함, 리스트 위젯 생성에 필요.
#include <QStatusBar> // QStatusBar 헤더 포함, 상태 표시줄 생성에 필요.
#include <QDebug> // QDebug 헤더 포함, 디버그 메시지 출력에 필요.
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) // 부모 위젯을 QMainWindow에 전달.
{
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()); // 중앙 위젯으로 MDI 창 설정.
statusBar()->showMessage(tr("Ready")); // 상태 표시줄에 "Ready" 메시지 표시.
}
MainWindow::~MainWindow() // 소멸자.
{
// 특별한 작업 없음.
}
// [SLOTS]
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 윈도우를 제공하는 것과 같은 기능을 제공한다.
- MDIMinWindow 클래스 헤더
#ifndef QMDIMAINWINDOW_H // QMDIMAINWINDOW_H가 정의되지 않았다면...
#define QMDIMAINWINDOW_H // 이 파일 포함 시작.
#include <QMainWindow> // QMainWindow 헤더 포함, 주 창 클래스를 위한 기본 클래스.
#include <QObject> // QObject 헤더 포함, Qt 객체 시스템을 위한 클래스.
class MDIMainWindow : public QMainWindow // MDIMainWindow 클래스, QMainWindow 상속.
{
Q_OBJECT // 시그널과 슬롯 사용 선언.
public:
explicit MDIMainWindow(QWidget *parent = nullptr); // 생성자, 부모 위젯을 받음, 기본값은 nullptr.
};
#endif // QMDIMAINWINDOW_H // 헤더 파일 끝.
- 이 클래스 성성자에서는 QMdiARea 클래스와 QMdiSubWindow 클래스를 이용해 MDIMainWindow 하위에 서브 윈도우로 등록할 윈도우를 생성한다.
- MDIMainwindow.cpp 소스코드
#include "MDIMainwindow.h" // MDIMainWindow 헤더 포함.
#include <QMdiArea> // QMdiArea 헤더 포함, MDI 영역 생성에 필요.
#include <QMdiSubWindow> // QMdiSubWindow 헤더 포함, MDI 서브 윈도우 생성에 필요.
#include <QPushButton> // QPushButton 헤더 포함, 버튼 생성에 필요.
MDIMainWindow::MDIMainWindow(QWidget *parent)
: QMainWindow(parent) // 부모 위젯을 QMainWindow에 전달.
{
setWindowTitle(QString::fromUtf8("My MDI")); // 창 제목 설정.
QMdiArea* area = new QMdiArea(); // MDI 영역 생성.
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); // 크기 설정.
// MDI 영역에 서브 윈도우 추가.
area->addSubWindow(subWindow1); // 첫 번째 서브 윈도우 추가.
area->addSubWindow(subWindow2); // 두 번째 서브 윈도우 추가.
setCentralWidget(area); // 중앙 위젯으로 MDI 영역 설정.
}

◆ CP13.Stream
- Stream이란 데이터를 특정 변수에 Write/Read를 쉽게 하기 위한 방법을 말한다.
- QdataStream은 Binart 데이터를 Write/Read하는데 사용하며 QTextStream은 Text 기반의 데이터를 Write/Read 하는데 사용된다.
◇ QDataStream을 이용한 예제
#include <QCoreApplication> // QCoreApplication 헤더 포함, 콘솔 애플리케이션을 위한 기본 클래스.
#include <QIODevice> // QIODevice 헤더 포함, I/O 장치 관련 클래스.
#include <QDataStream> // QDataStream 헤더 포함, 데이터 스트림 관련 클래스.
#include <QDebug> // QDebug 헤더 포함, 디버그 메시지 출력을 위한 클래스.
QByteArray encoding() // 인코딩 함수.
{
quint32 value1 = 123; // 32비트 부호 없는 정수.
quint8 value2 = 124; // 8비트 부호 없는 정수.
quint32 value3 = 125; // 32비트 부호 없는 정수.
QByteArray outData; // 출력 데이터 저장을 위한 QByteArray.
QDataStream outStream(&outData, QIODevice::WriteOnly); // 출력 데이터 스트림 생성.
outStream << value1; // value1을 스트림에 추가.
outStream << value2; // value2를 스트림에 추가.
outStream << value3; // value3을 스트림에 추가.
qDebug() << "outData size : " << outData.size() << " Bytes"; // 데이터 크기 출력.
return outData; // 인코딩된 데이터 반환.
}
void decoding(QByteArray _data) // 디코딩 함수.
{
QByteArray inData = _data; // 입력 데이터 설정.
quint32 inValue1 = 0; // 32비트 부호 없는 정수 초기화.
quint8 inValue2 = 0; // 8비트 부호 없는 정수 초기화.
quint32 inValue3 = 0; // 32비트 부호 없는 정수 초기화.
QDataStream inStream(&inData, QIODevice::ReadOnly); // 입력 데이터 스트림 생성.
inStream >> inValue1; // 스트림에서 value1 읽기 (123).
inStream >> inValue2; // 스트림에서 value2 읽기 (124).
inStream >> inValue3; // 스트림에서 value3 읽기 (125).
qDebug("[First : %d] [Second : %d] [Third : %d]", inValue1, inValue2, inValue3); // 읽은 값 출력.
}
int main(int argc, char *argv[]) // 메인 함수.
{
QCoreApplication a(argc, argv); // QCoreApplication 객체 생성.
QByteArray encData = encoding(); // 인코딩 함수 호출.
decoding(encData); // 디코딩 함수 호출.
return a.exec(); // 이벤트 루프 시작
◇ QTextStream을 이용한 예제
#include <QCoreApplication> // QCoreApplication 헤더 포함, 콘솔 애플리케이션을 위한 기본 클래스.
#include <QIODevice> // QIODevice 헤더 포함, I/O 장치 관련 클래스.
#include <QTextStream> // QTextStream 헤더 포함, 텍스트 스트림 클래스.
#include <QDebug> // QDebug 헤더 포함, 디버그 메시지 출력을 위한 클래스.
QByteArray writeData(QByteArray _data) // 데이터 쓰기 함수.
{
QByteArray temp = _data; // 입력 데이터 복사.
QByteArray outData; // 출력 데이터 저장을 위한 QByteArray.
QTextStream outStream(&outData, QIODevice::WriteOnly); // 출력 데이터 스트림 생성.
for(qsizetype i = 0; i < temp.size(); i++) { // 입력 데이터 크기만큼 반복.
outStream << temp.at(i); // 각 바이트를 스트림에 추가.
}
outStream.flush(); // 스트림의 모든 데이터를 출력.
return outData; // 출력 데이터 반환.
}
void readData(QByteArray _data) // 데이터 읽기 함수.
{
QTextStream outStream(&_data, QIODevice::ReadOnly); // 입력 데이터 스트림 생성.
QByteArray inData; // 읽은 데이터를 저장할 QByteArray.
for(qsizetype i = 0; i < _data.size(); i++) // 입력 데이터 크기만큼 반복.
{
char data; // 읽을 문자 저장 변수.
outStream >> data; // 스트림에서 문자 읽기.
inData.append(data); // 읽은 문자를 inData에 추가.
}
qDebug("READ DATA : [%s]", inData.data()); // 읽은 데이터 출력.
}
int main(int argc, char *argv[]) // 메인 함수.
{
QCoreApplication a(argc, argv); // QCoreApplication 객체 생성.
QByteArray retData = writeData("Hello world."); // 데이터 쓰기 함수 호출.
qDebug() << "WRITE DATA size : " << retData.size() << " Bytes"; // 쓰기 데이터 크기 출력.
readData(retData); // 데이터 읽기 함수 호출.
return a.exec(); // 이벤트 루프 시작.
}
◆ CP15.QT Property
◇ 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;
};
- getName() 멤버 함수는 m_name 변수 값을 리턴하고 setName()멤버 함수는 m_name 값을 설정하는 함수이다.
◆ CP16.Model and View
- QT에서는 다음 그림과 같이 표와 같은 위젯에 데이터를 표시하기 위한 위젯으로 QListWidget, QTableWidget, QTreeWidget, QListView, QTableView, QTreeView, QColumnsView 등 다양한 클래스를 제공한다.

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

- 하지만 각 위젯 클래스들은 사용방법이 다소 차이가 있으므로 각각의 위젯 클래스들의 사용 방법을 익혀야 한다.
- QListView, QTableView, QTReeView 클래스와 같이 마지막에 View라는 단어를 사용하는위젯 클래스들은 각각의 멤버 함수를 사용해 데이터를 삽입/수정/삭제 하지않고 Model 클래스라는 매개체를 이용해 데이터를 삽입/수정/삭제할 수 있다.

- 위 그림처럼 이 클래스들은 위젯의 데이터를 삽입/수정/삭제하기 위해 각 클래스에서 제공하는 멤버 함수를 사용하지 않고 Model 클래스를 사용한다. 이 방식을 사용할 경우 QListView를 사용하든지 QTavleView를 사용하든지 동일한 Model을 사용할 수 있다는 장점을 가지고 있다.
- QT에서 제공하는 Model/View는 다음 그림에서 보는 것과 같이 Delegate를 이용해 데이터를 핸들링할 수 있다.

- 예를 들어 View 위젯 상에 표시된 데이터 항목 중 특정 항목을 마우스로 더블 클릭해 데이터를 수정하기 위해서 이벤트를 발생해야 하는데 Model/View에서는 이러한 이벤트를 처리 하기 위해 Delegate를 사용할 수 있다.
- Model 클래스는 데이터를 관리(삽입/수정/삭제)하기 위한 기능을 제공한다. 예를 들어 QSqlQueryModel 클래스를 이용하면 SQL문을 직접 쿼리할 수 있는 멤버 함수를 제공한다. 따라서 별도의 데이터베이스 쿼리로 가져온 데이터를 편집 후에 Model을 이용해도 되지만 QSqlQueryModel 클래스를 이용하면 직접 데이터를 삽입할 수 있다. 이외에도 다음 그림에서 보는 것과 같이 다양한 Model 클래스를 제공한다.

- Modle/View를 이용해 많은 양의 데이터를 표시할 때 다음 그림에서 보는 것과 같이 3가지 종류로 구분할 수 있다.

- 가장 좌측에 있는 데이터 형태라면 QListView클래스, 표와 같이 표시해야 한다면 QTableView, 트리 형태라면 QTReeBiew를 사용하는 것이 적합하다.
◇ QTreeView와 QListView 클래스를 이용한 예제

- 좌측은 QTReeView 클래스를 이용해 파일 시스템으로부터 가져온 데이터 표시이고 우측은 QListView 위젯은 현재 디렉토리에 존재한는 파일과 디렉토리를 표시하였다.
◇ QTableView클래스를 이용한 예제

- 표 형태의 데이터를 표시하는데 적합한 위젯이다.
◇ QTableViewWidget 예제

- 위에서 보는 것과 같이 QTavleWidget 헤더에 체크 박스가 있다. 이 체크박스를 변경하면 모든 라인이 컬럼의 값이 헤더에 있는 체크 박스의 값과 동일하게 변경이 가능하다. 그리고 각각의 체크 박스의 값을 사용자가 변경할 수 있다.
◆ CP19. Timer
- QTimer 클래스는 지정한 시간을 기준으로 반복해 호출할 수 있다.
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
- QTimer 클래스의 오브젝트를 선언하고 지정한 시간을 반복 호출하기 위해서 connect()함수를 이용해 Signal과 Slot 함수를 연결해야 한다. connect() 함수에서 첫 번째 인자는 QTimer 오브젝트를 명시한다. 두 번째는 Signal을 명시한다. timeout() Signal은 지정한 시간이 경과되면 4번째 인자에 지정한 update() Slot함수가 호출된다.
- 마지막 라인의 QTimer 클래스의 start() 멤버 함수는 첫 번째 인자로 지정한 시간만큼 경과되면 connect() 함수에서 명시한 Slot 함수가 반복해 호출된다. 첫 번째 인자의 단위는 밀리세컨드(millisecond)이다.
- QTimer 클래스는 stop() 멤버 함수를 이용해 타이머를 정지할 수 있다. 만약 타이머를 반복해서 실행하지 않고 단 한번만 호출되도록 하기 위해 singleShot() 멤버 함수를 사용하면 된다.
QTimer::singleShot(200, this, SLOT(updateCaption()));
- singleShot() 멤버 함수의 첫 번째 인자는 경과 되는 시간이며 단위는 밀리세컨드이다. 그리고 세 번째 인자는 첫 번째 인자의 시간이 경과된 후 호출될 Slot 함수를 지정하면 된다.
◆ CP20. Thread programming
- QT에서는 Thread를 지원하기 위해서 QThread 클래스를 제공한다. QThread 클래스를 이용해 Thread를 사용하는 경우 특정 상황에서 동기화가 필요한 경우가 있다. 동기화는 Thread 환경에서 다중 사용자가 참조하는 변수를 특정 사용자가 변수 값을 변경할 때 다른 사용자가 잘못된 값을 참조할 수 있으므로 Thread 로 동작되는 특정 함수의 소스코드의 시작 시점부터 종료 시점까지 다른 사용자가 변수를 참조하거나 변경하지 못하게 함으로써 변수가 변경되기 이전의 잘못된 값을 참조하지 못하도록 하기위한 기능을 제공한다.
- QT에서는 동기화를 지원하기위해 QMutex 클래스를 제공한다.
#include <QThread>
#include <QMutex>
#include <QDateTime>
class MyThread : public QThread
{
Q_OBJECT
void run() override {
while(!m_threadStop)
{
m_mutex.lock();
...
m_mutex.unlock();
...
sleep(1);
}
}
public:
MyThread(int n);
private:
bool m_threadStop;
QMutex m_mutex;
};
- run() 함수는 QThread에서 상속받은 Virtual 멤버 함수이며 Thread에서 동작 하고자 하는 기능을 이 함수에서 구현하면 된다. - run () 함수는 내부에서 구현 한 함수이기 때문에 클래스 외부에서 QTread에서 제공하는 start() 함수를 호출하면 자동으로 run() 함수가 호출된다.
MyThread *thread = new MyThread(this);
thread->start();
- 간혹 run()함수를 Public으로 선언하고 외부에서 run()함수를 외부에서 호출하는 경우가 있다. 이 경우 run()함수가 Thread에서와 같이 동작하지 않는다. 즉 Thread가 아닌 일반 함수와 같이 동작하므로 run() 함수를 바로 호출하는 경우가 없도록 주의해야 한다. run()함수에서 사용한 QMutex 클래스는 동기화를 위한 기능이다.
◇ Reentrancy 와 Thread-Safety
- Reentrancy 는 두개 이상의 Thread가 동작되고 있을 때 Thread가 수행되는 순서에 상관없이 하나의 Thread가 수행되고 난 후, 다음 Thread가 수행된 것처럼 수행되는 것을 의미한다.
- Thread-Safety는 두 개 이상의 쓰레드가 동작하는 상황에서 Static 또는 Heap 메모리 영역과 같이 공유되는 메모리 영역을 접근할 때 안정성을 보장하기 위해 Mutex와 같은 메커니즘을 사용하는 것을 의미한다.
◇ QtConcurrent 클래스를 이용한 Thread 구현
- QT에서는 QThread를사용해 Thread를 구현하는 방법보다 간단하게 Multi Thread를 구현할 수 있는 QtConcurrent 클래스를 제공한다. QtConcurrent 클래스는 Thread 클래스를 작성하지 않고 특정 함수를 Thread와 같이 동작 시킬 수 있다.
#include <QThread> // QThread 헤더 포함, 쓰레드 관련 클래스.
#include <QtConcurrent/QtConcurrent> // QtConcurrent 헤더 포함, 비동기 처리 관련 클래스.
#include <QtConcurrent/QtConcurrentRun> // QtConcurrentRun 헤더 포함, 쓰레드에서 함수 실행에 필요.
void hello(QString name) // hello 함수, 이름을 인자로 받음.
{
qDebug() << "Hello" << name << "from" << QThread::currentThread(); // 현재 쓰레드 정보 출력.
for(int i = 0; i < 10; i++) // 0부터 9까지 반복.
{
QThread::sleep(1); // 1초 대기.
qDebug("[%s] i = %d", name.toLocal8Bit().data(), i); // 현재 이름과 반복 인덱스 출력.
}
}
void world(QString name) // world 함수, 이름을 인자로 받음.
{
qDebug() << "World" << name << "from" << QThread::currentThread(); // 현재 쓰레드 정보 출력.
for(int i = 0; i < 3; i++) // 0부터 2까지 반복.
{
QThread::sleep(1); // 1초 대기.
qDebug("[%s] i = %d", name.toLocal8Bit().data(), i); // 현재 이름과 반복 인덱스 출력.
}
}
int main(int argc, char *argv[]) // 메인 함수.
{
QCoreApplication a(argc, argv); // QCoreApplication 객체 생성.
// hello 함수를 비동기적으로 실행, 각각의 이름 전달.
QFuture<void> f1 = QtConcurrent::run(hello, QString("Alice"));
QFuture<void> f2 = QtConcurrent::run(hello, QString("Bob"));
QFuture<void> f3 = QtConcurrent::run(hello, QString("Eddy"));
f1.waitForFinished(); // f1 작업이 끝날 때까지 대기.
f2.waitForFinished(); // f2 작업이 끝날 때까지 대기.
f3.waitForFinished(); // f3 작업이 끝날 때까지 대기.
return a.exec(); // 이벤트 루프 시작.
}
- 위의 코드에서 보는 것처럼 QtConcurrent 클래스의 run()멤버 함수의 첫번째 인자의 함수 명을 지정한다. 그러면 Thread와 같이 함수를 동작시킬수 있다.
'C++ QT' 카테고리의 다른 글
QT 6 챕터 9~11 (0) | 2024.10.10 |
---|---|
QT 6 프로그래밍 챕터 6~8 (0) | 2024.10.10 |
QT 챕터 5(2) (0) | 2024.10.08 |
QT 6 프로그래밍 챕터 5. Qt GUI Widgets (3) | 2024.10.07 |
QT 6 프로그래밍 챕터 3~4. CMake, QMake (2) | 2024.10.07 |
댓글