Files
Obsidian-Main/-20.01. Programming/QT/Qt.md

734 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Prepare
### Windows 10 SDK
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
**Debugging Tools for Windows**就可以了。
![[chrome_20210309_110223_934x685.png]]
然後在QtCreator裡面設置Debugger。
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
### Visual Studio 2017
- ![[Pasted image 20210312144218.png]]
- ![[Pasted image 20210312144242.png]]
- ![[Pasted image 20210312144405.png]]
- ![[Pasted image 20210312144523.png]]
## PRO file 語法
### 導入lib
```
QT += <lib> <lib2>
```
### 導入include path
```
INCLUDEPATH += <INCLUDE_PATH>
```
若使用相對路徑必須相對於makefile生成的路徑。
也可以使用`$$PWD`來生成絕對路徑,例:
```
INCLUDEPATH += $$PWD/../../include
```
> 可以用`message()`來印出變數:`message($$PWD)`
### 導入lib
`-L`指定library path`-l`指定libraray name。例
```
LIBS += -L"../../lib" -lopencv_world320
```
### 指定執行目錄
```
DESTDIR += ../../bin
```
### 指定執行檔檔名
```
TARGET = <NEW_FILENAME>
```
- Import QT pro file into Visual Studio.
- `qmake -tp vc <QT_PROJECT_FILE>`
- 使用VS工具import。
## 基本類別class
### QIODevice
An abstract class that wraps input and output for most devices
### QDataStream
依序寫入的資料也可以依序被讀出來。資料會被編碼過所以用Text editor會看到亂碼。
### QTextStream
可以依序寫入字串也可以依序被讀出來因為寫入的是字串所以用Text editor就可以看到資料。
### QLockFile
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
```cpp
QLockFile lock(fileName() +".lock");
lock.setStaleLockTime(30000);
if(lock.tryLock()) {
...
lock.unlock();
} else {
qInfo() << "Could not lock the file!";
qint64 pid;
QString host;
QString application;
if(lock.getLockInfo(&pid,&host,&application)) {
qInfo() << "The file is locked by:";
qInfo() << "Pid: " << pid;
qInfo() << "Host: " << host;
qInfo() << "Application: " << application;
} else {
qInfo() << "File is locked, but we can't get the info!";
}
}
```
### QtMessageHandler
可以用來重新導向或攔截`qInfo()``qDebug()``qWarning()``qCritical()``qFatal()`這些function
```cpp
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QString txt;
switch (type) {
case QtInfoMsg:
txt = QString("Info: %1 in %2").arg(msg);
break;
case QtDebugMsg:
txt = QString("Debug: %1").arg(msg);
break;
case QtWarningMsg:
txt = QString("Warning: %1").arg(msg);
break;
case QtCriticalMsg:
txt = QString("Critical: %1").arg(msg);
break;
case QtFatalMsg:
txt = QString("Fatal: %1").arg(msg);
break;
}
...
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInstallMessageHandler(lhandler);
qInfo() << "This is a info message";
qDebug() << "This is a debug message";
qWarning() << "This is a warning message";
qCritical() << "This is a critical message";
qFatal("THIS IS SPARTA!!!");
return a.exec();
}
```
也可以把這機制封裝在class裡面
```cpp
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QDateTime>
#include <QDir>
#include <iostream>
#include <QTextStream>
class logger : public QObject
{
Q_OBJECT
public:
explicit logger(QObject *parent = nullptr);
static bool logging;
static QString filename;
static void attach();
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
signals:
public slots:
};
#endif // LOGGER_H
```
```cpp
// logger.cpp
#include "logger.h"
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
bool logger::logging = false;
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
logger::logger(QObject *parent) : QObject(parent)
{
}
void logger::attach()
{
logger::logging = true;
qInstallMessageHandler(logger::handler);
}
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
// Here, write log message to file
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
```
### Logging category
Logging category可以用來把輸出的log前面加上一個字串以便分類。
```cpp
#include <QCoreApplication>
#include <QDebug>
#include <QLoggingCategory>
//Declare a logging category
Q_DECLARE_LOGGING_CATEGORY(network);
Q_LOGGING_CATEGORY(network, "network");
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInfo() << "test"; // stdout: "test"
qInfo(network) << "test"; // stdout: "network: test"
//turn it off
QLoggingCategory::setFilterRules("network.debug=false");
//Will not log
qDebug(network) << This message will not shown;
if(!network().isDebugEnabled()) {
QLoggingCategory::setFilterRules("network.debug=true");
qDebug(network) << "We turned it back on";
}
qDebug(network) << "You see this because you turn the message on";
return a.exec();
}
```
## QThread
1. 繼承自QThread。
2. 重載`run()`
3. 呼叫`start()`來開始thread。
## QWidget
### 設定signal與對應的slot
1. 在class header裡面加入`Q_OBJECT`
2. 在constructor裡呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`
### Postion
1. `QRect geometry();` 取得x, y, width, height
2. `setGeometry();` 設定x, y, width, height
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
4. `move(x, y);` 只設定x, y
5. `resize(width, height);` 只設定width, height
### Window state
1. `setWindowState(Qt::WindowMaximized);`
1. `Qt::WindowMaximized` -> `showMaximized()`
2. `Qt::WindowMinimized` -> `showMinimized()`
3. `Qt::WindowNoState` -> `showNormal()`
4. `Qt::WindowFullScreen` -> `showFullScreen()`
### Window Style
1. `CustomizeWindowHint()`
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
### Layout
- QVBoxLayout
```cpp
QVBoxLayout* pVlayout = new QVBoxLayout();
widget->setLayout(pVlayout);
// Add button to layout
QPushButton* button = new QPushButton("My Button");
pVlayout->addWidget(button);
```
- `setContentsMargin()`設定元與layout的距離
- `setSpacing()`:設定元件與元件之間的距離
- QFormLayout
- Iterate all items
```cpp
QFormLayout* layout = (QFormLayout*)this->layout();
for (int i = 0; i < layout->rowCount(); i++) {
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
QWidget* widget = (QWidget*)item->widget();
if (widget) {
QString className = widget->metaObject()->className();
if (className == "QLineEdit") {
QLineEdit* edit = (QLineEdit*)widget;
edit->setText("Text changed");
}
} else {
continue;
}
}
```
#### QSizePolicly
- QSizePolicly::GrowFlag必要時可以超過
- QSizePolicly::ExpandFlag放到最大
- QSizePolicly::ShrinkFlag必要時可以小於建議尺寸
- QSizePolicly::IgnoreFlag忽略
## QLabel
- `QLabel.setTextIneractionFlags()`
## QSlider
### Style
#### groove
```
QSlider::groove {
border: 1px solid #999999;
height: 28px;
background: rgba(155, 155, 155, 200);
border-radius: 10px;
}
```
#### handle
```
QSlider::handle {
border: 1px solid #FF0000;
width: 20px;
margin: -10px 0;
background: rgba(255, 0, 0, 200);
border-radius: 10px;
}
```
#### add-page
```
QSlider::add-page {
background: rgba(255, 0, 0, 200);
}
```
#### sub-page
```
QSlider::add-page {
background: rgba(0, 255, 0, 200);
}
```
![[Pasted image 20210318115725.png]]
## QListWidget
- Insert a QLineEdit
```cpp
QLineEdit* lineEdit = new QLineEdit("A line edit here");
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
```
- Insert a QSpinBox
```
QSpinBox* spinBox = new QSpinBox();
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
```
### Stylesheet example
```
QTableView {
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
}
```
```
QTableView QTableCornerButton::section {
background: red;
border: 2px outset red;
}
```
```
QTableView::indicator:unchecked {
background-color: #d7d6d5
}
```
## QTableWidget
- setColumnCount()
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
- setColumnWidth(column_index, column_width)
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
- Bind Header's event
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
- Style
```
QHeaderView::section:checked{ /* Focus */
background-color: rgba(55, 55, 55,255 );
}
QHeaderView::section:hover{ /* 滑鼠懸停 */
background-color: rgba(85, 10, 10,255 );
}
QTableWidget{
background-color: rgb(27, 122, 239);
alternate-background-color: rgb(26, 81, 232);
color:white;
}
```
## QTreeWidget
- Data create & insert
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
- `addTopLevelItem(QTreeWidgetItem*)`
- `new QTreeWidgetItem(QTreeWidget*)`
- Insert widget
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
- Iterate items
- `QTreeWidgetItem* topLevelItem(int index) const`
- `int topLevelItemCount() const`
- Get item
- `QList<QTreeItemWidget*> selectedItems() const`
- Style
```cpp
/* 有旁節點,沒有與其他元素相鄰 */
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(vline.png) 0;
}
/* 有旁節點,與其他元素相鄰 */
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(branch-more.png) 0;
}
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(branch-end.png) 0;
}
/* 收合狀態,有子節點,沒有旁節點 */
QTreeView::branch:closed:has-children:!has-siblings,
/* 收合狀態,有子節點,有旁節點 */
QTreeView::branch:closed:has-children:has-siblings {
border-image: none; image: url(branch-closed.png);
}
/* 打開狀態,有子節點,沒有旁節點 */
QTreeView::branch:open:has-children:!has-siblings,
/* 打開狀態,有子節點,有旁節點 */
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(branch-open.png);
}
```
## QStatusBar
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
## QToolBar
- `addAction(QIcon, QString)`
- `addAction(QString)`
- `setToolButtonStyle(int)`
## QDockWidget
- https://blog.csdn.net/czyt1988/article/details/51209619
## QJson
- [C++(Qt5)入門範例各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
## QFile
Read UTF-8 content from file.
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
## String
### UTF-8
1. 對於n bytes的字元第一個byte的前n個bits是1第n+1個bit是0第2個byte以後都以`10`開頭。
1. 例:
2. 1 byte character: 0x0XXXXXXX。
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
2. 例:
1. 「A」是一個1 byte字元編碼為`0x01000001`。
2. 「Á」是一個2 bytes字元編碼為`0x11000011 0x10000001`。
3. 「好」是一個3 bytes字元編碼為`0x11100101 0x10100101 0x10111101`。
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
2. BOM
1. Big endian: `FE FF`
2. Little endian: `FF FE`
### QString
- 16 bits QChar(unsigned short), UNIDODE 4.0
- `==`: compare string
- `isNull()`: If has a value
- `isEmpty()`: If contained value == ""
- `QString::number()`: 接收一個數字,把數字轉為字串。
- `QString::toInt()`、`QString::toDouble()`把字串轉為Int或Double。
- 讀取UTF-8的字串
```
codec = QTextCodec::codecFromName("UTF-8");
QTextCodec::setCodecForLocale(codec);
```
然後用`QString::fromLocal8Bit()`來讀取字串該字串就會被當作UTF-8來處理。
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
- 在Visual Studio中強制文件用UTF-8來存檔`#pragma execution_character_set("UTF-8")`
- 在Visual Studio中預設是以CP950存檔所以字串**都不是**unicode而是**multi-character**所以要從Windows讀進來的字串一律需要使用`QString::fromLocal8Bit("")`而一旦轉為QString之後就是UTF-8了若需要再輸出回Windows則需要`QString.toLocal8Bit().toStdString()`。
- 例:
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值包含windows和linux](https://blog.csdn.net/iamdbl/article/details/78343642)
#### Search
- `QString.indexOf()`
- `QString.lastIndexOf()`
#### Trim
- `QString.chop(5)`去掉結尾的5個字元inplace轉換沒有回傳新字串。
- `QString.left(5)`取最左邊5個字元
- `QString.right(5)`取最右邊5個字元
#### Replace
- `QString.replace("original", "new")`:將"original"替換為"new"。
- 也支援regular expression
#### Split
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
#### Regular Expression
- `.`匹配任意1個字元
- `+`此類型的匹配字元可以1個以上
- `*`:匹配任意多個字元
- `^`:表示開始位置
- `$`:表示結束位置
- `?`:表示前一個匹配條件不一定要成立
- `[]`:用括號來限制要匹配的字元
## Event
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
### MouseEvent
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
- Get click
```
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
{
}
```
### MouseCursor
- 設定cursor click的位置`QCursor cursor = QCursor(pixmap, -1, -1)`
- (-1, -1) 表示用cursor中心點為click點
- ( 0, 0) 表示用cursor左上角為click點
- 更換cursor`setCursor(Qt::ArrowCursor)`
### ResizeEvent
- `void resizeEvent(QResizeEvent*)`
## QOpenGLWidget
- `void paintGL()`
- `void initialGL()`
- `void resizeGL(int w, int h)`
## QGLShaderProgram
- `addShaderFromSourceCode`
- `bindAttributeLocation`:設定傳入的變數
- `uniformLocation`:取得變數
## GL概念
### Vertex
#### Coordinate
```
float *vertexData = new float[12] {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
};
```
### Texture
#### Coordinate
材質座標永遠都在第一象限
```
float* textureVertexData = new float[8] {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
```
#### 建立材質
- `glGenTextures(1, t);`
- `glBindTexture(GL_TEXTURE_2D, *t);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
#### 寫入材質
- `glActiveTexture(GL_TEXTURE0);`
- `glBindTexture(GL_TEXTURE_2D, idY);`
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
```
glTexImage2D(
GL_TEXTURE_2D,
0, // 細節預設0
GL_LUMINANCE, // GPU內部格式
pixelW,
pixelH,
0,
GL_LUMINANCE, // Pixel format
GL_UNSIGNED_BYTE, // Data format
plane[0]);
```
- `glTexSubImage2D()`修改texture
- `glUniform1i(textureUniformY, 0);`
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
## GLSL
### 內建變數
- `gl_FragColor`
### 3種變數類型
- `varying`vertex與shader共用vertex運算完之後傳給shader使用。
- `attribute`vertex使用由`bindAttributeLocation`傳入
- `uniform`由user傳入的資料由`uniformLocation`得到pointer address再由`glUniform1i(textureUniformY, 0)`來設定。
### Vertex Shader
```
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main() {
gl_position = vertexIn;
textureOut = textureIn;
}
```
### Fragment Shader
```
varying vec2 textureOut;
uniform sampler2D texY;
uniform sampler2D texU;
uniform sampler2D texV;
void main() {
vec3 yuv;
vec3 rgb;
yuv.x = textue2D(texY, textureOut).r;
yuv.y = textue2D(texU, textureOut).r - 0.5;
yuv.z = textue2D(texV, textureOut).r - 0.5;
rgb = mat3(
1, 1, 1,
0, -0.39465, 2.03211,
1.13983, -0.5806, 0) * yuv;
gl_FragColor = vec4(rgb, 1);
}
```
### APIs
畫出矩形與材質
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
# 檔案操作
## 開啟檔案對話框
```c++
QString selectedFilepath = QFileDialog::getOpenFileName(
this,
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
tr("."), // 檔案選擇對話框一開始的路徑
"(*.json);;All files(*.*)"); // 可選擇的副檔名
```
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
## 存檔對話框
```c++
QString fileName = QFileDialog::getSaveFileName(
this,
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
```
# MISC
## 教學文
- [Qt教程Qt5编程入门教程非常详细](http://c.biancheng.net/qt/)