Open hyunsik-yoon opened 1 year ago
https://wiki.qt.io/Qt_for_Beginners 를 먼저 공부 했다. 새로운 개념은
https://doc.qt.io/qt-6/gettingstarted.html 안의 https://doc.qt.io/qt-6/create-your-first-applications.html 를 공부.
*.ui
파일이 생기고, 이걸 더블클릭하면 designer가 뜬다.lineEdit
라고 지으면 ui->lineEdit
하는 식으로 접근할 수 있다. (근데 슬프게도 auto completion이 안됨)Go to slots
하면 이 위젯이 emit 하는 signal을 받을 slot을 정의할 수 있다. *.qrc
파일을 추가할 수 있다. *.qrc
파일을 우클릭하면 resource file 들을 추가할 수 있게 되어 있다. *.qrc
파일은 사실 xml 파일로, 여기 resource file들의 리스트가 적혀 있다.*.qrc
파일을 CMakeLists.txt
에 추가해주어야 나중에 리소스 파일들도 패키지를 하든지 뭐 실행환경에 복사를 하든지 하는 것 같다.https://doc.qt.io/qt-6/qtwidgets-tutorials-notepad-example.html 를 공부
Notepad를 만드는 예제
QString
, QFile
, QFileDialog::getOpenFileName()
, QMessageBox::warning()
, QTextStream
// file open with file-open-dialog-box
QString fileName = QFileDialog::getOpenFileName(this, "Open the file");
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, "Warning", "Cannot open file: " + file.errorString());
return;
}
QTextStream in(&file); QString text = in.readAll(); ui->textEdit->setText(text); file.close();
- 기타 예제
```cpp
QString fileName = QFileDialog::getSaveFileName(this, "Save"); // save dialog
QFile file(file_path_qstring);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, "Warning", "Cannot save file: " + file.errorString());
return;
}
QTextStream out(&file); QString text = ui->textEdit->toPlainText(); out << text; file.close();
- font dialog
```cpp
bool fontSelected;
QFont font = QFontDialog::getFont(&fontSelected, this);
if (fontSelected)
ui->textEdit->setFont(font);
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
QString::number(i)
라는 것도 있다.SLOT(...)
을 하면 위 함수에 char*
로 전달되나보다.
for (int i = 0; i < NumDigitButtons; ++i)
digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked()));
// parents 설정 QGridLayout *mainLayout = new QGridLayout; for (int i = 1; i < NumDigitButtons; ++i) { int row = ((9 - i) / 3) + 2; int column = ((i - 1) % 3) + 1; mainLayout->addWidget(digitButtons[i], row, column); }
this->setLayout(mainLayout);
- `digitClicked()` slot 에서는 어떤 버튼이 눌려서 발생된 것인지 어떻게 아나?
- `qobject_cast<..>(..)`와 `sender()` 라는게 있다.
- `qobject_cast<..>(..)`는 변환 실패시 `nullptr` 리
```cpp
void Calculator::digitClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
int digitValue = clickedButton->text().toInt();
...
}
tr()
은 translate 의 약자. 왠만한 string은 tr()
로 감싸 쓰는게 좋다.
QPushButton *button = new QPushButton(tr("Hello"));
Button *divisionButton = createButton(tr("\303\267"), SLOT(multiplicativeOperatorClicked())); // "×" multiplication sign
Button *timesButton = createButton(tr("\303\227"), SLOT(multiplicativeOperatorClicked())); // "÷" division sign
Note: if you get an error such as "This file is not part of a project", You may need to switch to a different "kit" On some platforms Qt Creator will switch to a 32-bit version when you only have 64-bit installed.
How to change kits https://stackoverflow.com/questions/39092020/how-do-you-change-kits-in-qt-to-target-an-x86-machine
(from https://www.udemy.com/course/qt-core-for-beginners/learn/lecture/15196910#announcements)
TableView {
anchors.fill: parent
model: TableModel {
TableModelColumn { display: "Date" }
TableModelColumn { display: "Column 2" }
TableModelColumn { display: "Column 3" }
rows: [
{ "Date": "2023-05-29", "Column 2": "Data 2", "Column 3": "Data 3" },
{ "Date": "2023-05-30", "Column 2": "Data 2", "Column 3": "Data 3" },
{ "Date": "2023-05-31", "Column 2": "Data 2", "Column 3": "Data 3" },
]
}
}
how to add a row dynamically
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public: ...
public slots:
void addRow(const QDate& date, const QString& col2, const QString& col3);
};
MyModel
을 property로 QML로 expose 하고....
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &myModel);
...
qml 파일에서 아래처럼...
ApplicationWindow {
...
TableView {
anchors.fill: parent
model: myModel
}
Component.onCompleted: {
myModel.addRow(new Date(), "New Column 2", "New Column 3");
}
}
blood pressure 같은 데이터를 1초마다 계속 그래프로 업데이트하려면?
QML에서 1초마다 refresh
ApplicationWindow {
...
ChartView {
id: chartView
anchors.fill: parent
antialiasing: true
ValueAxis {
id: xAxis
min: 0
max: 60 // 60 seconds
}
ValueAxis {
id: yAxis
min: -1 // adjust these values according to your data range
max: 1
}
LineSeries {
id: lineSeries
axisX: xAxis
axisY: yAxis
}
}
Timer { interval: 1000 // 1 second running: true repeat: true onTriggered: { // get the y-value from the data source var y = dataSource.getNextValue();
// add the new data point at the current time
var x = (Date.now() / 1000) % 60; // x in seconds modulo 60
lineSeries.append(x, y);
// remove data points that are off the left edge of the chart
while (lineSeries.at(0).x < x - 60) {
lineSeries.remove(0);
}
}
} }
#ifdef Q_OS_LINUX
#elif defined(W_OS__WIN32)
#elif defined(W_OS__MACX)
system()
명령어처럼 process 생성QTimer::singleShot(5000, &func);
// 1번만 func를 실행timer.setInterval(5000);
connect(&timer, &QTTimer::timeout, &ThisClass::timeoutFunc);
timer.start();
timerFunc()
가 호출됨 (물론 timerFunc()는 public slots:
이어야)timer.stop();
하면 중지connect(&watcher, &QFileSystemWatcher::fileChaged, this &ThisClass::fileChanged);
class Foo : public QObject { ... }
// main 함수
Foo foo; // 생성자에서 타이머 시작. main thread 에서 동작 중
QThread qthread;
foo.moveToThread(qthread); // qthread 에서 foo가 동작함 (타이머가 동작)
qthread.start(); // thread를 명시적으로 시작
...
qthread.quit(); // 종
foo(parent)
등으로 parent를 주면 moveToThread
가 안됨
moveToThread(...)
하기 위해서는 parent가 없어야 하고, 즉 memory destruction을 책임져야 함run()
을 override 해야 한다.
connect
가능
class ThreadHandler : public QObject { ... public slots: void onStarted() { .. } ... };
// main
ThreadHandler handler;
QThread thread;
connect(thread, &QThread::started, handler, &ThreadHandler::onStarted,
QT::QueuedConnection);
// thread 관련 connection 시 QueuedConnection 을 써야 connection이 missing되지 않는다고
thread.start();
class Worker: public QObject, public Runnable {
...
public: void run() { .. } ...
};
// main QThreadPool pool = QThreadPool::globalInstance(); for (int i = 0; i < 10; i++) { Counter c = new Counter; c->setAutoDelete(true); // thead pool 이 free 해 준다고. pool->start(c); // c.run() 시작됨 } pool->waitForDone(); // C++ thread의 join() 비슷한 pool 단위의 명령
LockGuard
와 비슷함.QConditionVariable condition;
condition.wait(mutex);
condition.wakeAll();
qInfo() << qObj->metaObject()->className();
qInfo() << qObj->metaObject()->methodCount();
qInfo() << qObj->metaObject()->methodSignature();
qInfo() << qObj->metaObject()->propertyCount();
QMetaProperty mp = qInfo() << qObj->metaObject()->property(n);
qInfo() << mp.name() << ", " << mp.typeName
qObj->setProperty(name.toLatin1(), value); //value is of QVariant type
QFuture
int mul(int x) { return x * 10; }
// in main
QList
QFutureWatcher
qInfo() << future.results(); // {10, 20, 30, 40, 50} 가 출력된다
- QFutureWatcher slot
- 예를 들어 아래처럼 하면 watcher 가 실행 시작시, 중간중간 progress 값, 끝날 시 slot 이 호출됨.
```cpp
class ProgressBarUI : public QObject { ...
public slots:
void progressValueChanged(int progressValue);
void started();
void finished();
};
// in main
connect(&watcher, &QFutureWatcher<void>::started, progressBarUI, &ProgressBarUI::started);
connect(&watcher, &QFutureWatcher<void>::progressValueChanged, progressBarUI, &ProgressBarUI::progressValueChanged);
connect(&watcher, &QFutureWatcher<void>::finished, progressBarUI, &ProgressBarUI::finished);
future = QtConcurrent::mapped(list, &mul);
watcher.setFuture(future);
#include <QApplication>
#include <QPushButton>
#include <QThread>
#include <QDebug>
class WorkerThread : public QThread { Q_OBJECT public: WorkerThread(QPushButton *button) : button(button) {}
void run() override {
// WARNING: This is incorrect!
// Modifying GUI elements from a non-main thread is not safe.
button->setText("Hello from Worker Thread");
}
private: QPushButton *button; };
// in main QPushButton button("Hello from Main Thread"); button.show();
WorkerThread workerThread(&button); workerThread.start();
app.exec(); workerThread.wait();
// map reduce 할때의 map을 concurrent하게 해보는 예
int do_map(int val) { return val + 10; }
// main
QList<int> inputs = {1, 2, 3, 4};
QList<int> after_map = QtConcurrent::blockingMapped(inputs, &do_map);
// after_map 은 {11, 12, 13, 14) 가
QtTest
를 include 하고 `private slots: 에 test case 함수들을 만들면 됨
#include <QtTest/QtTest>
class TestQString: public QObject { Q_OBJECT private slots: void toUpper(); };
```cpp
void TestQString::toUpper()
{
QString str = "Hello";
QVERIFY(str.toUpper() == "HELLO");
}
// main
QTEST_MAIN(TestQString)
#include "testqstring.moc"
/myTestDirectory$ qmake -project "QT += testlib"
/myTestDirectory$ qmake
/myTestDirectory$ make
void TestGui::testGui()
{
QLineEdit lineEdit;
QTest::keyClicks(&lineEdit, "hello world");
QCOMPARE(lineEdit.text(), QString("hello world"));
}
QTest::keyClicks()
simulates clicking a sequence of keys on a widget. Optionally, a keyboard modifier can be specified as well as a delay (in milliseconds) of the test after each key click. QTest::keyClick()
, QTest::keyPress()
, QTest::keyRelease()
, QTest::mouseClick()
, QTest::mouseDClick()
, QTest::mouseMove()
, QTest::mousePress()
and QTest::mouseRelease()
등이 제공됨benchmark 도 test 의 일종으로 보아서인지 Test case 안에 작성하고 test 를 돌리면 소요시간을 보여줌
void TestBenchmark::simple()
{
QString str1 = QLatin1String("This is a test string");
QString str2 = QLatin1String("This is a test string");
QCOMPARE(str1.localeAwareCompare(str2), 0);
QBENCHMARK
{
// 벤치마크할 코드들을 적는다
str1.localeAwareCompare(str2);
}
}
TapHandler
Rectangle {
TapHandler { id: inputHandler
}
color: inputHandler.pressed ? "Red" : "Blue" // mousedown 시 반응
}
<item>
Column
Row
Grid
Grid {
rows: 3
colums: 2
spacing: 5 // 아래 rectangle 간의 간격
Rectangle { ... }
Rectangle { ... }
Rectangle { ... }
Rectangle { ... }
Rectangle { ... }
}
Flow
Rectangle {
x: 0
y: 0
width: 500
height: 200
Flow {
flow: Flow.TopToBottom // column 이랑 비슷한데, center부터 위치된다 그래서 수가 많으면 위 아래로 길어짐
clip: true // 그래서 잘라버릴 수도
Rectangle { ... }
Rectangle { ... }
Rectangle { ... }
Rectangle { ... }
...
}
}
PropertyAnimation
Window {
id: root
width: 1000
height: 1000
Rectangle {
x: 0
y: 0
width: 50
height: 50
PropertyAnimation {
id: animationRight
target: parent
property: "x"
to: root.width - parent.width
duration: 500 // animation 지속시간 (ms)
}
PropertyAnimation {
id: animationLeft
target: parent
property: "x"
to: 0
duration: 500
}
MouseArea {
anchors.fill: parent
onClicked: {
if (parent.x === 0) {
animationRight.start() // start animation
} else {
animationLeft.start()
}
}
}
}
}
마치 ppt 개체들 animation 정의하는 느낌. 이런 animation object 들로는 아래가 있
RotationAnimation
(회전하는 animation)ScaleAnimator
확대/축소SequentialAnimation
여러개의 animation을 child로 두고 순서대로 실행OpacityAnimator
명암 변화SmoothedAnimation
SequentialAnimation
Rectangle {
radius: 10 // rectangle이 더이상 아니고, 10짜리 circle으로 바뀜
// x, y 가 바뀌면 smooth 하게 곡선을 그리며 움직이는 에니메이션을 한다
Behavior on x { SmoothedAnimation { velocity: 100 }}
Behavior on y { SmoothedAnimation { velocity: 100 }}
}
참고: 키보드 처리
Window {
Rectangle {
id: rect1
x: 0
}
Keys.onRightPressed: rect1.x = rect1.x + 10
Keys.onLeftPressed: rect1.x = rect1.x - 10
}
Window {
property string username: "eric"
property string pw: "1234"
property string status: "Failed"
}
Window {
Popup {
id: pop
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Label {
text: ...
}
}
Button {
OnClicked : { pop.open() }
}
}
// main() in main.cpp
Foo foo;
auto qml_ctx = engine.rootContext()->setContextProperty("foo_prop", &foo);
// Foo.cpp
class Foo {
public:
void hello();
public slots:
void hello_world();
};
Button {
onClicked: {
foo_prop.hello_world(); // works
foo_prop.hello(); // runtime error, "cannot find a function". slot 함수여야만 qml에서 호출 가능
}
}
// Note: main()은 위처럼 그대로 한다.
class Foo { signals: void notice(QVariant data);
public slots: void say(QString msg) { emit notice(QVariant(msg)); } };
```qml
Window {
Connections {
target: foo_prop
onNotice: {
fooLabel.text = data; // signals: void notice(QVariant data)
}
}
Label {
id: fooLabel
}
}
class Foo : public: QObject {
..
signals:
void status(QVariant data);
public slots:
void work(QVarient work) {
qInfo() << "work called";
emit status(QVariant("Good"));
}
};
// main()
...
qmlRegisterType<Foo>("com.company.foo", 1, 0, "Foo"); // this type can be created inside QML
...
import com.company.fooi 1.0
Window {
Foo {
id: foo1
// status() slot 이 있으므로
onStatus : {
fooLabel.text = data; // data는 status(QVarient data) 의 바로 그 data
}
}
Label {
id: fooLabel
}
Button {
onClick: {
foo.work(fooLabel.text + ' good')
}
}
}
정리하자