轉自:Qt開發經驗: 自己總結的這十多年來做Qt開發以來的經驗,以及Qt相關武林秘籍電子書,會一直持續更新增加,歡迎各位留言增加內容或者提出建議,謝謝!
一、開發經驗
01:001-010
當編譯中發現大量錯誤的時候,應該從第一個看起,一個一個的解決,不要急著去看下一個錯誤,往往后面的錯誤都是由于前面的錯誤引起的,第一個解決后很可能都解決了。 定時器是個好東西,學會好使用它,有時候用QTimer::singleShot單次定時器和QMetaObject::invokeMethod可以解決意想不到的問題。比如在窗體初始化的時候加載一個耗時的操作,很容易卡主界面的顯示,要在加載完以后才會顯示界面,這就導致了體驗很卡不友好的感覺,此時你可以將耗時的加載(有時候這些加載又必須在主線程,比如用QStackWidget堆棧窗體加載一些子窗體),延時或者異步進行加載,這樣就會在界面顯示后去執行,而不是卡主主界面。
//異步執行load函數
QMetaObject::invokeMethod(this, "load", Qt::QueuedConnection);
//延時10毫秒執行load函數
QTimer::singleShot(10, this, SLOT(load()));
默認QtCreator是單線程編譯,可能設計之初考慮到盡量不過多占用系統資源,而現在的電腦都是多核心的,默認msvc編譯器是多線程編譯的不需要手動設置,而對于其他編譯器,需要手動設置才行。
方法一:在每個項目的構建設置中(可以勾選一個shadow build的頁面地方)的build步驟,make arguments增加一行 -j16 即可,此設置會保存在pro.user文件中,一旦刪除就需要重新設置,不建議此方法; 方法二:在構建套件的環境中增加,工具->選項->構建套件(kits)->選中一個構建套件->environment->右側change按鈕->打開的輸入框中填入 MAKEFLAGS=-j4 , 這樣就可以不用每次設置多線程編譯,只要是應用該構件套件的項目都會加上這個編譯參數; 注意:-j后面接的是電腦的核心數,寫多了不會有效果,要自己看下電腦的參數,或者填個-j4就行,畢竟現在電腦4核心應該是最基本的; 大概從2019年開始的新版本的QtCreator默認已經會根據電腦的核心自動設置多線程編譯,比如識別到你的電腦是16核心的就會默認設置-j16參數進行編譯;
如果你想順利用QtCreator部署安卓程序,首先你要在AndroidStudio 里面配置成功,把坑全部趟平。
很多時候找到Qt對應封裝的方法后,記得多看看該函數的重載,多個參數的,你會發現不一樣的世界,有時候會恍然大悟,原來Qt已經幫我們封裝好了,比如QString、QColor的重載參數極其豐富。
可以在pro文件中寫上標記版本號+ico圖標(Qt5才支持),其實在windows上就是qmake的時候會自動將此信息轉換成rc文件。
VERSION = 2025.10.01
RC_ICONS = main.ico
管理員運行程序,限定在MSVC編譯器。
QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" #以管理員運行
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" #VS2013 在XP運行
運行文件附帶調試輸出窗口,這個非常有用,很多時候當我們發布程序階段,我們會遇到程序雙擊無法運行也不報錯提示(開發機器上一切正常),都不知道發生了什么,甚至任務管理器可以看到運行了但是沒有界面彈出來,此時就需要在項目的pro文件中加上這個,帶界面的程序也會自動彈出調試窗口打印輸出信息,方便找問題,一般沒法正常運行的程序都會打印一些提示信息缺啥之類的。
TEMPLATE = app
MOC_DIR = temp/moc
RCC_DIR = temp/rcc
UI_DIR = temp/ui
OBJECTS_DIR = temp/obj
#就是下面這行用來設置運行文件附帶調試輸出窗口
CONFIG += console
繪制平鋪背景QPainter::drawTiledPixmap,繪制圓角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();
移除舊的樣式
//移除原有樣式
style()->unpolish(ui->btn);
//必須要有下面這行不然還是不會卸載
ui->btn->setStyleSheet("");
//重新設置新的該控件的樣式。
style()->polish(ui->btn);
02:011-020
獲取類的屬性
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {QMetaProperty metaproperty = metaobject->property(i);const char *name = metaproperty.name();QVariant value = object->property(name);qDebug() << name << value;
}
Qt內置圖標封裝在QStyle中,大概七十多個圖標,可以直接拿來用。
SP_TitleBarMenuButton,
SP_TitleBarMinButton,
SP_TitleBarMaxButton,
SP_TitleBarCloseButton,
SP_MessageBoxInformation,
SP_MessageBoxWarning,
SP_MessageBoxCritical,
SP_MessageBoxQuestion,
...
//下面這樣取出來使用就行
QPixmap pixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMenuButton);
ui->label->setPixmap(pixmap);
根據操作系統位數判斷加載
win32 {contains(DEFINES, WIN64) {DESTDIR = $$PWD/../bin64} else { DESTDIR = $$PWD/../bin32}
}
Qt5增強了很多安全性驗證,如果出現setGeometry: Unable to set geometry,請將該控件的可見移到加入布局之后。
可以將控件A添加到布局,然后控件B設置該布局,這種靈活性提高了控件的組合度,比如可以在文本框左側右側增加一個搜索按鈕,按鈕設置圖標即可。
QPushButton *btn = new QPushButton;
btn->resize(30, ui->lineEdit->height());
QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);
layout->setMargin(0);
layout->addStretch();
layout->addWidget(btn);
對QLCDNumber控件設置樣式,需要將QLCDNumber的segmentstyle設置為flat,不然你會發現沒效果。
巧妙的使用 findChildren 可以查找該控件下的所有子控件。 findChild 為查找單個。
//查找指定類名objectName的控件
QList<QWidget *> widgets = fatherWidget.findChildren<QWidget *>("widgetname");
//查找所有QPushButton
QList<QPushButton *> allPButtons = fatherWidget.findChildren<QPushButton *>();
//查找一級子控件,不然會一直遍歷所有子控件
QList<QPushButton *> childButtons = fatherWidget.findChildren<QPushButton *>(QString(), Qt::FindDirectChildrenOnly);
巧妙的使用inherits判斷是否屬于某種類。
QTimer *timer = new QTimer; // QTimer inherits QObject
timer->inherits("QTimer"); // returns true
timer->inherits("QObject"); // returns true
timer->inherits("QAbstractButton"); // returns false
使用弱屬性機制,可以存儲臨時的值用于傳遞判斷。可以通過widget->dynamicPropertyNames()列出所有弱屬性名稱,然后通過widget->property("name")取出對應的弱屬性的值。
在開發時, 無論是出于維護的便捷性, 還是節省內存資源的考慮, 都應該有一個 qss 文件來存放所有的樣式表, 而不應該將 setStyleSheet 寫的到處都是。如果是初學階段或者測試階段可以直接UI上右鍵設置樣式表,正式項目還是建議統一到一個qss樣式表文件比較好,統一管理。
03:021-030
如果出現Z-order assignment: is not a valid widget.錯誤提示,用記事本打開對應的ui文件,找到為空的地方,刪除即可。
善于利用QComboBox的addItem的第二個參數設置用戶數據,可以實現很多效果,使用itemData取出來。
如果用了webengine模塊,發布程序的時候帶上QtWebEngineProcess.exe+translations文件夾+resources文件夾。
默認Qt是一個窗體一個句柄,如果要讓每個控件都擁有獨立的句柄,設置下 a.setAttribute(Qt::AA_NativeWindows);
Qt+Android防止程序被關閉。
#if defined(Q_OS_ANDROID)
QAndroidService a(argc, argv);
return a.exec()
#else
QApplication a(argc, argv);
return a.exec();
#endif
可以對整體的指示器設置樣式,而不需要單獨對每個控件的指示器設置,
*::down-arrow{}
*::menu-indicator{}
*::up-arrow:disabled{}
*::up-arrow:off{}
可以指定位置設置背景圖片。
QMainWindow > .QWidget {background-color: gainsboro;background-image: url(:/images/pagefold.png);background-position: top right;background-repeat: no-repeat
}
嵌入式linux運行Qt程序
//Qt4寫法
./HelloQt -qws &//Qt5寫法 xcb 可以改成 eglfs vnc wayland 等,有哪個就用哪個挨個測試
./HelloQt --platform xcb
./HelloQt --platform wayland
Qtcreator軟件的配置文件存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有時候如果發現出問題了,將這個文件夾刪除后打開creator自動重新生成即可。
QMediaPlayer是個殼(也可以叫框架),依賴本地解碼器,視頻這塊默認基本上就播放個MP4甚至連MP4都不能播放,如果要支持其他格式需要下載k-lite或者LAV Filters安裝即可(k-lite或者LAV Filters是指windows上的,其他系統上自行搜索,貌似嵌入式linux上依賴GStreamer,并未完整驗證)。如果需要做功能強勁的播放器,初學者建議用vlc、mpv,終極萬能大法用ffmpeg(解碼出來的視頻可以用QOpenGLWidget走GPU繪制或者轉成QImage繪制,音頻數據可以用QAudioOutput播放)。
04:031-040
判斷編譯器類型、編譯器版本、操作系統。
//GCC編譯器
#ifdef __GNUC__
#if __GNUC__ >= 3 // GCC3.0 以上//MSVC編譯器
#ifdef _MSC_VER
#if _MSC_VER >=1000 // VC++4.0 以上
#if _MSC_VER >=1100 // VC++5.0 以上
#if _MSC_VER >=1200 // VC++6.0 以上
#if _MSC_VER >=1300 // VC2003 以上
#if _MSC_VER >=1400 // VC2005 以上
#if _MSC_VER >=1500 // VC2008 以上
#if _MSC_VER >=1600 // VC2010 以上
#if _MSC_VER >=1700 // VC2012 以上
#if _MSC_VER >=1800 // VC2013 以上
#if _MSC_VER >=1900 // VC2015 以上//Borland C++
#ifdef __BORLANDC__//Cygwin
#ifdef __CYGWIN__
#ifdef __CYGWIN32__//mingw
#ifdef __MINGW32__//windows
#ifdef _WIN32 //32bit
#ifdef _WIN64 //64bit
#ifdef _WINDOWS //圖形界面程序
#ifdef _CONSOLE //控制臺程序//Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定義了
#if (WINVER >= 0x030a) // Windows 3.1以上
#if (WINVER >= 0x0400) // Windows 95/NT4.0以上
#if (WINVER >= 0x0410) // Windows 98以上
#if (WINVER >= 0x0500) // Windows Me/2000以上
#if (WINVER >= 0x0501) // Windows XP以上
#if (WINVER >= 0x0600) // Windows Vista以上//_WIN32_WINNT 內核版本
#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上
#if (_WIN32_WINNT >= 0x0501) // Windows XP以上
#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上
在pro中判斷Qt版本及構建套件位數
#打印版本信息
message(qt version: $$QT_VERSION)
#判斷當前qt版本號
QT_VERSION = $$[QT_VERSION]
QT_VERSION = $$split(QT_VERSION, ".")
QT_VER_MAJ = $$member(QT_VERSION, 0)
QT_VER_MIN = $$member(QT_VERSION, 1)
#下面是表示 Qt5.5及以上版本
greaterThan(QT_VER_MAJ, 4) {
greaterThan(QT_VER_MIN, 4) {
#自己根據需要做一些處理
}}#QT_ARCH是Qt5新增的,在Qt4上沒效果
#打印當前Qt構建套件的信息
message($$QT_ARCH)
#表示arm平臺構建套件
contains(QT_ARCH, arm) {}
#表示32位的構建套件
contains(QT_ARCH, i386) {}
#表示64位的構建套件
contains(QT_ARCH, x86_64) {}#其實Qt內置了主版本號和子版本號變量
#判斷當前qt版本號
message($$QT_ARCH : $$QT_VERSION -> $$QT_MAJOR_VERSION . $$QT_MINOR_VERSION)#下面的含義是如果版本 < 4.8
lessThan(QT_MAJOR_VERSION, 5) {
lessThan(QT_MINOR_VERSION, 8) {
#這里放要做的處理
}}#下面的含義是如果版本 >= 5.5
greaterThan(QT_MAJOR_VERSION, 4) {
greaterThan(QT_MINOR_VERSION, 4) {
#這里放要做的處理
}}//代碼中判斷版本不要太簡單
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
//這里放要做的處理
#endif
Qt最小化后恢復界面假死凍結,加上代碼
void showEvent(QShowEvent *e)
{setAttribute(Qt::WA_Mapped);QWidget::showEvent(e);
}
獲取標題欄高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight點進去你會發現新大陸。
設置高分屏屬性以便支持2K4K等高分辨率,尤其是手機app。必須寫在main函數的QApplication a(argc, argv);的前面。
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endifQApplication a(argc, argv);
如果運行程序出現 Fault tolerant heap shim applied to current process. This is usually due to previous crashes. 錯誤。
第一步:輸入命令 regedit 打開注冊表; 第二步:找到節點 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers\; 第三步:選中Layers鍵值,從右側列表中刪除自己的那個程序路徑即可。
Qt內置了QFormLayout表單布局用于自動生成標簽+輸入框的組合的表單界面,設置布局用的很少,一般用的最多的是橫向布局、垂直布局、表格布局。
qml播放視頻在linux需要安裝 sudo apt-get install libpulse-dev。
可以直接繼承QSqlQueryModel實現自定義的QueryModel,比如某一列字體顏色,占位符,其他樣式等,重寫QVariant CustomSqlModel::data(const QModelIndex &index, int role) const。
Qt5以后提供了類QScroller直接將控件滾動。
//禁用橫向滾動條
ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//禁用縱向滾動條
ui->listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//設置橫向按照像素值為單位滾動
ui->listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel);
//設置縱向按照像素值為單位滾動
ui->listWidget->setVerticalScrollMode(QListWidget::ScrollPerPixel);
//設置滾動對象以及滾動方式為鼠標左鍵拉動滾動
QScroller::grabGesture(ui->listWidget, QScroller::LeftMouseButtonGesture);
//還有個QScrollerProperties可以設置滾動的一些參數
05:041-050
如果使用sqlite數據庫不想產生數據庫文件,可以創建內存數據庫。
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
清空數據表并重置自增ID,sql = truncate table table_name。
Qtchart模塊從Qt5.7開始自帶,最低編譯要求Qt5.4。在安裝的時候記得勾選,默認不勾選。使用該模塊需要引入命名空間。
#include <QChartView>
QT_CHARTS_USE_NAMESPACE
class CustomChart : public QChartView
QPushButton左對齊文字,需要設置樣式表QPushButton{text-align:left;}
QLabel有三種設置文本的方法,掌握好Qt的屬性系統,舉一反三,可以做出很多效果。
//常規辦法
ui->label->setText("hello");
//取巧辦法
ui->label->setProperty("text", "hello");
//屬性大法
ui->label->setStyleSheet("qproperty-text:hello;");
巧妙的用QEventLoop開啟事件循環,可以使得很多同步獲取返回結果而不阻塞界面。查看源碼得知,原來QEventLoop內部新建了線程執行。
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
多種預定義變量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目錄 CONFIG -= debug_and_release。
新版的Qtcreator增強了語法檢查,會彈出很多警告提示等,可以在插件列表中關閉clang打頭的幾個即可,Help》About Plugins。也可以設置代碼檢查級別,Tools》Options 》C++ 》Code Model。
QSqlTableModel的rowCount方法,默認最大返回256,如果超過256,可以將表格拉到底部,會自動加載剩余的,每次最大加載256條數據,如果需要打印或者導出數據,記得最好采用sql語句去查詢,而不是使用QSqlTableModel的rowCount方法。不然永遠最大只會導出256條數據。 如果數據量很小,也可以采用如下方法:
//主動加載所有數據,不然獲取到的行數<=256
while(model->canFetchMore()) {model->fetchMore();
}
如果需要指定無邊框窗體,但是又需要保留操作系統的邊框特性,可以自由拉伸邊框,可以使用 setWindowFlags(Qt::CustomizeWindowHint);
06:051-060
在某些http post數據的時候,如果采用的是&字符串連接的數據發送,中文解析亂碼的話,需要將中文進行URL轉碼。
QString content = "測試中文";
QString note = content.toUtf8().toPercentEncoding();
Qt默認不支持大資源文件,比如添加了字體文件,需要pro文件開啟。 CONFIG += resources_big
Qt中繼承QWidget之后,樣式表不起作用,解決辦法有三個。強烈推薦方法一。
方法一:設置屬性 this->setAttribute(Qt::WA_StyledBackground, true); 方法二:改成繼承QFrame,因為QFrame自帶paintEvent函數已做了實現,在使用樣式表時會進行解析和繪制。 方法三:重新實現QWidget的paintEvent函數時,使用QStylePainter繪制。
void Widget::paintEvent(QPaintEvent *)
{QStyleOption option;option.initFrom(this);QPainter painter(this);style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
}
有時候在界面上加了彈簧,需要動態改變彈簧對應的拉伸策略,對應方法為changeSize,很多人會選擇使用set開頭去找,找不到的。
在使用QFile的過程中,不建議頻繁的打開文件寫入然后再關閉文件,比如間隔5ms輸出日志,IO性能瓶頸很大,這種情況建議先打開文件不要關閉,等待合適的時機比如析構函數中或者日期變了需要重新變換日志文件的時候關閉文件。不然短時間內大量的打開關閉文件會很卡,文件越大越卡。
在很多網絡應用程序,需要自定義心跳包來保持連接,不然斷電或者非法關閉程序,對方識別不到,需要進行超時檢測,但是有些程序沒有提供心跳協議,此時需要啟用系統層的保活程序,此方法適用于TCP連接。
int fd = tcpSocket->socketDescriptor();
int keepAlive = 1; //開啟keepalive屬性,缺省值:0(關閉)
int keepIdle = 5; //如果在5秒內沒有任何數據交互,則進行探測,缺省值:7200(s)
int keepInterval = 2; //探測時發探測包的時間間隔為2秒,缺省值:75(s)
int keepCount = 2; //探測重試的次數,全部超時則認定連接失效,缺省值:9(次)
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
如果程序打包好以后彈出提示 This application failed to start because it could not find or load the Qt platform plugin 一般都是因為platforms插件目錄未打包或者打包錯了的原因導致的。
非常不建議tr中包含中文,盡管現在的新版Qt支持中文到其他語言的翻譯,但是很不規范,也不知道TMD是誰教的,tr的本意是包含英文,然后翻譯到其他語言比如中文,現在大量的初學者濫用tr,如果沒有翻譯的需求,禁用tr,tr需要開銷的,Qt默認會認為他需要翻譯,會額外進行特殊處理。
很多人Qt和Qt Creator傻傻分不清楚,經常問Qt什么版本結果發一個Qt Creator的版本過來,Qt Creator是使用Qt編寫的集成開發環境IDE,和宇宙第一的Visual Studio一樣,他可以是msvc編譯器的(WIN對應的Qt集成安裝環境中自帶的Qt Cerator是msvc的),也可以是mingw編譯的,還可以是gcc的。如果是自定義控件插件,需要集成到Qt Creator中,必須保證該插件的動態庫文件(dll或者so等文件)對應的編譯器和Qt版本以及位數和Qt Creator的版本完全一致才行,否則基本不大可能集成進去。特別注意的是Qt集成環境安裝包中的Qt版本和Qt Creator版本未必完全一致,必須擦亮眼睛看清楚,有些是完全一致的。
超過兩處相同處理的代碼,建議單獨寫成函數。代碼盡量規范精簡,比如 if(a == 123) 要寫成 if (123 == a),值在前面,再比如 if (ok == true) 要寫成 if (ok),if (ok == false) 要寫成 if (!ok)等。
07:061-070
很多人問Qt嵌入式平臺用哪個好,這里統一回答(當前時間節點2018年):imx6+335x比較穩定,性能高就用RK3288 RK3399,便宜的話就用全志H3,玩一玩可以用樹莓派香橙派。
對于大段的注釋代碼,建議用 #if 0 #endif 將代碼塊包含起來,而不是將該段代碼選中然后全部雙斜杠注釋,下次要打開這段代碼的話,又需要重新選中一次取消,如果采用的是 #if 0則只要把0改成1即可,開發效率提升很多。
Qt打包發布,有很多辦法,Qt5以后提供了打包工具windeployqt(linux上為linuxdeployqt,mac上為macdeployqt)可以很方便的將應用程序打包,使用下來發現也不是萬能的,有時候會多打包一些沒有依賴的文件,有時候又會忘記打包一些插件尤其是用了qml的情況下,而且不能識別第三方庫,比如程序依賴ffmpeg,則對應的庫需要自行拷貝,終極大法就是將你的可執行文件復制到Qt安裝目錄下的bin目錄,然后整個一起打包,挨個刪除不大可能依賴的組件,直到刪到正常運行為止。
Qt中的動畫,底層用的是QElapsedTimer定時器來完成處理,比如產生一些指定規則算法的數據,然后對屬性進行處理。
在繪制無背景顏色只有邊框顏色的圓形時候,可以用繪制360度的圓弧替代,效果完全一致。
QRect rect(-radius, -radius, radius * 2, radius * 2);
//以下兩種方法二選一,其實繪制360度的圓弧=繪制無背景的圓形
painter->drawArc(rect, 0, 360 * 16);
painter->drawEllipse(rect);
不要把d指針看的很玄乎,其實就是在類的實現文件定義了一個私有類,用來存放局部變量,個人建議在做一些小項目時,沒有太大必要引入這種機制,會降低代碼可讀性,增加復雜性,新手接受項目后會看的很懵逼。
很多人在繪制的時候,設置畫筆以為就只可以設置個單調的顏色,其實QPen還可以設置brush,這樣靈活性就提高不知道多少倍,比如設置QPen的brush以后,可以使用各種漸變,比如繪制漸變顏色的進度條和文字等,而不再是單調的一種顏色。
很多控件都帶有viewport,比如QTextEdit/QTableWidget/QScrollArea,有時候對這些控件直接處理的時候發現不起作用,需要對其viewport()設置才行,比如設置滾動條區域背景透明,需要使用scrollArea->viewport()->setStyleSheet("background-color:transparent;");而不是scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}");
有時候設置了鼠標跟蹤setMouseTracking為真,如果該窗體上面還有其他控件,當鼠標移到其他控件上面的時候,父類的鼠標移動事件MouseMove識別不到了,此時需要用到HoverMove事件,需要先設置 setAttribute(Qt::WA_Hover, true);
Qt封裝的QDateTime日期時間類非常強大,可以字符串和日期時間相互轉換,也可以毫秒數和日期時間相互轉換,還可以1970經過的秒數和日期時間相互轉換等。
QDateTime dateTime;
QString dateTime_str = dateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//從字符串轉換為毫秒(需完整的年月日時分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toMSecsSinceEpoch();
//從字符串轉換為秒(需完整的年月日時分秒)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toTime_t();
//從毫秒轉換到年月日時分秒
datetime.fromMSecsSinceEpoch(1315193829218).toString("yyyy-MM-dd hh:mm:ss:zzz");
//從秒轉換到年月日時分秒(若有zzz,則為000)
datetime.fromTime_t(1315193829).toString("yyyy-MM-dd hh:mm:ss[:zzz]");
08:071-080
在我們使用QList、QStringList、QByteArray等鏈表或者數組的過程中,如果只需要取值,而不是賦值,強烈建議使用 at() 取值而不是 [] 操作符,在官方書籍《C++ GUI Qt 4編程(第二版)》的書中有特別的強調說明,此教材的原作者據說是Qt開發的核心人員編寫的,所以還是比較權威,至于使用 at() 與使用 [] 操作符速度效率的比較,網上也有網友做過此類對比。原文在書的212頁,這樣描述的:Qt對所有的容器和許多其他類都使用隱含共享,隱含共享是Qt對不希望修改的數據決不進行復制的保證,為了使隱含共享的作用發揮得最好,可以采用兩個新的編程習慣。第一種習慣是對于一個(非常量的)向量或者列表進行只讀存取時,使用 at() 函數而不用 [] 操作符,因為Qt的容器類不能辨別 [] 操作符是否將出現在一個賦值的左邊還是右邊,他假設最壞的情況出現并且強制執行深層賦值,而 at() 函數則不被允許出現在一個賦值的左邊。
如果是dialog窗體,需要在exec以后還能讓其他代碼繼續執行,請在dialog窗體exec前增加一行代碼,否則會阻塞窗體消息。
QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
安全的刪除Qt的對象類,強烈建議使用deleteLater而不是delete,因為deleteLater會選擇在合適的時機進行釋放,而delete會立即釋放,很可能會出錯崩潰。如果要批量刪除對象集合,可以用qDeleteAll,比如 qDeleteAll(btns);
在QTableView控件中,如果需要自定義的列按鈕、復選框、下拉框等其他模式顯示,可以采用自定義委托QItemDelegate來實現,如果需要禁用某列,則在自定義委托的重載createEditor函數返回0即可。自定義委托對應的控件在進入編輯狀態的時候出現,如果想一直出現,則需要重載paint函數用drawPrimitive或者drawControl來繪制。
將 QApplication::style() 對應的drawPrimitive、drawControl、drawItemText、drawItemPixmap等幾個方法用熟悉了,再結合QStyleOption屬性,可以玩轉各種自定義委托,還可以直接使用paint函數中的painter進行各種繪制,各種牛逼的表格、樹狀列表、下拉框等,絕對屌炸天。QApplication::style()->drawControl 的第4個參數如果不設置,則繪制出來的控件不會應用樣式表。
心中有坐標,萬物皆painter,強烈建議在學習自定義控件繪制的時候,將qpainter.h頭文件中的函數全部看一遍、試一遍、理解一遍,這里邊包含了所有Qt內置的繪制的接口,對應的參數都試一遍,你會發現很多新大陸,會一定程度上激發你的繪制的興趣,猶如神筆馬良一般,策馬崩騰遨游代碼繪制的世界。
在使用setItemWidget或者setCellWidget的過程中,有時候會發現設置的控件沒有居中顯示而是默認的左對齊,而且不會自動拉伸填充,對于追求完美的程序員來說,這個可不大好看,有個終極通用辦法就是,將這個控件放到一個widget的布局中,然后將widget添加到item中,這樣就完美解決了,而且這樣可以組合多個控件產生復雜的控件。
//實例化進度條控件
QProgressBar *progress = new QProgressBar;
//增加widget+布局巧妙實現居中
QWidget *widget = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setSpacing(0);
layout->setMargin(0);
layout->addWidget(progress);
widget->setLayout(layout);
ui->tableWidget->setCellWidget(0, 0, widget);
很多時候需要在已知背景色的情況下,能夠清晰的繪制文字,這個時候需要計算對應的文字顏色。
//根據背景色自動計算合適的前景色
double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
QColor textColor = gray > 0.5 ? Qt::black : Qt::white;
對QTableView或者QTableWidget禁用列拖動。
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))ui->tableView->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed);
#elseui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
#endif
從Qt4轉到Qt5,有些類的方法已經廢棄或者過時了,如果想要在Qt5中啟用Qt4的方法,比如QHeadVew的setMovable,可以在你的pro或者pri文件中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
09:081-090
Qt中的QColor對顏色封裝的很完美,支持各種轉換,比如rgb、hsb、cmy、hsl,對應的是toRgb、toHsv、toCmyk、toHsl,還支持透明度設置,顏色值還能轉成16進制格式顯示。
QColor color(255, 0, 0, 100);
qDebug() << color.name() << color.name(QColor::HexArgb);
//輸出 #ff0000 #64ff0000
QVariant類型異常的強大,可以說是萬能的類型,在進行配置文件的存儲的時候,經常會用到QVariant的轉換,QVariant默認自帶了toString、toFloat等各種轉換,但是還是不夠,比如有時候需要從QVariant轉到QColor,而卻沒有提供toColor的函數,這個時候就要用到萬能辦法。
if (variant.typeName() == "QColor") {QColor color = variant.value<QColor>();QFont font = variant.value<QFont>();QString nodeValue = color.name(QColor::HexArgb);
}
Qt中的QString和const char *之間轉換,最好用toStdString().c_str()而不是toLocal8Bit().constData(),比如在setProperty中如果用后者,字符串中文就會不正確,英文正常。
Qt的信號槽機制非常牛逼,也是Qt的獨特的核心功能之一,有時候我們在很多窗體中傳遞信號來實現更新或者處理,如果窗體層級比較多,比如窗體A的父類是窗體B,窗體B的父類是窗體C,窗體C有個子窗體D,如果窗體A一個信號要傳遞給窗體D,問題來了,必須先經過窗體B中轉到窗體C再到窗體D才行,這樣的話各種信號關聯信號的connect會非常多而且管理起來比較亂,可以考慮增加一個全局的單例類AppEvent,公共的信號放這里,然后窗體A對應信號綁定到AppEvent,窗體D綁定AppEvent的信號到對應的槽函數即可,干凈清爽整潔。
QTextEdit右鍵菜單默認英文的,如果想要中文顯示,加載widgets.qm文件即可,一個Qt程序中可以安裝多個翻譯文件,不沖突。
Qt中有個全局的焦點切換信號focusChanged,可以用它做自定義的輸入法。Qt4中默認會安裝輸入法上下文,比如在main函數打印a.inputContext會顯示值,這個默認安裝的輸入法上下文,會攔截兩個牛逼的信號QEvent::RequestSoftwareInputPanel和QEvent::CloseSoftwareInputPanel,以至于就算你安裝了全局的事件過濾器依然識別不到這兩個信號,你只需要在main函數執行a.setInputContext(0)即可,意思是安裝輸入法上下文為空。Qt5.7以后提供了內置的輸入法,可以通過在main函數最前面加上 qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); 來啟用。
在Qt5.10以后,表格控件QTableWidget或者QTableView的默認最小列寬改成了15,以前的版本是0,所以在新版的qt中,如果設置表格的列寬過小,不會應用,取的是最小的列寬。所以如果要設置更小的列寬需要重新設置ui->tableView->horizontalHeader()->setMinimumSectionSize(0);
Qt源碼中內置了一些未公開的不能直接使用的黑科技,都藏在對應模塊的private中,比如gui-private widgets-private等,比如zip文件解壓類QZipReader、壓縮類QZipWriter就在gui-private模塊中,需要在pro中引入QT += gui-private才能使用。
#include "QtGui/private/qzipreader_p.h"
#include "QtGui/private/qzipwriter_p.h"QZipReader reader(dirPath);
QString path("");
//解壓文件夾到當前目錄
reader.extractAll(path);
//文件夾名稱
QZipReader::FileInfo fileInfo = reader.entryInfoAt(0);
//解壓文件
QFile file(filePath);
file.open(QIODevice::WriteOnly);
file.write(reader.fileData(QString::fromLocal8Bit("%1").arg(filePath)));
file.close();
reader.close();QZipWriter *writer = new QZipWriter(dirPath);
//添加文件夾
writer->addDirectory(unCompress);
//添加文件
QFile file(filePath);
file.open(QIODevice::ReadOnly);
writer->addFile(data, file.readAll());
file.close();
writer->close();
理論上串口和網絡收發數據都是默認異步的,操作系統自動調度,完全不會卡住界面,網上那些說收發數據卡住界面主線程的都是扯幾把蛋,真正的耗時是在運算以及運算后的處理,而不是收發數據,在一些小數據量運算處理的項目中,一般不建議動用線程去處理,線程需要調度開銷的,不要什么東西都往線程里邊扔,線程不是萬能的。只有當真正需要將一些很耗時的操作比如編碼解碼等,才需要移到線程處理。
在構造函數中獲取控件的寬高很可能是不正確的,需要在控件首次顯示以后再獲取才是正確的,控件是在首次顯示以后才會設置好正確的寬高值,記住是在首次顯示以后,而不是構造函數或者程序啟動好以后,如果程序啟動好以后有些容器控件比如QTabWidget中的沒有顯示的頁面的控件,你去獲取寬高很可能也是不正確的,萬無一失的辦法就是首次顯示以后去獲取。
10:091-100
數據庫處理一般建議在主線程,如果非要在其他線程,務必記得打開數據庫也要在那個線程,即在那個線程使用數據庫就在那個線程打開,不能打開數據庫在主線程,執行sql在子線程,很可能出問題。
新版的QTcpServer類在64位版本的Qt下很可能不會進入incomingConnection函數,那是因為Qt5對應的incomingConnection函數參數變了,由之前的int改成了qintptr,改成qintptr有個好處,在32位上自動是quint32而在64位上自動是quint64,如果在Qt5中繼續寫的參數是int則在32位上沒有問題在64位上才有問題,所以為了兼容Qt4和Qt5,必須按照不一樣的參數寫。
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))void incomingConnection(qintptr handle);
#elsevoid incomingConnection(int handle);
#endif
Qt支持所有的界面控件比如QPushButton、QLineEdit自動關聯 on_控件名_信號(參數) 信號槽,比如按鈕的單擊信號 on_pushButton_clicked(),然后直接實現槽函數即可。
QWebEngineView控件由于使用了opengl,在某些電腦上可能由于opengl的驅動過低會導致花屏或者各種奇奇怪怪的問題,比如showfullscreen的情況下鼠標右鍵失效,需要在main函數啟用軟件opengl渲染。
#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))//下面兩種方法都可以,Qt默認采用的是AA_UseDesktopOpenGLQCoreApplication::setAttribute(Qt::AA_UseOpenGLES);//QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#endifQApplication a(argc, argv);
另外一個方法解決 全屏+QWebEngineView控件一起會產生右鍵菜單無法彈出的bug,需要上移一個像素
QRect rect = qApp->desktop()->geometry();
rect.setY(-1);
rect.setHeight(rect.height());
this->setGeometry(rect);
QStyle內置了很多方法用處很大,比如精確獲取滑動條鼠標按下處的值。
QStyle::sliderValueFromPosition(minimum(), maximum(), event->x(), width());
用QFile讀寫文件的時候,推薦用QTextStream文件流的方式來讀寫文件,速度快很多,基本上會有30%的提升,文件越大性能區別越大。
//從文件加載英文屬性與中文屬性對照表
QFile file(":/propertyname.txt");
if (file.open(QFile::ReadOnly)) {//QTextStream方法讀取速度至少快百分之30
#if 0while(!file.atEnd()) {QString line = file.readLine();appendName(line);}
#elseQTextStream in(&file);while (!in.atEnd()) {QString line = in.readLine();appendName(line);}
#endiffile.close();
}
用QFile.readAll()讀取QSS文件默認是ANSI格式,不支持UTF8,如果在QtCreator中打開qss文件來編輯保存,這樣很可能導致qss加載以后沒有效果。
void frmMain::initStyle()
{//加載樣式表QString qss;//QFile file(":/qss/psblack.css");//QFile file(":/qss/flatwhite.css");QFile file(":/qss/lightblue.css");if (file.open(QFile::ReadOnly)) {
#if 1//用QTextStream讀取樣式文件不用區分文件編碼 帶bom也行QStringList list;QTextStream in(&file);//in.setCodec("utf-8");while (!in.atEnd()) {QString line;in >> line;list << line;}qss = list.join("\n");
#else//用readAll讀取默認支持的是ANSI格式,如果不小心用creator打開編輯過了很可能打不開qss = QLatin1String(file.readAll());
#endifQString paletteColor = qss.mid(20, 7);qApp->setPalette(QPalette(QColor(paletteColor)));qApp->setStyleSheet(qss);file.close();}
}
QString內置了很多轉換函數,比如可以調用toDouble轉為double數據,但是當你轉完并打印的時候你會發現精確少了,只剩下三位了,其實原始數據還是完整的精確度的,只是打印的時候優化成了三位,如果要保證完整的精確度,可以調用 qSetRealNumberPrecision 函數設置精確度位數即可。
QString s1, s2;
s1 = "666.5567124";
s2.setNum(888.5632123, 'f', 7);
qDebug() << qSetRealNumberPrecision(10) << s1.toDouble() << s2.toDouble();
用QScriptValueIterator解析數據的時候,會發現總是會多一個節點內容,并且內容為空,如果需要跳過則增加一行代碼。
while (it.hasNext()) {it.next(); if (it.flags() & QScriptValue::SkipInEnumeration) continue; qDebug() << it.name();
}
setPixmap是最糟糕的貼圖方式,一般只用來簡單的不是很頻繁的貼圖,頻繁的建議painter繪制,默認雙緩沖,在高級點用opengl繪制,利用GPU。
11:101-110
如果需要在尺寸改變的時候不重繪窗體,則設置屬性即可 this->setAttribute(Qt::WA_StaticContents, true); 這樣可以避免可以避免對已經顯示區域的重新繪制。
默認程序中獲取焦點以后會有虛邊框,如果看著覺得礙眼不舒服可以去掉,設置樣式即可:setStyleSheet("*{outline:0px;}");
Qt表格控件一些常用的設置封裝,QTableWidget繼承自QTableView,所以下面這個函數支持傳入QTableWidget。
void QUIHelper::initTableView(QTableView *tableView, int rowHeight, bool headVisible, bool edit)
{//奇數偶數行顏色交替tableView->setAlternatingRowColors(false);//垂直表頭是否可見tableView->verticalHeader()->setVisible(headVisible);//選中一行表頭是否加粗tableView->horizontalHeader()->setHighlightSections(false);//最后一行拉伸填充tableView->horizontalHeader()->setStretchLastSection(true);//行標題最小寬度尺寸tableView->horizontalHeader()->setMinimumSectionSize(0);//行標題最大高度tableView->horizontalHeader()->setMaximumHeight(rowHeight);//默認行高tableView->verticalHeader()->setDefaultSectionSize(rowHeight);//選中時一行整體選中tableView->setSelectionBehavior(QAbstractItemView::SelectRows);//只允許選擇單個tableView->setSelectionMode(QAbstractItemView::SingleSelection);//表頭不可單擊
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))tableView->horizontalHeader()->setSectionsClickable(false);
#elsetableView->horizontalHeader()->setClickable(false);
#endif//鼠標按下即進入編輯模式if (edit) {tableView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked);} else {tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);}
}
在一些大的項目中,可能嵌套了很多子項目,有時候會遇到子項目依賴其他子項目的時候,比如一部分子項目用來生成動態庫,一部分子項目依賴這個動態庫進行編譯,此時就需要子項目按照順序編譯或者設置好依賴規則。
TEMPLATE = subdirs
#設置ordered參數以后會依次編譯 projA projB projC
CONFIG += ordered
SUBDIRS += projA
SUBDIRS += projB
SUBDIRS += projC
#還可以通過設置depends指定某個項目依賴 比如下面指定projB依賴projA
projB.depends = projA
projC.depends = projA
projD.depends = projC
MSVC編譯器的選擇說明
如果是32位的Qt則編譯器選擇x86開頭的 如果是64位的Qt則編譯器選擇amd64開頭的 具體是看安裝的Qt構建套件版本以及目標運行平臺的系統位數和架構 一般現在的電腦默認以64位的居多,選擇amd64即可 如果用戶需要兼容32位的系統則建議選擇32位的Qt,這樣即可在32位也可以在64位系統運行 諸葛大佬補充:x86/x64都是編譯環境和運行環境相同,沒有或。帶下劃線的就是交叉編譯,前面是編譯環境,后面是運行環境。
名稱說明 x86 32/64位系統上編譯在32/64位系統上運行 x86_amd64 32/64位系統上編譯在64位系統上運行 x86_arm 32/64位系統上編譯在arm系統上運行 amd64 64位系統上編譯在64位系統上運行 amd64_x86 64位系統上編譯在32/64位系統上運行 amd64_arm 64位系統上編譯在arm系統上運行
很多時候用QDialog的時候會發現阻塞了消息,而有的時候我們希望是后臺的一些消息繼續運行不要終止,此時需要做個設置。
QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
很多初學者甚至幾年工作經驗的人,對多線程有很深的誤解和濫用,尤其是在串口和網絡通信這塊,什么都往多線程里面丟,一旦遇到界面卡,就把數據收發啥的都搞到多線程里面去,殊不知絕大部分時候那根本沒啥用,因為沒找到出問題的根源。
如果你沒有使用wait***函數的話,大部分的界面卡都出在數據處理和展示中,比如傳過來的是一張圖片的數據,你需要將這些數據轉成圖片,這個肯定是耗時的; 還有就是就收到的數據曲線繪制出來,如果過于頻繁或者間隔過短,肯定會給UI造成很大的壓力的,最好的辦法是解決如何不要頻繁繪制UI比如合并數據一起繪制等; 如果是因為繪制UI造成的卡,那多線程也是沒啥用的,因為UI只能在主線程; 串口和網絡的數據收發默認都是異步的,由操作系統調度的,如果數據處理復雜而且數據量大,你要做的是將數據處理放到多線程中; 如果沒有嚴格的數據同步需求,根本不需要調用wait***之類的函數來立即發送和接收數據,實際需求中大部分的應用場景其實異步收發數據就足夠了; 有嚴格數據同步需求的場景還是放到多線程會好一些,不然你wait***就卡在那邊了; 多線程是需要占用系統資源的,理論上來說,如果線程數量超過了CPU的核心數量,其實多線程調度可能花費的時間更多,各位在使用過程中要權衡利弊; 再次強調,不要指望Qt的網絡通信支持高并發,最多到1000個能正常工作就萬事大吉,一般建議500以內的連接數。有大量高并發的需求請用第三方庫比如swoole等。
在嵌入式linux上,如果設置了無邊框窗體,而該窗體中又有文本框之類的,發現沒法產生焦點進行輸入,此時需要主動激活窗體才行。
//這種方式設置的無邊框窗體在嵌入式設備上無法產生焦點
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);//需要在show以后主動激活窗體
w->show();
w->activateWindow();
QString的replace函數會改變原字符串,切記,他在返回替換后的新字符串的同時也會改變原字符串,我的乖乖!
QGraphicsEffect類的相關效果很炫,可以實現很多效果比如透明、漸變、陰影等,但是該類很耗CPU,如果不是特別需要一般不建議用,就算用也是要用在該部件后期不會發生頻繁繪制的場景,不然會讓你哭暈在廁所。
12:111-120
在不同的平臺上文件路徑的斜杠也是不一樣的,比如linux系統一般都是 / 斜杠,而在windows上都是 \ 兩個反斜杠,Qt本身程序內部無論在win還是linux都支持 / 斜杠的路徑,但是一些第三方庫的話可能需要轉換成對應系統的路徑,這就需要用到斜杠轉換,Qt當然內置類方法。
QString path = "C:/temp/test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:\\temp\\test.txtQString path = "C:\\temp\\test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:/temp/test.txt
巧用QMetaObject::invokeMethod方法可以實現很多效果,包括同步和異步執行,很大程度上解決了跨線程處理信號槽的問題。比如有個應用場景是在回調中,需要異步調用一個public函數,如果直接調用的話會發現不成功,此時需要使用 QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection); 這種方式來就可以。
invokeMethod函數有很多重載參數,可以傳入返回值和執行方法的參數等。 invokeMethod函數不僅支持槽函數還支持信號,而且這逼居然是線程安全的,可以在線程中放心使用,牛逼! 測試下來發現只能執行signals或者slots標識的方法。 默認可以執行private(protected/public) slots下的函數,但是不能執行private(protected/public)下的函數。 毛總補充:前提必須是slots或者signals標注的函數,不是標注的函數不在元信息導致無法查找,執行之后會提示No such method。 2021-11-06補充:如果要執行private(protected/public)下的函數,需要函數前面加上 Q_INVOKABLE 關鍵字,今天又學到了,必須加雞腿。 其實這樣看下來,就是任何方法函數都能執行了,這就超越了private(protected/public)的權限限定了,相當于一個類的私有函數用了 Q_INVOKABLE 關鍵字修飾也可以被 invokeMethod 執行,哇咔咔。
//頭文件聲明信號和槽函數
signals:void sig_test(int type,double value);
private slots:void slot_test(int type, double value);
private:Q_INVOKABLE void fun_test(int type, double value);//構造函數關聯信號槽
connect(this, SIGNAL(sig_test(int, double)), this, SLOT(slot_test(int, double)));//單擊按鈕觸發信號和槽,這里是同時舉例信號槽都可以
void MainWindow::on_pushButton_clicked()
{QMetaObject::invokeMethod(this, "sig_test", Q_ARG(int, 66), Q_ARG(double, 66.66));QMetaObject::invokeMethod(this, "slot_test", Q_ARG(int, 88), Q_ARG(double, 88.88));QMetaObject::invokeMethod(this, "fun_test", Q_ARG(int, 99), Q_ARG(double, 99.99));
}//會打印 66 66.66、88 88.88
void MainWindow::slot_test(int type, double value)
{qDebug() << type << value;
}//會打印 99.99
void MainWindow::fun_test(int type, double value)
{qDebug() << type << value;
}
Qt5中的信號是public的,可以在需要的地方直接emit即可,而在Qt4中信號是protected的,不能直接使用,需要定義一個public函數來emit。
Qt5.15版本開始官方不再提供安裝包,只提供源碼,可以自行編譯或者在線安裝,估計每次編譯各種版本太麻煩,更多的是為了統計收集用戶使用信息比如通過在線安裝,后期可能會逐步加大商業化力度。
有時候我們需要判斷當前Qt版本有沒有某個模塊可以使用qtHaveModule(Qt5新引入的判斷)來判斷,如果要判斷自己的項目中有沒有 QT += 的方式添加的模塊,可以用 contains來判斷。
qtHaveModule(webenginewidgets) {
message("當前Qt庫有找到 webenginewidgets 模塊")
}!qtHaveModule(webkit) {
message("當前Qt庫沒有找到 webkit 模塊")
}contains(QT, network) {
message("當前項目已經引入 network 模塊")
}!contains(QT, widgets) {
message("當前項目沒有引入 widgets 模塊")
}
c++11新引入了原始字符串格式,用戶避免在字符串中加入轉義字符\,可以用于表示json字符串等場景。
QString s1 = R"(test\001.jpg)";
s1.replace("\\", "#");
qDebug()<< s1;
//結果 test#001.jpg
安卓上打印信息建議使用 qInfo() 而不是 qDebug() ,qInfo()才有效果。
Qt的默認定時器精度不夠高(比如應用場景是1分鐘保存一條記錄或者文件,當你用默認的定時器的時候你會發現有些時候是60秒而有些是59秒隨機的,如果客戶有要求這就需要設置精度了。當然我們所做的絕大部分項目也不需要精度非常高的定時器,畢竟精度越高,占用的系統資源可能越大),如果需要設置更高的精度可以設置 setTimerType(Qt::PreciseTimer)。Qt有兩種定時器處理,一種是QTimer類,還有一種是QObject類就內置的timeevent事件,如果是QObject類的定時器要設置的話調用 startTimer(interval, Qt::PreciseTimer);
Qt::PreciseTimer 精確的定時器,盡量保持毫秒精度。 Qt::CoarseTimer 粗略的定時器,盡量保持精度在所需的時間間隔5%范圍內。 Qt::VeryCoarseTimer 很粗略的定時器,只保留完整的第二精度。 精度再高,也依賴對應的操作系統中斷,假設中斷需要 5ms,則定時器精度不可能高于5毫秒。
QGraphicsEffect相關類很耗CPU,甚至在繪制的時候和某些地方有沖突干擾,基本上不建議使用,情非得已只建議少量使用和非頻繁觸發繪制的地方使用。
用QSettings設置注冊表,如果不是管理員身份運行會打印 QSettings: failed to set subkey "xxx" (拒絕訪問。),你需要手動鼠標右鍵管理員身份運行就可以。
13:121-130
QLineEdit除了單純的文本框以外,還可以做很多特殊的處理用途。
限制輸入只能輸入IP地址。 限制輸入范圍,強烈推薦使用 QRegExpValidator 正則表達式來處理。
//正在表達式限制輸入
QString str = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b";
ui->lineEdit->setValidator(new QRegExpValidator(QRegExp(str)));
//用于占位
ui->lineEdit->setInputMask("000.000.000.000");#if 0
//下面代碼設置浮點數范圍限制失敗
ui->lineEdit->setValidator(new QDoubleValidator(20, 50, 1));
#else
//下面代碼設置浮點數范圍限制成功
QDoubleValidator *validator = new QDoubleValidator(20, 50, 1);
validator->setNotation(QDoubleValidator::StandardNotation);
ui->lineEdit->setValidator(validator);
#endif
//下面代碼設置整數范圍限制成功
ui->lineEdit->setValidator(new QIntValidator(10, 120));//其實上面的代碼缺陷很多,只能限制只輸入小數,無法設定數值范圍,很操蛋
//需要來個萬能的牛逼的 QRegExpValidator//限制浮點數輸入范圍為[-180,180]
QRegExp regexp("^-?(180|1?[0-7]?\\d(\\.\\d+)?)$");
//限制浮點數輸入范圍為[-90,90]并限定為小數位后4位
QRegExp regexp("^-?(90|[1-8]?\\d(\\.\\d{1,4})?)$");
QRegExpValidator *validator = new QRegExpValidator(regexp, this);
ui->lineEdit->setValidator(validator);
在繼承自QAbstractItemView的控件中,比如QTableView、QTableWidget,如果文本超過對應item的寬度,則會自動省略號顯示,想要快速顯示完整的文本,可以在該列和下一列分割線中間雙擊即可,會自動自適應顯示最大寬度,如果是Qt5.14或者更高版本,你會發現顯示省略號的計算規則變了,如果是rtsp、http之類的開頭的英文字符串,同樣的列寬下,會提前就顯示省略號,比如字符串 rtmp://58.200.131.2:1935/livetv/cctv1,會顯示成 rtmp://... ,而在舊版本的Qt中會顯示成 rtmp://58.200.131... ,很多時候我們并不想看到煩人的省略號,可以設置取消。
//取消自動換行
tableView->setWordWrap(false);
//超出文本不顯示省略號
tableView->setTextElideMode(Qt::ElideNone);
QVideoWidget播放視頻,可能會遇到畫面閃爍的情況,播放視頻的窗體需要設置個屬性。
QVideoWidget *videoWidget = new QVideoWidget;
videoWidget->setAttribute(Qt::WA_OpaquePaintEvent);
Qt bug成千上萬,這個不用大驚小怪,也基本上遇不到,大部分都是特殊極端情況特定應用場景出現,甚至你會遇到有些是debug可以release報錯,有些release可以debug卻報錯的情況,最神奇的還有先是debug報錯,然后release正常,再返回去用debug又正常,需要用release激活一下!學習編程的路本來就是一條坑坑洼洼的路,不斷填坑,盡量規避坑!很多時候很多看起來的坑其實是自己沒有注意細節導致的。
Qt視圖中默認排序是按照字符串的ASCII排序的,如果是IP地址的話會出現192.168.1.117排在192.168.1.2前面的情況,如果要規避這種情況,一種做法是取末尾的地址轉成整型再比較大小,缺點是跨網段就歇菜了,又會出現192.168.2.65出現在192.168.1.70前面,終極大法是將IP地址轉成整型再比較大小。
QString QUIHelper::ipv4IntToString(quint32 ip)
{QString result = QString("%1.%2.%3.%4").arg((ip >> 24) & 0xFF).arg((ip >> 16) & 0xFF).arg((ip >> 8) & 0xFF).arg(ip & 0xFF);return result;
}quint32 QUIHelper::ipv4StringToInt(const QString &ip)
{int result = 0;if (isIP(ip)) {QStringList list = ip.split(".");int ip0 = list.at(0).toInt();int ip1 = list.at(1).toInt();int ip2 = list.at(2).toInt();int ip3 = list.at(3).toInt();result = ip3 | ip2 << 8 | ip1 << 16 | ip0 << 24;}return result;
}
在主QWidget窗體如果直接qss設置背景圖片的話,預覽是可見的,運行并沒有效果,你需要在這個主widget上再放個widget,在新的widget上設置qss圖片就行,而如果是Dialog或者QMainWindow窗體是支持直接設置qss背景圖的,預覽和運行效果一致。
Qt提供了qDebug機制直接輸出打印信息,這個彌補了QtCreator調試很雞肋的缺點,而且無縫對接日志鉤子,使得現場運行期間按照預定的打印信息輸出到日志文件,有時候在開發階段,又不想要看到一堆堆的打印信息,最笨的做法是一行行注釋掉qdebug的地方,其實還可以直接pro中加上一行來禁用整個項目的qdebug輸出。
#禁用qdebug打印輸出
DEFINES += QT_NO_DEBUG_OUTPUT
在使用 QT_NO_DEBUG_OUTPUT 關鍵字禁用了所有打印信息以后,可以節約不少的開銷,有時候又想在禁用打印信息后,極少地方還需要看到打印信息,怎么辦呢?其實 QT_NO_DEBUG_OUTPUT 禁用的 qdebug 的輸出,Qt還有其他幾種打印信息比如 qInfo、qWarning、qCritical,這些是不受影響的,也就是說在極少部分需要打印的地方用 qInfo 來輸出信息就好。特別注意:qFatal 打印完信息程序會自動結束。
qDebug() << "qDebug";
qInfo() << "qInfo";
qWarning() << "qWarning";
qCritical() << "qCritical";qDebug("qDebug");
qWarning("qWarning");
qCritical("qCritical");
Qt的pro文件可以添加各種處理來使得配置更方便,比如指定輸出文件路徑等,這樣就不會全部在一堆編譯生成的臨時文件中找來找去。
#禁用qdebug打印輸出
DEFINES += QT_NO_DEBUG_OUTPUT#自定義define變量 可以在整個項目中使用
#pro文件可以這樣判斷 contains(DEFINES, videovlc) {}
#代碼文件可以這樣判斷 #ifdef videovlc
DEFINES += videovlc1 videoffmpeg#關閉編譯警告提示 眼不見為凈
CONFIG += warn_off#指定編譯生成的文件到temp目錄 分門別類存儲
MOC_DIR = temp/moc
RCC_DIR = temp/rcc
UI_DIR = temp/ui
OBJECTS_DIR = temp/obj#指定編譯生成的可執行文件到bin目錄
DESTDIR = bin
Qt對操作系統層的消息也做了很多的封裝,可以直接拿到進行處理(如果需要攔截處理要用對應操作系統的API才行比如鼠標鍵盤鉤子),比如系統休眠和喚醒做一些處理。
//主窗體頭文件
protected:bool nativeEvent(const QByteArray &eventType, void *message, long *result);
#ifdef Q_OS_WINbool winEvent(MSG *message, long *result);
#endif//主窗體實現函數
#ifdef Q_OS_WIN
#include "Windows.h"
#endifbool frmMain::nativeEvent(const QByteArray &eventType, void *message, long *result)
{if (eventType == "windows_generic_MSG") {
#ifdef Q_OS_WINMSG *msg = static_cast<MSG *>(message);//qDebug() << TIMEMS << msg->message;if (msg->wParam == PBT_APMSUSPEND && msg->message == WM_POWERBROADCAST) {//系統休眠的時候自動最小化可以規避程序可能出現的問題this->showMinimized();} else if (msg->wParam == PBT_APMRESUMEAUTOMATIC) {//休眠喚醒后自動打開this->showNormal();}
#endif} else if (eventType == "NSEvent") {
#ifdef Q_OS_MACOS
#endif}return false;
}#ifdef Q_OS_WIN
bool frmMain::winEvent(MSG *message, long *result)
{return nativeEvent("windows_generic_MSG", message, result);
}
#endif
Qt的pro項目管理配置文件中也可添加各種編譯前后的操作及配置,主要通過 QMAKE_POST_LINK和QMAKE_PRE_LINK,他們支持的函數以及寫法,可以在QtCreator的幫助中搜索 qmake Function Reference 查看詳情說明。
QMAKE_PRE_LINK 表示編譯前執行內容 QMAKE_POST_LINK 表示編譯后執行內容
srcFile1 = $$PWD/1.txt
srcFile2 = $$PWD/2.txt
dstDir = $$PWD/../bin
#windows上需要轉換路徑斜杠 其他系統不需要
srcFile1 = $$replace(srcFile1, /, \\);
srcFile2 = $$replace(srcFile2, /, \\);
dstDir = $$replace(dstDir, /, \\);#編譯前執行拷貝 多個拷貝可以通過 && 符號隔開
QMAKE_PRE_LINK += copy /Y $$srcFile1 $$dstDir && copy /Y $$srcFile2 $$dstDir
#編譯后執行拷貝 多個拷貝可以通過 && 符號隔開
QMAKE_POST_LINK += copy /Y $$srcFile1 $$dstDir && copy /Y $$srcFile2 $$dstDir
14:131-140
Qt新版本往往會帶來一些頭文件的更新,比如以前使用QPainter繪制,不需要額外包含QPainterPath頭文件,而5.15版本開始就需要顯示主動引入#include "qpainterpath.h"才行。
Qt6.0發布了,是個比較大的改動版本,很多基礎的類或者組件都放到單獨的源碼包中,需要自行官網下載并編譯,默認不提供集成在開發目錄下,需要手動編譯并集成,比如QRegExp,QTextCodec類,需要編譯集成后pro文件 QT += core5compat 才能用, 具體說明在https://doc.qt.io/qt-6/qtcore5-index.html。
qDebug輸出打印信息,默認會完整打印轉義字符,例如:\ " \t \n" 等,所以當你發現你明明設置了轉義字符以后打印確還是轉義前的字符,這就懵逼了,其實這是qdebug為了方便調試將各種字符都打印輸出。無可否認,很多時候,我們極其興奮的享受著Qt帶來的各種輪子各種便利,但是偶爾,稍不留意,這些便利可能也會坑你一把。要做的就是擦亮眼睛,時刻謹慎,一步一個腳印踏踏實實碼代碼。
QString s1 = R"(\:device0)";
//TNND居然輸出的是 \\:device0
qDebug() << s1;
//這次終于正確的輸出 \:device0
qDebug().noquote() << s1;
很多人有疑問為何qss對瀏覽器控件中的網頁樣式沒法控制,其實用屁股想想也知道,那玩意是html css去控制的,和Qt一毛錢關系也沒有,根本管不著,如果想要對滾動條樣式設置,可以在網頁代碼中設置樣式就行。
<style type="text/css">::-webkit-scrollbar{width:0.8em;}::-webkit-scrollbar-track{background:rgb(241,241,241);}::-webkit-scrollbar-thumb{background:rgb(188,188,188);}
</style>
Qt的ini配置文件默認不支持直接讀寫中文,需要手動設置下編碼格式才行,強烈建議統一用utf-8編碼,包括代碼文件。
//設置了編碼以后配置文件內容為 Company=上海物聯網技術研究中心
//沒有設置編碼則配置文件內容為 Company=\xe4\xb8\x8a\xe6\xb5\xb7\xe7\x89\xa9\xe8\x81\x94\xe7\xbd\x91\xe6\x8a\x80\xe6\x9c\xaf\xe7\xa0\x94\xe7\xa9\xb6\xe4\xb8\xad\xe5\xbf\x83
void App::readConfig()
{QSettings set(App::ConfigFile, QSettings::IniFormat);set.setIniCodec("utf-8");set.beginGroup("AppConfig1");App::Company = set.value("Company", App::Company).toString();set.endGroup();
}
void App::writeConfig()
{QSettings set(App::ConfigFile, QSettings::IniFormat);set.setIniCodec("utf-8");set.beginGroup("AppConfig1");set.setValue("Company", App::Company);set.endGroup();
}
用Qt做安卓開發都會遇到權限的問題,早期的安卓版本可以直接通過 AndroidManifest.xml 配置文件來添加需要的權限,這樣在安裝app的時候就會提示該app需要哪些權限讓用戶同意,現在的安卓版本都改成了動態權限,需要在app運行的時候彈出提示讓用戶確認再有權限,Qt迎合了這種策略內置了動態申請權限的方法 QtAndroid::requestPermissionsSync。
//動態設置權限
bool checkPermission(const QString &permission)
{
#ifdef Q_OS_ANDROID
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))QtAndroid::PermissionResult result = QtAndroid::checkPermission(permission);if (result == QtAndroid::PermissionResult::Denied) {QtAndroid::requestPermissionsSync(QStringList() << permission);result = QtAndroid::checkPermission(permission);if (result == QtAndroid::PermissionResult::Denied) {return false;}}
#endif
#endifreturn true;
}int main(int argc, char *argv[])
{QApplication a(argc, argv);//請求權限checkPermission("android.permission.READ_EXTERNAL_STORAGE");checkPermission("android.permission.WRITE_EXTERNAL_STORAGE"); return a.exec();
}
Qt重載qDebug輸出自定義的信息。
struct FunctionInfo {QString function;QString name;QString groupEnabled;QString action;QString group;friend QDebug operator << (QDebug debug, const FunctionInfo &functionInfo) {QString info = QString("功能: %1 名稱: %2 啟用: %3 方法: %4 分組: %5").arg(functionInfo.function).arg(functionInfo.name).arg(functionInfo.groupEnabled).arg(functionInfo.action).arg(functionInfo.group);debug << info;return debug;}
};
對高分屏不同縮放比例的自適應處理方法。
//方法1:在main函數的最前面加上下面這句 5.6版本才開始有這個函數
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//開啟高縮放支持以后圖片可能發虛還要開啟下面這個屬性QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif//方法2:在可執行文件同目錄下新建文件 qt.conf 填入下面內容
[Platforms]
WindowsArguments = dpiawareness=0//方法3:在main函數最前面設置Qt內部的環境變量
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1.5");//方法4:新版本的Qt比如Qt5.14修正了對高分屏的處理支持不是整數的縮放
qputenv("QT_ENABLE_HIGHDPI_SCALING",?"1");
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
QTabWidget選項卡有個自動生成按鈕切換選項卡的機制,有時候不想看到這個煩人的切換按鈕,可以設置usesScrollButtons為假,其實QTabWidget的usesScrollButtons屬性最終是應用到QTabWidget的QTabBar對象上,所以只要設置全局的QTabBar的這個屬性關閉即可。為啥要設置全局的呢,因為如果只是對QTabWidget設置了該屬性,而在QMainWindow窗體中QDockWidget合并自動形成的選項卡只有QTabBar對象導致依然是有切換按鈕。
//對tabWidget設置無切換按鈕
ui->tabWidget->setUsesScrollButtons(false);
//對tabBar設置無切換按鈕
ui->tabWidget->tabBar()->setUsesScrollButtons(false);
//對整個系統的選項卡設置無切換按鈕
QTabBar{qproperty-usesScrollButtons:false;}
//設置選項卡自動拉伸 這玩意居然之前自動計算來設置原來內置了哇咔咔
QTabBar{qproperty-expanding:false;}
//設置選項卡關閉按鈕可見
QTabBar{qproperty-tabsClosable:true;}
//還有其他屬性參見QTabBar頭文件有驚喜
//依舊是萬能大法所有可視化類的 Q_PROPERTY 包含的屬性都可以這樣設置
QMainWindow的分割線默認尺寸比較大,有時候想設置小一點或者不想要,最開始的時候以為是QSplitter,打印所有子元素找遍了也沒找到影子,最后發現樣式表中有對應設置的內容。
//真的是做夢也沒想到要這樣設置
QMainWindow::separator{width:1px;height:1px;margin:1px;padding:1px;background:#FF0000;}
15:141-150
QImage支持xpm圖標,查看Qt內置的QStyle風格的代碼中可以發現大量的xpm圖標定義,通過代碼的形式來產生圖標,哇咔咔好牛逼。
static const char * const imgData[] = {"15 11 6 1"," c None","+ c #979797","@ c #C9C9C9","$ c #C1C1C1","b c None","d c None"," $++++++++$ ","$+bbbbbbbb+$ ","+b $$ +$ ","+b $@ +$ ","+b +$","+b d+","+b d+$","+b $$ d+$ ","+b $@ d+$ ","$+dddddddd+$ "," $++++++++$ "};//這樣就能直接顯示一個箭頭的圖形
QImage img(imgData);
QLabel lab;
lab.setPixmap(QPixmap::fromImage(img));
lab.show();
在停靠窗體QDockWidget和QOpenGLWidget同時使用的時候,從嵌入狀態切換到浮動狀態或者浮動狀態切換到嵌入狀態,QOpenGLWidget的上下文會被打亂導致白屏失效,需要在main函數中開頭位置設置下共享OpenGL上下文。
int main(int argc, char *argv[])
{//需要設置共享上下文不然停靠窗體從正常到浮動后QOpenGLWidget窗體會失效
#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endifQApplication a(argc, argv);...
}
關于Qt中文亂碼的問題,個人也稍微總結了一點,應該可以解決99%以上的Qt版本的亂碼問題。
第一步:代碼文件選擇用utf8編碼帶bom。 第二步:在有中文漢字的代碼文件頂部加一行(一般是cpp文件) #pragma execution_character_set("utf-8") 可以考慮放在head.h中,然后需要的地方就引入head頭文件就行,而不是這行代碼寫的到處都是;這行代碼是為了告訴msvc編譯器當前代碼文件用utf8去編譯。 第三步:main函數中加入設置編碼的代碼,以便兼容Qt4,如果沒有Qt4的場景可以不用,從Qt5開始默認就是utf8編碼。
void QUIHelper::setCode()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
#if _MSC_VERQTextCodec *codec = QTextCodec::codecForName("gbk");
#elseQTextCodec *codec = QTextCodec::codecForName("utf-8");
#endifQTextCodec::setCodecForLocale(codec);QTextCodec::setCodecForCStrings(codec);QTextCodec::setCodecForTr(codec);
#elseQTextCodec *codec = QTextCodec::codecForName("utf-8");QTextCodec::setCodecForLocale(codec);
#endif
}
關于Qt眾多版本(至少幾百個)都不兼容的問題,在經過和Qt中國的林斌大神和其他大神(Qt非官方技術交流群)頭腦風暴以后,最終得出以下的結論。
Qt在二進制兼容這塊,已經做了最大的努力,通過將各種代碼細節隱藏,Q指針+D指針技巧,盡量保持了接口的統一; 是否兼容最主要考慮編譯器的因素,畢竟任何Qt版本都是需要通過編譯器編譯成對應的二進制文件,由他說了算。如果兩個Qt版本采用的編譯器版本一樣,極大概率可執行文件是兼容的,比如 Qt5.10+msvc2015 32 位 和 Qt5.11+msvc2015 32位 編譯出來的可執行文件,都用Qt5.11的庫是可行的; mingw編譯器的Qt版本也是如此,就是因為Qt官方安裝包集成的mingw編譯器一直在更新(極少附近版本沒有更新mingw編譯器版本除外),比如5.7用的mingw53,5.12用的mingw73,5.15用的mingw81,因為帶的Qt庫也是這個編譯器編譯出來的,所以導致看起來全部不兼容; 如果想要完全兼容,還有一個注意要素,那就是對應代碼使用的類的頭文件接口是否變了,按道理原有的接口極少會變,一般都是新增加,或者大版本才會改變,比如Qt4-Qt5-Qt6這種肯定沒法兼容的,接口和模塊都變了; 大膽的猜測:如果Qt5.6到Qt5.15你全部用一種編譯器比如mingw73或者msvc2015重新編譯生成對應的Qt運行庫,然后在此基礎上開發程序,最后生成的可執行文件用Qt5.15的庫是都可以的,這樣就輕松跨越了多個版本兼容; 大膽的建議:在附近的幾個版本統一編譯器,比如5.6-5.12之間就統一用mingw53或者msvc2015,5.12-5.15統一用msvc2017,要嘗鮮其他編譯器的可以自行源碼編譯其他版本,這樣最起碼附近的一大段版本(大概2-3年的版本周期)默認就兼容了。 本人測試的是widget部分,qml未做測試,不清楚是否機制一樣;
通過酷碼大哥(Qt開發者交流群)的指點,到今天才知道,Qt設置樣式表支持直接傳入樣式表文件路徑,親測4.7到5.15任意版本,通過查看對應函數的源碼可以看到內部會檢查是否是 'file:///' 開頭,是的話則自動讀取樣式表文件進行設置,無需手動讀取。
//以前都是下面的方法
QFile file(":/qss/psblack.css");
if (file.open(QFile::ReadOnly)) {QString qss = QLatin1String(file.readAll());qApp->setStyleSheet(qss);file.close();
}//其實一行代碼就行
qApp->setStyleSheet("file:///:/qss/psblack.css");
//特別說明,只支持qApp->setStyleSheet 不支持其他比如widget->setStyleSheet
Qt中自帶的很多控件,其實都是由一堆基礎控件(QLabel、QPushButton等)組成的,比如日歷面板 QCalendarWidget 就是 QToolButton+QSpinBox+QTableView 等組成,妙用 findChildren 可以拿到父類對應的子控件集合,可以直接對封裝的控件中的子控件進行樣式的設置,其他參數的設置比如設置中文文本(默認可能是英文)等。
//打印子類類名集合
void printObjectChild(const QObject *obj, int spaceCount)
{qDebug() << QString("%1%2 : %3").arg("", spaceCount).arg(obj->metaObject()->className()).arg(obj->objectName());QObjectList childs = obj->children();foreach (QObject *child, childs) {printObjectChild(child, spaceCount + 2);}
}//拿到對話框進行設置和美化
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
QLabel *lookinLabel = fileDialog->findChild<QLabel*>("lookInLabel");
lookinLabel->setText(QString::fromLocal8Bit("文件目錄:"));
lookinLabel->setStyleSheet("color:red;");//設置日期框默認值為空
QLineEdit *edit = ui->dateEdit->findChild<QLineEdit *>("qt_spinbox_lineedit");
if (!edit->text().isEmpty()) {edit->clear();
}
Qt內置了各種對話框,比如文件對話框-QFileDialog ,顏色對話框-QColorDialog ,默認都會采用系統的對話框風格樣式,這樣可以保持和系統一致,如果不需要的話可以取消該特性,取消以后會采用Qt自身的對話框,這樣才能進行美化和其他處理。
QFileDialog *fileDialog = new QFileDialog(this);
//不設置此屬性根本查找不到任何子元素,因為默認采用的系統對話框
fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
qDebug() << fileDialog->findChildren<QLabel *>();
//打印輸出 QLabel(0x17e2ff68, name="lookInLabel"), QLabel(0x17e35f88, name="fileNameLabel"), QLabel(0x17e35e68, name="fileTypeLabel")
QtCreator集成開發環境,也內置了對快速添加注釋的支持,比如最常用的在頭文件開頭添加一大段通用模板的注釋,標注文件創建者、時間等信息。
菜單->工具->選項->文本編輯器->右側tab頁面片段(snippets); 組選擇C++, 可以看到這里面已經內置了不少定義比如foreach,可以依葫蘆畫瓢; 添加一個片段, 比如名字是fun, 觸發種類是這個片段的簡單描述; 當我們在代碼文件中鍵入fun時, 會自動彈出智能提醒, 選擇我們的代碼片段回車, 自動填充代碼; 按tab可以在變量間切換, 輸入完成后回車, 完成編輯;
/*** @brief $name$* @param $param$* @author feiyangqingyun* @date $date$*/
$ret$ $name$($param$)
{$$
}
Qt5時代對信號槽運行機制據說有了很大的改進。
在Qt5之前,connect一般都只能這么寫connect(sender, SIGNAL(signalFunc()), receiver, SLOT(receiveFunc())),就是說在connect的時候,必須把信號用宏SIGNAL包裹起來,把槽函數用宏SLOT包裹起來,這樣才能被Qt的Moc機制識別; 在編譯的時候即使信號或槽不存在或者參數不正確也不會報錯,但是在執行的時候無效,會打印提示,對于C++這種靜態語言來說,這是不友好的,不利于調試; 但是Qt5之后更加推薦"取地址的寫法",采用這種寫法,如果編譯的時候信號或槽不存在是無法編譯通過的,相當于編譯時檢查,不容易出錯; 如果沒有歷史遺留問題需要兼容Qt4的話,還是推薦用新寫法,有類型檢查更嚴格,而且支持的寫法多樣非常靈活; 一些簡單的處理邏輯強烈推薦直接lambda表達式直接處理完;
class MainWindow : public QMainWindow
{Q_OBJECTpublic: MainWindow(QWidget *parent = 0);~MainWindow();private:Ui::MainWindow *ui;private:void test_fun();private slots:void test_slot();
};MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//早期寫法,通用Qt所有版本,只支持定義了slots關鍵字的函數//connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(test_fun()));connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(test_slot()));//新寫法,支持Qt5及后期所有版本,支持所有函數,無需定義slots關鍵字也行connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::test_fun);connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::test_slot);//另類寫法,支持lambda表達式,直接執行代碼connect(ui->pushButton, &QPushButton::clicked, [this] {test_fun();});connect(ui->pushButton, &QPushButton::clicked, [this] {qDebug() << "hello lambda";});//lambda帶參數connect(ui->pushButton, &QPushButton::clicked, [&] (bool isCheck) {qDebug() << "hello lambda" << isCheck;});//頭文件 signals:void sig_test(int i);connect(this, &MainWindow::sig_test, [] (int i) {qDebug() << "hello lambda" << i;});emit sig_test(5);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::test_fun()
{qDebug() << "test_fun";
}void MainWindow::test_slot()
{qDebug() << "test_slot";
}
Qt樣式表有多種運行機制,主要是考慮到各種需求場景,繼承自QWidget的類和qApp類都支持setStyleSheet方法,還可以統一將樣式表放在文件,或者將樣式文件加入到資源文件。
斗氣:qss內容寫得到處都是,哪里需要就寫在哪里,各種控件調用 setStyleSheet方法傳入樣式表內容,或者直接對應控件鼠標右鍵彈出菜單選擇改變樣式表填入內容; 斗者:qss內容放在文件,讀取文件內容設置樣式表,程序發布的時候帶上qss文件; 斗師:qss文件作為資源文件放到qrc文件,直接編譯到可執行文件中,防止篡改; 斗靈:在qss文件中自定義一些標志充當變量使用,讀取以后替換對應的變量為顏色值,類似動態換膚; 斗王:放在文件容易被篡改,集成到可執行文件不夠靈活,一旦樣式表更新需要重新編譯文件,如何做到既能只更新樣式表文件,又不需要重新編譯可執行文件,又能防止被篡改:采用rcc命令將資源文件編譯生成二進制,只需要替換該二進制文件即可; 斗皇:繼承qstyle類自己實現完成所有樣式接口,統一整體風格,大名鼎鼎的UOS系統默認規則就是如此,不允許用樣式表,全部painter繪制;
16:151-160
當Qt中編譯資源文件太大時,效率很低,或者需要修改資源文件中的文件比如圖片、樣式表等,需要重新編譯可執行文件,這樣很不友好,當然Qt都給我們考慮好了策略,此時可以將資源文件轉化為二進制的rcc文件,這樣就將資源文件單獨出來了,可在需要的時候動態加載。
//Qt中使用二進制資源文件方法如下
//將qrc編譯為二進制文件rcc,在控制臺執行下列命令
rcc -binary main.qrc -o main.rcc
//在應用程序中注冊資源,一般在main函數啟動后就注冊
QResource::registerResource(qApp->applicationDirPath() + "/main.rcc");
關于設置字體,大概都會經歷一個誤區,本來是打算設置整個窗體包括子控件的字體大小的,結果發現只有主窗體自己應用了字體而子控件沒有。
//假設窗體中有子控件,默認字體12px,父類類型是QWidget,父類類名是Widget//下面幾種方法只會設置主窗體的字體,子控件不會應用,需要按個調用setFont
QFont font;
font.setPixelSize(20);
this->setFont(font);
this->setStyleSheet("{font:26px;}");
this->setStyleSheet("QWidget{font:26px;}");
this->setStyleSheet("Widget{font:26px;}");//下面才是通過樣式表設置整個控件+子控件的字體
this->setStyleSheet("font:26px;");
this->setStyleSheet("*{font:26px;}");
this->setStyleSheet("QWidget>*{font:26px;}");
this->setStyleSheet("Widget>*{font:26px;}");//下面設置全局字體
qApp->setFont(font);
Qt中封裝的QImage異常的強大,提供了各種圖片格式的轉換,還可以對每個像素的顏色值進行替換,有時候我們需要將單色的圖片換成另外一種顏色,要注意的是如果帶有透明值的顏色需要進行格式轉化,比如轉成Format_ARGB32或者Format_RGBA8888。
//pixel 函數獲取像素點的顏色 setPixel 函數設置像素點的顏色 此函數任意Qt版本都有
//pixelColor 函數獲取像素點的顏色 setPixelColor 函數設置像素點的顏色 此函數Qt5.6以后才有
//pixel函數取出來的是QRgb格式需要用 qRed qGreen qBlue qAlpha 進行轉換
QImage image("1.png");
image = image.convertToFormat(QImage::Format_ARGB32);
int width = image.width();
int height = image.height();
//遍歷圖像的每一個像素
for (int x = 0; x < width; ++x) {for (int y = 0; y < height; ++y) {QString name = image.pixelColor(x, y).name();//將白色以外的顏色全部替換成紅色if (name != "#ffffff") {image.setPixelColor(x, y, Qt::red);}}
}//保存文件
image.save("2.png");
在數據庫相關的應用中,如果僅僅是單機版本,沒有特別的需要(比如領導指定,或者需要遠程存放數據),強烈建議使用sqlite數據庫,這是本人經過無數次的對比測試和N個商業項目應用得出的結論。
Qt天生內置了sqlite數據庫,只需要發布的時候帶上插件就行(可以看到插件動態庫文件比其他幾種都要大,那是因為直接將數據庫的源碼都編譯進去了,而其他只編譯了中間通信交互的插件源碼),其他數據庫要么還要帶上動態庫,要么還需要創建數據源; 速度上,絕對無與倫比的出類拔萃,同樣的數據庫結構(表結構、索引等完全一致),查詢速度和批量更新速度、數據庫事務等,速度都是其他幾種的至少3倍以上,而且隨著數據量的增大對比越發明顯; 幾千萬的數據量完全沒問題,而且速度和性能都還可以,不要以訛傳訛網上部分菜雞說的不支持百萬以上的數據量,本人親測億級別,數據量建議千萬級別以下,著重注意數據庫表和索引的設計; 其他數據庫還要注意版本的區別,ODBC數據源形式還容易出錯和執行失敗; sqlite數據庫也有幾個重大缺點:不支持加密,不支持網絡訪問,不支持部分數據庫高級特性,不支持海量數據(億級別以上),但是對于絕大部分Qt項目還是足夠; 數據庫支持友好度大致是 sqlite > postgresql > mysql > odbc ; 以上都是在Qt環境中個人測試得出的結論,結果未必正確,作為參考即可,其他編程環境比如C#、JAVA請忽略,也許差別可能在中間通信的效率造成的;
Qt5.10以后提供了新的類 QRandomGenerator QRandomGenerator64 管理隨機數,使用更方便,尤其是取某個區間的隨機數。
//早期處理辦法 先初始化隨機數種子然后取隨機數
qsrand(QTime::currentTime().msec());
//取 0-10 之間的隨機數
qrand() % 10;
//取 0-1 之間的浮點數
qrand() / double(RAND_MAX);//新版處理辦法 支持5.10以后的所有版本包括qt6
QRandomGenerator::global()->bounded(10); //生成一個0和10之間的整數
QRandomGenerator::global()->bounded(10.123); //生成一個0和10.123之間的浮點數
QRandomGenerator::global()->bounded(10, 15); //生成一個10和15之間的整數//兼容qt4-qt6及以后所有版本的方法 就是用標準c++的隨機數函數
srand(QTime::currentTime().msec());
rand() % 10;
rand() / double(RAND_MAX);//通用公式 a是起始值,n是整數的范圍
int value = a + rand() % n;
//(min, max)的隨機數
int value = min + 1 + (rand() % (max - min - 1));
//(min, max]的隨機數
int value = min + 1 + (rand() % (max - min + 0));
//[min, max)的隨機數
int value = min + 0 + (rand() % (max - min + 0));
//[min, max]的隨機數
int value = min + 0 + (rand() % (max - min + 1));//如果在線程中取隨機數,線程啟動的時間幾乎一樣,很可能出現取到的隨機數一樣的問題,就算設置隨機數為當前時間啥的也沒用,電腦太快很可能還是一樣的時間,同一個毫秒。
//取巧辦法就是在run函數之前最前面將當前線程的id作為種子設置。時間不可靠,線程的id才是唯一的。
//切記 void * 轉換到數值必須用 long long,在32位是可以int但是在64位必須long,確保萬一直接用quint64最大
srand((long long)currentThreadId());
qrand((long long)currentThreadId());
Qt的UI界面在resize以后有個BUG,懸停樣式沒有取消掉,需要主動模擬鼠標動一下。
void frmMain::on_btnMenu_Max_clicked()
{......//最大化以后有個BUG,懸停樣式沒有取消掉,需要主動模擬鼠標動一下 QEvent event(QEvent::Leave);QApplication::sendEvent(ui->btnMenu_Max, &event);
}
項目中啟用c++11語法支持。
greaterThan(QT_MAJOR_VERSION, 4): CONFIG += c++11
lessThan(QT_MAJOR_VERSION, 5): QMAKE_CXXFLAGS += -std=c++11
Qt的文本控件比如QTextEdit默認加載大文本比如10MB的文本,很容易卡死甚至崩潰,那是因為默認一個屬性開啟了,需要屏蔽掉就好很多。
ui->textEdit->setUndoRedoEnabled(false);
其他幾點常規小經驗,本人在這幾個地方摔跤過很多次。
有返回值的函數,一定要主動return返回值,有部分編譯器在沒有返回值的情況下也能正常編譯通過,但是運行的時候會出問題,得不到想要的結果,因為沒有return對應的值。 定義的局部變量,主動給定個初始值,是個必須養成的好習慣,不然編譯器給的初始值很可能不是你想要的,比如int變量默認0,有時候隨機變成一個很大的數值,bool變量的初始值不同編譯器不同值,有些是true有些是false,主動給一個初始值更可靠。 某些函數參數很多,而且后期可能還會修改和增加,這就導致了源頭修改以后,關聯信號槽的地方也要修改,參數類型和位置必須保持完全一致,對應槽函數處理也要修改等,改動的工作量非常大而且極不友好,所以對于非固定參數的函數,建議用結構體,這樣非常容易增加其他的參數,而且不用修改信號槽關聯和信號槽函數定義等,比如學生信息表、商品信息表作為參數傳輸,最佳方案就是結構體。
QTabWidget選項卡控件,生成的tabbar選項卡寬度是按照文本自動設置的,文本越長選項卡的寬度越大,很多時候,我們需要的是一樣的寬度或者等分填充,
//方法1:字符串空格填充
ui->tabWidget->addTab(httpClient1, "測 試");
ui->tabWidget->addTab(httpClient1, "人員管理");
ui->tabWidget->addTab(httpClient1, "系統設置");//方法2:識別尺寸改變事件自動設置最小寬度
void MainWindow::resizeEvent(QResizeEvent *e)
{int count = ui->tabWidget->tabBar()->count();int width = this->width() - 30;QString qss = QString("QTabBar::tab{min-width:%1px;}").arg(width / count);this->setStyleSheet(qss);
}//方法3:設置全局樣式,不同選項卡個數的設置不同的寬度
QStringList list;
list << QString("QTabWidget[tabCount=\"2\"]>QTabBar::tab{min-width:%1px;}").arg(100);
list << QString("QTabWidget[tabCount=\"3\"]>QTabBar::tab{min-width:%1px;}").arg(70);
qApp->setStyleSheet(list.join(""));
//設置了tabCount弱屬性自動去找對應的寬度設置
ui->tabWidget->setProperty("tabCount", 2);
ui->tabWidget->setProperty("tabCount", 3);//方法4:強烈推薦-》使用內置的方法 setExpanding setDocumentMode 兩個屬性都必須設置
//Qt4的tabBar()是propected的,所以建議還是通過樣式表設置
ui->tabWidget->tabBar()->setDocumentMode(true);
ui->tabWidget->tabBar()->setExpanding(true);
//樣式表一步到位不用每個都單獨設置
QString("QTabBar{qproperty-usesScrollButtons:false;qproperty-documentMode:true;qproperty-expanding:true;}");
//在5.9以前開啟這個設置后,貌似選項卡個數按照真實個數+1計算寬度,也就是永遠會留空一個tab的占位。
//5.9以后貌似修復了這個BUG,按照理想中的拉伸填充等分設置tab的寬度。
17:161-170
經常有人說Qt垃圾,說用Qt在1毫秒繪制幾千個數據點卡成屎。其實顯示器最高刷新頻率一般才60幀,1毫秒就繪制一次有意義嗎?不僅顯示器沒刷新過來,人肉眼也看不過來(有人可能又要抬杠說這是老板要求的,顯示歸顯示,至于人看不看那是另外一回事,我想說的是顯示不就是給人看的嗎?給程序看可以直接后臺繪制圖片讓程序識別啊沒必要顯示的),程序中要做的應該是盡量降低程序的繪制刷新頻率到顯示器的頻率(其實一秒鐘30幀都足夠),一次搞多一點的數據一次性繪制(數據量很大還可以考慮重采樣,比如平均值法等,畢竟要考慮顯示器的分辨率就那么大,搞個幾十萬的數據點擠一塊沒啥意思,可以將一整塊區域內的數據點換成一個點),而不是繪制多次,盡管兩種辦法都可以將收到的數據繪制完成,但是效率相差的不是一點點,信號也是如此,不建議太頻繁的發送信號,Qt內部1秒鐘處理信號的個數也是有限制的,太頻繁高并發的信號,很可能會丟失或者合并一部分,比如網絡請求接收到的學生信息表,應該是在該應答數據內的所有學生信息解析完一次性發送,而不是解析一條發送一條。
Qt提供了N種窗體屬性比如無邊框屬性FramelessWindowHint、不在任務欄顯示屬性Tool等,有時候我們需要對窗口的屬性進行動態設置,比如增加一個屬性或者移除一個屬性,Qt5.9以前需要拿到原有的窗體屬性做運算,后面可以用新的方法。
//增加一個無邊框屬性
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
//移除無邊框屬性
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);//下面是5.9以后新增的方法
//增加一個無邊框屬性到窗體屬性鏈表
setWindowFlag(Qt::FramelessWindowHint, true);
//從窗體屬性鏈表中移除無邊框屬性
setWindowFlag(Qt::FramelessWindowHint, false);
如果對窗體設置了固定尺寸,窗體會變得大小不可拉伸,如果需要重新還原可拉伸,必須重新設置最小尺寸和最大尺寸。
setMinimumSize(0, 0);
setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
Qt內置了很多全局的對象參數可以直接獲取,這樣在使用的時候方便的不要不要的,比如判斷當前鼠標左鍵還是右鍵可以直接用qApp->mouseButtons(),全局的鼠標坐標可以用QCursor::pos()。
//在鼠標右鍵的地方彈出菜單,如果菜單是QMenu而不是QAction則只能通過下面的方式彈出
if (qApp->mouseButtons() == Qt::RightButton) {videoMenu->exec(QCursor::pos());
}//全局剪切板
qApp->clipboard();
//頂層控件對象集合
qApp->topLevelWidgets()
//當前焦點所在控件
qApp->focusWidget()
//當前平臺名稱
qApp->platformName()
//調用系統蜂鳴器
qApp->beep()
//打印當前Qt版本信息
qApp->aboutQt()
//設置全局的鼠標樣式
qApp->setOverrideCursor()
//不使用系統的標準顏色字體等
QGuiApplication::setDesktopSettingsAware(bool on);
QApplication app(argc, argv);//更多的全局對象屬性等可以查閱 qguiapplication.h 頭文件,你會發現新大陸。
Qt對區分不同的編譯器也做了非常細致的處理。
#pro文件可以這樣判斷
msvc{}//代碼中可以這樣判斷
#ifdef Q_CC_MINGW
//mingw編譯器
#elif Q_CC_MSVC
//msvc編譯器
#endif//判斷編譯器和編譯器版本
#if defined Q_CC_MSVC && _MSC_VER < 1300
#if defined(Q_CC_GNU) && (__GNUC__ < 4)//代碼中判斷ARM平臺
#ifdef QT_ARCH_ARM
//多個條件判斷
#if defined(QT_ARCH_ARM) || defined(QT_ARCH_WINDOWSCE)
有時候需要暫時停止某個控件發射信號(比如下拉框combobox添加數據的時候會觸發當前元素改變信號),有多種處理,推薦用 blockSignals 方法。
//方法1:先 disconnect 掉信號,處理好以后再 connect 信號,缺點很明顯,很傻,如果信號很多,每個型號都要這么來一次。
disconnect(ui->cbox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cbox_currentIndexChanged(int)));
for (int i = 0; i <= 100; i++) {ui->cbox->addItem(QString::number(i));
}
connect(ui->cbox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cbox_currentIndexChanged(int)));//方法2:先調用 blockSignals(true) 阻塞信號,處理號以后再調用 blockSignals(false) 恢復所有信號。
//如果需要指定某個信號進行斷開那就只能用 disconnect 來處理。
ui->cbox->blockSignals(true);
for (int i = 0; i <= 100; i++) {ui->cbox->addItem(QString::number(i));
}
ui->cbox->blockSignals(false);
項目代碼文件數量如果很多的話,全部包含在pro項目文件中會顯得非常凌亂,甚至滾動條都要拉好久,有兩個方法可以處理的更好,推薦方法2。
//方法1:pro文件直接全部引入,而不是每個都添加一次,省心省力。
HEADERS += *.h
SOURCES += *.cpp//方法2:分模塊文件夾存放,不同模塊用pri包含代碼文件,比如界面可以放在ui文件夾,下面搞個ui.pri,然后pro項目文件只需要引入這個pri文件即可。
include($$PWD/ui/ui.pri)
//還可以加上一句包含路徑這樣可以省去在使用代碼的時候不用寫文件夾
INCLUDEPATH += $$PWD/ui
//加上上面這行,在使用頭文件的時候可以直接 include "form.h",沒有加則需要 include "ui/form.h"。
在網絡通信中,無論是tcp客戶端還是udp客戶端,其實都是可以綁定網卡IP和端口的,很多人只知道服務端可以指定網卡監聽端口。客戶端如果沒有綁定通信端口則由客戶端所在的操作系統隨機遞增分配的,這里為啥這么強調,因為無數人,甚至不乏一些多年經驗的新時代農名工,以為客戶端的端口是服務端分配的,因為他們看到在服務端建立連接后可以打印出不同的端口號。網絡通信的雙方自己決定自己要用什么端口,服務器端只能決定自己監聽的是哪個端口,不能決定客戶端的端口,同理客戶端也只能決定自己的端口。端口隨機分配一般是按照順序遞增的,比如先是45110端口,連接重新建立就用45111端口,只要端口沒被占用就這樣遞增下去,所以很多人會問是否可以復用一些端口,不然端口一直這樣頻繁的分配下去不妥,甚至有些特定的場景和需求也是會要求客戶端綁定網卡和端口來和服務器通信的。
//tcp客戶端
QTcpSocket *socket = new QTcpSocket(this);
//斷開所有連接和操作
socket->abort();
//綁定網卡和端口
socket->bind(QHostAddress("192.168.1.2"), 6005);
//連接服務器
socket->connectToHost("192.168.1.3", 6000);//打印通信用的本地綁定地址和端口
qDebug() << socket->localAddress() << socket->localPort();
//打印通信服務器對方的地址和端口
qDebug() << socket->peerAddress() << socket->peerPort() << socket->peerName();//udp客戶端
QUdpSocket *socket = new QUdpSocket(this);
//綁定網卡和端口,沒有綁定過才需要綁定
//采用端口是否一樣來判斷是為了方便可以直接動態綁定切換端口
if (socket->localPort() != 6005) {socket->abort();socket->bind(QHostAddress("192.168.1.2"), 6005);
}
//指定地址和端口發送數據
socket->writeDatagram(buffer, QHostAddress("192.168.1.3"), 6000);//上面是Qt5可以使用bind,Qt4中的QTcpSocket的對應接口是protected的沒法直接使用,需要繼承類重新實現把接口放出來。
//Qt4中的QUdpSocket有bind函數是開放的,奇怪了,為何Qt4中獨獨QTcpSocket不開放。
TcpSocket *socket = new TcpSocket(this);
socket->setLocalAddress(QHostAddress("192.168.1.2"));
socket->setLocalPort(6005);
關于網絡通信,tcp和udp是兩種不同的底層的網絡通信協議,兩者監聽和通信的端口互不相干的,不同的協議或者不同的網卡IP地址可以用相同的端口。之前有個人說他的電腦居然可以監聽一樣的端口進行通信,顛覆了他以前的認知,書上說的明明是不可以相同端口的,后面遠程一看原來選擇的不同的網卡IP地址,當然可以的咯。
tcp對網卡1監聽了端口6000,還可以對網卡2監聽端口6000。 tcp對網卡1監聽了端口6000,udp對網卡1還可以繼續監聽端口6000。 tcp對網卡1監聽了端口6000,在網卡1上其他tcp只能監聽6000以外的端口。 udp協議也是上面的邏輯。
開源的圖表控件QCustomPlot很經典,在曲線數據展示這塊性能彪悍,總結了一些容易忽略的經驗要點。
可以將XY軸對調,然后形成橫向的效果,無論是曲線圖還是柱狀圖,分組圖、堆積圖等,都支持這個特性。 不需要的提示圖例可以調用 legend->removeItem 進行移除。 兩條曲線可以調用 setChannelFillGraph 設置合并為一個面積區域。 可以關閉抗鋸齒 setAntialiased 加快繪制速度。 可以設置不同的線條樣式(setLineStyle)、數據樣式(setScatterStyle)。 坐標軸的箭頭樣式可更換 setUpperEnding。 可以用 QCPBarsGroup 實現柱狀分組圖,這個類在官方demo中沒有,所以非常容易忽略。
//對調XY軸,在最前面設置
QCPAxis *yAxis = customPlot->yAxis;
QCPAxis *xAxis = customPlot->xAxis;
customPlot->xAxis = yAxis;
customPlot->yAxis = xAxis;//移除圖例
customPlot->legend->removeItem(1);//合并兩個曲線畫布形成封閉區域
customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));//關閉抗鋸齒以及設置拖動的時候不啟用抗鋸齒
customPlot->graph()->setAntialiased(false);
customPlot->setNoAntialiasingOnDrag(true);//多種設置數據的方法
customPlot->graph(0)->setData();
customPlot->graph(0)->data()->set();//設置不同的線條樣式、數據樣式
customPlot->graph()->setLineStyle(QCPGraph::lsLine);
customPlot->graph()->setScatterStyle(QCPScatterStyle::ssDot);
customPlot->graph()->setScatterStyle(QCPScatterStyle(shapes.at(i), 10));//還可以設置為圖片或者自定義形狀
customPlot->graph()->setScatterStyle(QCPScatterStyle(QPixmap("./sun.png")));
QPainterPath customScatterPath;
for (int i = 0; i < 3; ++i) {customScatterPath.cubicTo(qCos(2 * M_PI * i / 3.0) * 9, qSin(2 * M_PI * i / 3.0) * 9, qCos(2 * M_PI * (i + 0.9) / 3.0) * 9, qSin(2 * M_PI * (i + 0.9) / 3.0) * 9, 0, 0);
}
customPlot->graph()->setScatterStyle(QCPScatterStyle(customScatterPath, QPen(Qt::black, 0), QColor(40, 70, 255, 50), 10));//更換坐標軸的箭頭樣式
customPlot->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
customPlot->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);//設置背景圖片
customPlot->axisRect()->setBackground(QPixmap("./solarpanels.jpg"));
//畫布也可以設置背景圖片
customPlot->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg")));
//整體可以設置填充顏色或者圖片
customPlot->setBackground(QBrush(gradient));
//設置零點線條顏色
customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen);
//控制是否鼠標滾輪縮放拖動等交互形式
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);//柱狀分組圖
QCPBarsGroup *group = new QCPBarsGroup(customPlot);
QList<QCPBars*> bars;
bars << fossil << nuclear << regen;
foreach (QCPBars *bar, bars) {//設置柱狀圖的寬度大小bar->setWidth(bar->width() / bars.size());group->append(bar);
}
//設置分組之間的間隔
group->setSpacing(2);
18:171-180
在Qt編程中經常會遇到編碼的問題,由于跨平臺的考慮兼容各種系統,而windows系統默認是gbk或者gb2312編碼,當然后期可能msvc編譯器都支持utf8編碼,所以在部分程序中傳入中文目錄文件名稱的時候會發現失敗,因為可能對應的接口用了早期的fopen函數而不是fopen_s函數,比如fmod中也是這個情況。這個時候就需要轉碼處理。
QString fileName = "c:/測試目錄/1.txt";
//如果應用程序main函數中沒有設置編碼則默認采用系統的編碼,可以直接通過toLocal8Bit轉成正確的數據
const char *name = fileName.toLocal8Bit().constData();//如果設置過了下面兩句則需要主動轉碼
QTextCodec *codec = QTextCodec::codecForName("utf-8");
QTextCodec::setCodecForLocale(codec);QTextCodec *code = QTextCodec::codecForName("gbk");
const char *name = code->fromUnicode(fileName).constData();//推薦方式2以防萬一保證絕對的正確,哪怕是設置過主程序的編碼
//切記一旦設置過QTextCodec::setCodecForLocale會影響toLocal8Bit//有時候可能還有下面這種情況
#ifdef Q_OS_WIN
#if defined(_MSC_VER) && (_MSC_VER >= 1400)QTextCodec *code = QTextCodec::codecForName("utf-8");
#elseQTextCodec *code = QTextCodec::codecForName("gbk");
#endifconst char *name = code->fromUnicode(fileName).constData();
#elseconst char *name = fileName.toUtf8().constData();
#endif
在查閱和學習Qt源碼的過程中,發現了一些趨勢和改變。
數據類型這塊盡量用Qt內部的數據類型,哪怕是重定義過的比如quint8其實unsigned char,qreal就是double,以前翻看源碼的時候可能還有些是double,現在慢慢改成了qreal。 循環結構用 for(;;) 替代 while(1),因為轉成匯編指令后 for(;;) 只有一條指令而 while(1) 確有4條,指令少不占用寄存器而且不用跳轉,理論上速度要更快。 其實Qt中就重定義了 forever 關鍵字表示 for(;;) ,我的乖乖,想的真周到。 自動c++11以及后續的標準都支持auto萬能數據類型,發現Qt的源碼中也慢慢的改成了auto,這樣加快了編寫代碼的效率,不用自己去指定數據類型而是讓編譯器自己推導數據類型。而且其實也不影響編譯器編譯的速度,因為無論指定和沒有指定數據類型,編譯器都要推導右側的數據類型進行判斷。不過有個缺點就是影響了閱讀代碼的成本,很多時候需要自己去理解推導。
Qt中設置或者打開加載本地文件需要用到QUrl類,本地文件建議加上 file:/// 前綴。
QString url = "file:///c:/1.html";
//瀏覽器控件打開本地網頁文件
webView->setUrl(QUrl(url));
//打開本地網頁文件,下面兩種方法都可以
QDesktopServices::openUrl(QUrl::fromLocalFile(url));
QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode));
在網絡請求中經常涉及到超時時間的問題,因為默認是30秒鐘,一旦遇到網絡故障的時候要等好久才能反應過來,所以需要主動設置下超時時間,超過了就直接中斷結束請求。從Qt5.15開始內置了setTransferTimeout來設置超時時間,非常好用。
//局部的事件循環,不卡主界面
QEventLoop eventLoop;//設置超時 5.15開始自帶了超時時間函數 默認30秒
#if (QT_VERSION >= QT_VERSION_CHECK(5,15,0))
manager->setTransferTimeout(timeout);
#else
QTimer timer;
connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
timer.setSingleShot(true);
timer.start(timeout);
#endifQNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
eventLoop.exec();if (reply->bytesAvailable() > 0 && reply->error() == QNetworkReply::NoError) {//讀取所有數據保存成文件QByteArray data = reply->readAll();QFile file(dirName + fileName);if (file.open(QFile::WriteOnly | QFile::Truncate)) {file.write(data);file.close();}
}
Qt中基本上有三大類型的項目,控制臺項目對應QCoreApplication、傳統QWidget界面程序對應QApplication、quick/qml項目程序對應QGuiApplication。有很多屬性的開啟需要在main函數的最前面執行才有效果,比如開啟高分屏支持、設置opengl模式等。不同類型的項目需要對應的QApplication。
//如果是控制臺程序則下面的QApplication換成QCoreApplication
//如果是quick/qml程序則下面的QApplication換成QGuiApplication
int main(int argc, char *argv[])
{//可以用下面這行測試Qt自帶的輸入法 qtvirtualkeyboardqputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));//設置不應用操作系統設置比如字體QApplication::setDesktopSettingsAware(false);#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))//設置高分屏縮放舍入策略QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))//設置啟用高分屏縮放支持//要注意開啟后計算到的控件或界面寬度高度可能都不對,全部需要用縮放比例運算下QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//設置啟用高分屏圖片支持QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))//設置opengl模式 AA_UseDesktopOpenGL(默認) AA_UseSoftwareOpenGL AA_UseOpenGLES//QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);//設置opengl共享上下文QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endifQApplication a(argc, argv);QWidget w;w.show();return a.exec();
}
QCamera中獲取設備的配置參數比如支持的分辨率集合等,需要先調用load后才能正確獲取,或者關聯stateChanged信號中判斷狀態是否是ActiveState,然后再讀取。
//方法1:調用load后獲取
camera = new QCamera(this);
//先需要載入才能獲取到對應參數
camera->load();
//輸出當前設備支持的分辨率
QList<QSize> sizes = camera->supportedViewfinderResolutions();
emit resolutions(sizes);
//重新設置分辨率
QCameraViewfinderSettings set;
set.setResolution(cameraWidth, cameraHeight);
camera->setViewfinderSettings(set);
//獲取完成后卸載
camera->unload();//方法2:通過事件信號獲取
camera = new QCamera(this);
connect(camera, SIGNAL(stateChanged(QCamera::State)), this, SLOT(stateChanged(QCamera::State)));
void CameraThread::stateChanged(QCamera::State state)
{if (state == QCamera::ActiveState) {//輸出當前設備支持的分辨率QList<QSize> sizes = camera->supportedViewfinderResolutions();emit resolutions(sizes);//重新設置分辨率QCameraViewfinderSettings set;set.setResolution(cameraWidth, cameraHeight);camera->setViewfinderSettings(set);}
}//QCamera沒有指定設備名稱的時候則采用默認的攝像機
camera = new QCamera(this);
//cameraName = @device:pnp:\\\\?\\usb#vid_046d&pid_0825&mi_00#6&212eebd3&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\\global
//可以通過設備描述符來查找設備名稱(唯一標識)
camera = new QCamera(cameraName.toUtf8(), this);
很多時候需要在窗體首次顯示的時候加載一些東西,而且只加載一次,當窗體再次顯示的時候不加載。為什么不是在構造函數呢?因為很多玩意都是要在顯示后才能確定,比如控件的尺寸,部分樣式表的應用。
void Widget::showEvent(QShowEvent *)
{static bool isLoad = false;if (!isLoad) {isLoad = true;//執行對應的處理}
}
Qt獲取當前所用的Qt版本、編譯器、位數等信息。
//詳細的Qt版本+編譯器+位數
QString compilerString = "<unknown>";
{
#if defined(Q_CC_CLANG)QString isAppleString;
#if defined(__apple_build_version__)isAppleString = QLatin1String(" (Apple)");
#endifcompilerString = QLatin1String("Clang ") + QString::number(__clang_major__) + QLatin1Char('.') + QString::number(__clang_minor__) + isAppleString;
#elif defined(Q_CC_GNU)compilerString = QLatin1String("GCC ") + QLatin1String(__VERSION__);
#elif defined(Q_CC_MSVC)if (_MSC_VER > 1999) {compilerString = QLatin1String("MSVC <unknown>");} else if (_MSC_VER >= 1920) {compilerString = QLatin1String("MSVC 2019");} else if (_MSC_VER >= 1910) {compilerString = QLatin1String("MSVC 2017");} else if (_MSC_VER >= 1900) {compilerString = QLatin1String("MSVC 2015");} else if (_MSC_VER >= 1800) {compilerString = QLatin1String("MSVC 2013");} else if (_MSC_VER >= 1700) {compilerString = QLatin1String("MSVC 2012");} else if (_MSC_VER >= 1600) {compilerString = QLatin1String("MSVC 2010");} else {compilerString = QLatin1String("MSVC <old>");}
#endif
}//拓展知識 查看 QSysInfo 類下面有很多好東西
// qVersion() = QT_VERSION_STR
QString version = QString("%1 %2 %3").arg(qVersion()).arg(compilerString).arg(QString::number(QSysInfo::WordSize));
QDateTime可以直接格式化輸出星期幾周幾,Qt6默認按照英文輸出比如 ddd = 周二 Tue dddd = 星期二 Tuesday ,此時如果只想永遠是中文就需要用到QLocale進行轉換。
//格式化輸出受到本地操作系統語言的影響//英文操作系統
//這樣獲取到的是Mon到Sun,英文星期的3個字母的縮寫。
QDateTime::currentDateTime().toString("ddd");
//這樣獲取到的是Monday到Sunday,英文星期完整單詞。
QDateTime::currentDateTime().toString("dddd");//中文操作系統
//這樣獲取到的是周一到周日。
QDateTime::currentDateTime().toString("ddd");
//這樣獲取到的是星期一到星期日。
QDateTime::currentDateTime().toString("dddd");//主動指定語言轉換
//如果沒有指定本地語言則默認采用系統的語言環境。
QLocale locale;
//QLocale locale = QLocale::Chinese;
//QLocale locale = QLocale::English;
//QLocale locale = QLocale::Japanese;//下面永遠輸出中文的周一到周日
locale.toString(QDateTime::currentDateTime(), "ddd");
//下面永遠輸出中文的星期一到星期日
locale.toString(QDateTime::currentDateTime(), "dddd");
QSqlTableModel大大簡化了對數據庫表的顯示、添加、刪除、修改等,唯獨對數據庫分頁操作有點繞彎。
//實例化數據庫表模型
QSqlTableModel *model = new QSqlTableModel(this);
//指定表名
model->setTable("table");
//設置列排序
model->setSort(0, Qt::AscendingOrder);
//設置提交模式
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
//立即查詢一次
model->select();
//將數據庫表模型設置到表格上
ui->tableView->setModel(model);//測試發現過濾條件中除了可以帶where語句還可以帶排序及limit等
model->setFilter("1=1 order by id desc limit 100");//如果在過濾條件中設置了排序語句則不可以再使用setSort方法
//下面的代碼結果是執行出錯,可能因為setSort又重新增加了order by語句導致多個order by語句沖突了。
model->setSort(0, Qt::AscendingOrder);
model->setFilter("1=1 order by id desc limit 100");//通過setFilter設置單純的where語句可以不用加1=1
model->setFilter("name='張三'");
//如果還有其他語句比如排序或者limit等則需要最前面加上1=1
//下面表示按照id升序排序,查詢結果顯示第5-15條記錄。
model->setFilter("1=1 order by id asc limit 5,10");//多個條件用and連接
//建議任何時候用了setFilter則最前面寫1=1最末尾加上 ; 防止有些地方無法正確執行。
model->setFilter("1=1 and name='張三' and result>=70;");//下面表示查詢姓名是張三的記錄,按照id字段降序排序,結果從第10條開始100條,相當于從第10條到110條記錄。
model->setFilter("1=1 and name='張三' order by id desc limit 10,100;");//在第3行開始添加一條記錄
model->insertRow(2);
//立即填充剛剛新增加的行,默認為空需要用戶手動在表格中輸入。
model->setData(model->index(2, 0), 100);
model->setData(model->index(2, 1), "張三");
//提交更新
model->submitAll();//刪除第4行
model->removeRow(3);
model->submitAll();//總之有增刪改操作后都需要調用model->submitAll();來真正執行,否則僅僅是數據模型更新了數據,并不會更新到數據庫中。//撤銷更改
model->revertAll();
19:181-190
Qt天生就是linux的,從linux開始發展起來的,所以不少Qt程序員經常的開發環境是linux,比如常用的ubuntu等系統,整理了一點常用的linux命令。
命令功能 sudo -s 切換到管理員,如果是 sudo -i 切換后會改變當前目錄。 apt install g++ 安裝軟件包(要管理員權限),另一個派系的是 yum install cd /home 進入home目錄 ls 羅列當前所在目錄所有目錄和文件 ifconfig 查看網卡信息包括IP地址,windows上是 ipconfig。 tar -zxvf bin.tar.gz 解壓文件到當前目錄 tar -jxvf bin.tar.xz 解壓文件到當前目錄 tar -zxvf bin.tar.gz -C /home 解壓文件到/home目錄,記住是大寫的C。 tar -zcvf bin.tar.gz bin 將bin目錄壓縮成tar.gz格式文件(壓縮比一般) tar -jcvf bin.tar.xz bin 將bin目錄壓縮成tar.xz格式文件(壓縮比高,推薦) tar -... j z 表示不同的壓縮方法,x表示解壓,c表示壓縮。 gedit 1.txt 用記事本打開文本文件 vim 1.txt 用vim打開文件,很多時候可以縮寫用vi。 ./configure make -j4 make install 通用編譯源碼命令, 第一步./configure執行配置腳本,第二步make -j4啟用多線程編譯,第三步make install安裝編譯好的文件。 ./configure -prefix /home/liu/Qt-5.9.3-static -static -sql-sqlite -qt-zlib -qt-xcb -qt-libpng -qt-libjpeg -fontconfig -system-freetype -iconv -nomake tests -nomake examples -skip qt3d -skip qtdoc Qt通用編譯命令 ./configure -prefix /home/liu/Qt-5.9.3-static -static -release -nomake examples -nomake tests -skip qt3d 精簡編譯命令 ./configure --prefix=host --enable-static --disable-shared --disable-doc ffmpeg編譯命令
Qt自帶的日志重定向機制非常簡單好用,自從用了以后再也不用什么斷點調試啥的了,在需要的地方支持qdebug輸出對應的信息,而且發布程序以后也可以開啟調試日志將其輸出查看等。
//Qt5開始提供了日志上下文信息輸出,比如輸出當前打印消息所在的代碼文件、行號、函數名等。
//如果是release還需要在pro中加上 DEFINES += QT_MESSAGELOGCONTEXT 才能輸出上下文,默認release關閉的。
//切記不要在日志鉤子函數中再寫qdebug之類的,那樣就死循環了。
//日志重定向一般就三種處理
//1: 輸出到日志文件比如txt文本文件。
//2: 存儲到數據庫,可以分類存儲,以便相關人員查詢分析。
//3: 重定向到網絡,對方用小工具連接程序后,所有打印信息通過tcp發過去。//日志重定向
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
#else
void Log(QtMsgType type, const char *msg)
#endif
{//加鎖,防止多線程中qdebug太頻繁導致崩潰static QMutex mutex;QMutexLocker locker(&mutex);QString content;//這里可以根據不同的類型加上不同的頭部用于區分switch (type) {case QtDebugMsg:content = QString("%1").arg(msg);break;case QtWarningMsg:content = QString("%1").arg(msg);break;case QtCriticalMsg:content = QString("%1").arg(msg);break;case QtFatalMsg:content = QString("%1").arg(msg);break;}//加上打印代碼所在代碼文件、行號、函數名
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))if (SaveLog::Instance()->getUseContext()) {int line = context.line;QString file = context.file;QString function = context.function;if (line > 0) {content = QString("行號: %1 文件: %2 函數: %3\n%4").arg(line).arg(file).arg(function).arg(content);}}
#endif//將內容傳給函數進行處理SaveLog::Instance()->save(content);
}//安裝日志鉤子,輸出調試信息到文件,便于調試
void SaveLog::start()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))qInstallMessageHandler(Log);
#elseqInstallMsgHandler(Log);
#endif
}//卸載日志鉤子
void SaveLog::stop()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))qInstallMessageHandler(0);
#elseqInstallMsgHandler(0);
#endif
}
自從c++11標準以后,各種語法糖層出不窮,其中lambda表達式用的最廣,基本上從Qt5以后就支持lambda表達式。對于習慣了c99的老一輩的程序員來說,這玩意是個新鮮事物,這里特意做個小理解筆記。
代碼格式:capture?mutable ->return-type {statement} [capture]:捕捉列表,捕捉列表總是出現在Lambda函數的開始處,實際上,[]是Lambda引出符,編譯器根據該引出符判斷接下來的代碼是否是Lambda函數,捕捉列表能夠捕捉上下文中的變量以供Lambda函數使用。 (parameters):參數列表,與普通函數的參數列表一致,如果不需要參數傳遞,則可以連同括號 () 一起省略。 mutable:mutable修飾符,默認情況下,Lambda函數總是一個const函數,mutable可以取消其常量性。在使用該修飾符時,參數列表不可省略(即使參數為空)。 ->return-type:返回類型,用追蹤返回類型形式聲明函數的返回類型,我們可以在不需要返回值的時候也可以連同符號 -> 一起省略。此外,在返回類型明確的情況下,也可以省略該部分,讓編譯器對返回類型進行推導。 {statement}:函數體,內容與普通函數一樣,不過除了可以使用參數之外,還可以使用所有捕獲的變量。
捕捉列表有以下幾種形式:
[var]表示值傳遞方式捕捉變量var。 [=]表示值傳遞方式捕捉所有父作用域的變量(包括this)。 [&var]表示引用傳遞捕捉變量var。 [&]表示引用傳遞方式捕捉所有父作用域的變量(包括this)。 [this]表示值傳遞方式捕捉當前的this指針。
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//按鈕單擊不帶參數connect(ui->pushButton, &QPushButton::clicked, [] {qDebug() << "hello lambda";});//按鈕單擊帶參數connect(ui->pushButton, &QPushButton::clicked, [] (bool isCheck) {qDebug() << "hello lambda" << isCheck;});//自定義信號帶參數connect(this, &MainWindow::sig_test, [] (int i, int j) {qDebug() << "hello lambda" << i << j;});emit sig_test(5, 8);
}
由于Qt版本眾多,有時候為了兼容多個版本甚至跨度Qt4/Qt5/Qt6的兼容,有些頭文件或者類名等變了或者新增了,需要用到Qt版本的判斷。需要注意的是如果在頭文件中使用 QT_VERSION_CHECK 需要先引入#include "qglobal.h"不然編譯失敗,因為 QT_VERSION_CHECK 這個函數在 qglobal.h 頭文件中。
//至少要包含 qglobal.h,理論上Qt所有的類都包含了這個頭文件,所以你引入Qt的其他頭文件也行比如 qobject.h
#include "qglobal.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#else
#include "qdesktopwidget.h"
#endif
在使用QString轉換到char *或者const char *的時候,務必記得分兩步來完成,血的教訓,在一個場景中,就因為沒有分兩步走,現象是msvc的debug異常release正常,mingw和gcc的debug和release都正常,這就很無語了,找問題找半天,對比法排除法按道理要么都有問題才對。
轉換前QString的內容無關中文還是英文,要出問題都一樣。 轉換中QByteArray無關具體類型,toUtf8、toLatin1、toLocal8Bit等方法,要出問題都一樣。 轉換后無關char *還是const char *,要出問題都一樣。 出問題的隨機性的,概率出現,理論上debug的概率更大。 根據酷碼大佬分析可能的原因(不確定)是msvc為了方便調試,debug會在內存釋放后做填充,release則不會。
QString text = "xxxxx";
//下面這樣轉換很可能會有問題
char *data = text.toUtf8().data();
//分兩步轉換肯定不會有問題
QByteArray buffer = text.toUtf8();
char *data = buffer.data();
const char *data = buffer.constData();
關于是使用QList還是QVector的問題,一直是眾多Qter的選擇問題,主要是這兩個玩意提供的的接口函數基本一致,比如插入、刪除、取值等。
大多數情況下可以用QList。像append、prepend、insert這種操作,通常QList比QVector快的多。 QList是基于index標簽存儲它的元素項在內存中,比那種依賴iterator迭代的更快捷,而且你的代碼也更少。 如果你需要一個真正的連接著的list,且需要保證一個固定插入耗時。那就用迭代器,而不是標簽。使用QLinkedList()。 如果你需要開辟連續的內存空間存儲,或者你的元素遠比一個指針大,這時你需要避免個別插入操作,出現堆棧溢出,這時候用QVector。 如果更在意取值的速度則用QVector,QCustomPlot用的就是QVector,需要頻繁大量的取出數據進行繪制。 如果更在意更新數據(添加、刪除等)的速度則用QList,就因為QChart用的是QList存取數據,也是導致大數據量卡頓的原因之一,一直被詬病。 在數據量很小的情況下兩者幾乎沒啥性能區別。 貌似Qt6對這兩個類合并了(選擇困難癥的Qter解放了),QVector=QList即QVector是QList的別名,可能底層改了代碼以便發揮兩者的優勢。
關于mouseTracking鼠標追蹤和tabletTracking平板追蹤的幾點官方說明。
mouseTracking屬性用于保存是否啟用鼠標跟蹤,缺省情況是不啟用的。 沒啟用的情況下,對應部件只接收在鼠標移動同時至少一個鼠標按鍵按下時的鼠標移動事件。 啟用鼠標跟蹤的情況下,任何鼠標移動事件部件都會接收。 部件方法hasMouseTracking()用于返回當前是否啟用鼠標跟蹤。 setMouseTracking(bool enable)用于設置是否啟用鼠標跟蹤。 與鼠標跟蹤相關的函數主要是mouseMoveEvent()。 tabletTracking屬性保存是否啟用部件的平板跟蹤,缺省是不起用的。 沒有啟用平板跟蹤的情況下,部件僅接收觸控筆與平板接觸或至少有個觸控筆按鍵按下時的觸控筆移動事件。 如果部件啟用了平板跟蹤功能,部件能接收觸控筆靠近但未真正接觸平板時的觸控筆移動事件。 這可以用于監視操作位置以及部件的輔助操作功能(如旋轉和傾斜),并為圖形界面提供這些操作的信息接口。 部件方法hasTabletTracking()用于返回當前是否啟用平板跟蹤。 setTabletTracking(bool enable)用于設置是否啟用平板跟蹤。 與平板跟蹤相關的函數主要是tabletEvent()。
關于QTableWidget等控件調用自帶的removeRow、clearContents、clear函數刪除了里面的item和內容,會自動調用item或者cellwidget的析構函數進行資源釋放,不用自己手動再去釋放。
//每次調用 clearContents 都會自動清理之前的item
ui->tableWidget->clearContents();
for (int i = 0; i < count; ++i) {ui->tableWidget->setItem(i, 0, new QTableWidgetItem("aaa"));ui->tableWidget->setItem(i, 1, new QTableWidgetItem("bbb"));ui->tableWidget->setCellWidget(i, 2, new QPushButton("ccc"));
}
二、升級到Qt6
2.1 直觀總結
增加了很多輪子,同時原有模塊拆分的也更細致,估計為了方便拓展個管理。 把一些過度封裝的東西移除了(比如同樣的功能有多個函數),保證了只有一個函數執行該功能。 把一些Qt5中兼容Qt4的方法廢棄了,必須用Qt5中對應的新的函數。 跟隨時代腳步,增加了不少新特性以滿足日益增長的客戶需求。 對某些模塊和類型及處理進行了革命性的重寫,運行效率提高不少。 有參數類型的變化,比如 long * 到 qintptr * 等,更加適應后續的拓展以及同時對32 64位不同系統的兼容。 源碼中的double數據類型全部換成了qreal,和Qt內部數據類型高度一致和統一。 我測試的都是QWidget部分,quick部分沒有測試,估計quick部分更新可能會更多。 強烈建議暫時不要用Qt6.0到Qt6.2之間的版本,一些模塊還缺失,相對來說BUG也比較多,推薦6.2版本開始正式遷移。
2.2 經驗總結
萬能方法:安裝5.15版本,定位到報錯的函數,切換到源碼頭文件,可以看到對應提示字樣 QT_DEPRECATED_X("Use sizeInBytes") 和新函數。按照這個提示類修改就沒錯,一些函數是從Qt5.7 5.9 5.10等版本新增加的,可能你的項目還用的Qt4的方法,但是Qt6以前都兼容這些舊方法,到了Qt6就徹底需要用新方法了。
Qt6對core這個核心類進行了拆分,多出來core5compat,因此你需要在pro增加對應的模塊已經代碼中引入對應的頭文件。
//pro文件引入模塊
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat//代碼中引入頭文件
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
#include <QtCore5Compat>
#endif
默認Qt6開啟了高分屏支持,界面會變得很大,甚至字體發虛,很多人會不習慣,因為這種模式如果程序很多坐標計算沒有采用devicePixelRatio進行運算的話,100%會出現奇奇怪怪的問題,因為坐標不準確了。要取消這種效果可以設置高分屏縮放因子。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif
原有的隨機數函數提示用QRandomGenerator替代,為了兼容所有qt版本,改動最小的辦法是直接用c++中的隨機數,比如qsrand函數換成srand,qrand函數換成rand,查看過源代碼,其實封裝的就是c++中的隨機數,很多類似的封裝比如qSin封裝的sin。
QColor的 light 改成 lighter ,dark 改成 darker,其實 lighter、darker 這兩個方法以前一直有。
QFontMetricsF 中的 fm.width 換成 fm.horizontalAdvance ,從5.11開始用新函數。
QPalette調色板枚舉值,Foreground = WindowText, Background = Window,其中 Foreground 和 Background 沒有了,要用 WindowText 和 Window 替代,以前就有。類似的還有 setTextColor 改成了 setForeground 。
QWheelEvent的 delta() 改成 angleDelta().y(),pos() 改成 position() 。
svg模塊拆分出來了svgwidgets,如果用到了該模塊則需要在pro增加 QT += svgwidgets ,同理opengl模塊拆分出來了openglwidgets。
qlayout中的 margin() 函數換成 contentsMargins().left(),查看源碼得知以前的 margin() 返回的就是 contentsMargins().left(),在四個數值一樣的時候,默認四個數值就是一樣。類似的還有setMargin移除了,統統用setContentsMargins。
之前 QChar c = 0xf105 全部要改成強制轉換 QChar c = (QChar)0xf105,不再有隱式轉換,不然編譯報錯提示error: conversion from 'int' to 'QChar' is ambiguous 。
qSort等一些函數用回c++的 std::sort 。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))std::sort(ipv4s.begin(), ipv4s.end());
#elseqSort(ipv4s);
#endif
Qt::WA_NoBackground 改成 Qt::WA_OpaquePaintEvent 。
QMatrix 類廢棄了沒有了,換成 QTransform ,函數功能基本一致,QTransform 類在Qt4就一直有。
QTime 計時去掉了,需要改成 QElapsedTimer ,QElapsedTimer 類在Qt4就一直有。
QApplication::desktop()廢棄了, 換成了 QApplication::primaryScreen()。
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#define deskGeometry qApp->primaryScreen()->geometry()
#define deskGeometry2 qApp->primaryScreen()->availableGeometry()
#else
#include "qdesktopwidget.h"
#define deskGeometry qApp->desktop()->geometry()
#define deskGeometry2 qApp->desktop()->availableGeometry()
#endif
獲取當前屏幕索引以及尺寸需要分別處理。
//獲取當前屏幕索引
int QUIHelper::getScreenIndex()
{//需要對多個屏幕進行處理int screenIndex = 0;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))int screenCount = qApp->screens().count();
#elseint screenCount = qApp->desktop()->screenCount();
#endifif (screenCount > 1) {//找到當前鼠標所在屏幕QPoint pos = QCursor::pos();for (int i = 0; i < screenCount; ++i) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))if (qApp->screens().at(i)->geometry().contains(pos)) {
#elseif (qApp->desktop()->screenGeometry(i).contains(pos)) {
#endifscreenIndex = i;break;}}}return screenIndex;
}//獲取當前屏幕尺寸區域
QRect QUIHelper::getScreenRect(bool available)
{QRect rect;int screenIndex = QUIHelper::getScreenIndex();if (available) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))rect = qApp->screens().at(screenIndex)->availableGeometry();
#elserect = qApp->desktop()->availableGeometry(screenIndex);
#endif} else {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))rect = qApp->screens().at(screenIndex)->geometry();
#elserect = qApp->desktop()->screenGeometry(screenIndex);
#endif}return rect;
}
QRegExp類移到了core5compat模塊,需要主動引入頭文件 #include 。
//設置限制只能輸入數字+小數位QString pattern = "^-?[0-9]+([.]{1}[0-9]+){0,1}$";//設置IP地址校驗過濾QString pattern = "(2[0-5]{2}|2[0-4][0-9]|1?[0-9]{1,2})";//確切的說 QRegularExpression QRegularExpressionValidator 從5.0 5.1開始就有
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))QRegularExpression regExp(pattern);QRegularExpressionValidator *validator = new QRegularExpressionValidator(regExp, this);
#elseQRegExp regExp(pattern);QRegExpValidator *validator = new QRegExpValidator(regExp, this);
#endiflineEdit->setValidator(validator);
QWheelEvent構造參數和對應的計算方位函數變了。
//模擬鼠標滾輪
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
QWheelEvent wheelEvent(QPoint(0, 0), -scal, Qt::LeftButton, Qt::NoModifier);
#else
QWheelEvent wheelEvent(QPointF(0, 0), QPointF(0, 0), QPoint(0, 0), QPoint(0, -scal), Qt::LeftButton, Qt::NoModifier, Qt::ScrollBegin, false);
#endif
QApplication::sendEvent(widget, &wheelEvent);//鼠標滾輪直接修改值
QWheelEvent *whellEvent = (QWheelEvent *)event;
//滾動的角度,*8就是鼠標滾動的距離
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
int degrees = whellEvent->delta() / 8;
#else
int degrees = whellEvent->angleDelta().x() / 8;
#endif
//滾動的步數,*15就是鼠標滾動的角度
int steps = degrees / 15;
qVariantValue 改成 qvariant_cast ,qVariantSetValue(v, value) 改成了 v.setValue(val)。相當于退回到最原始的方法,查看qVariantValue源碼封裝的就是qvariant_cast。
QStyleOption的init改成了initFrom。
QVariant::Type 換成了 QMetaType::Type ,本身以前的 QVariant::Type 封裝的就是 QMetaType::Type 。
QStyleOptionViewItemV2 V3 V4 之類的全部沒有了,暫時可以用 QStyleOptionViewItem 替代。
QFont的 resolve 的一個重載函數換成了 resolveMask。
QSettings的 setIniCodec 方法移除了,默認就是utf8,不需要設置。
qcombobox 的 activated(QString) 和 currentIndexChanged(QString) 信號刪除了,用int索引參數的那個,然后自己通過索引獲取值。個人覺得這個沒必要刪除。
qtscript模塊徹底沒有了,盡管從Qt5時代的后期版本就提示為廢棄模塊,一致堅持到Qt6才正式廢棄,各種json數據解析全部換成qjson類解析。
QByteArray 的 append indexOf lastIndexOf 等眾多方法的QString參數重載函數廢棄了,要直接傳 QByteArray,就在原來參數基礎上加上 .toUtf8() 。查看源碼也看得到以前的QString參數也是轉成.toUtf8()再去比較。
QDateTime的時間轉換函數 toTime_t + setTime_t 名字改了,對應改成了 toSecsSinceEpoch + setSecsSinceEpoch ,這兩個方法在Qt5.8時候新增加的。
QLabel的 pixmap 函數之前是指針 *pixmap() 現在換成了引用 pixmap()。
QTableWidget的 sortByColumn 方法移除了默認升序的方法,必須要填入第二個參數表示升序還是降序。
qtnetwork中的錯誤信號error換成了errorOccurred。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))connect(tcpSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(error()));
#elseconnect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
#endif
XmlPatterns模塊木有了,全部用xml模塊重新解析。
nativeEvent的參數類型變了。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result);
#else
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
#endif
QButtonGroup的buttonClicked信號中int參數的函數全部改名字叫idClicked。
QButtonGroup *btnGroup = new QButtonGroup(this);
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))connect(btnGroup, SIGNAL(idClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#elseconnect(btnGroup, SIGNAL(buttonClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#endif
QWebEngineSettings之前是QWebEngineSettings::defaultSettings();現在改成了QWebEngineProfile::defaultProfile()->settings();通過查看之前的源碼得知QWebEngineSettings::defaultSettings();封裝的就是QWebEngineProfile::defaultProfile()->settings();因為Qt6去除了N多過度封裝的函數。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))QWebEngineSettings *webSetting = QWebEngineProfile::defaultProfile()->settings();
#elseQWebEngineSettings *webSetting = QWebEngineSettings::defaultSettings();
#endif
Qt6將enterEvent的參數QEvent改成了QEnterEvent也不打個招呼。這種改變編譯也不會提示的。
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))void enterEvent(QEnterEvent *);
#elsevoid enterEvent(QEvent *);
#endif//后面經過JasonWong大佬的指點,從父類重新實現的virtual修飾的函數,建議都加上override關鍵字。
//這樣的話一旦父類的函數或者參數變了則會提示編譯報錯,而不是編譯通過但是運行不正常會一臉懵逼茫然,從而把鍋扣給Qt。//下面是父類函數
virtual void enterEvent(QEvent *event);
//子類建議加上override
void enterEvent(QEvent *event) override;
Qt6中多個類進行了合并,比如現在QVector就成了QList的別名,意味著這兩個類是同一個類沒有任何區別,可能Qt內部對兩種的優點都集中在一起,并盡量重寫算法或者其他處理規避缺點。同理QStringList現在也成了 QList<QString> 的別名,是同一個類,沒有單獨的類。
在Qt4時代默認QWidget構造函數父類是0,到了Qt5變成了Q_NULLPTR,到了Qt6居然用的是默認的c++標準中的nullptr而不是Qt自定義定義的Q_NULLPTR(同樣的還有Q_DECL_OVERRIDE換成了用override等),可能是為了徹底拋棄歷史包袱擁抱未來。
//下面依次是Qt4/5/6的寫法
MainWindow(QWidget *parent = 0);
MainWindow(QWidget *parent = Q_NULLPTR);
MainWindow(QWidget *parent = nullptr);//查閱Qt源碼查看Q_NULLPTR原來是根據編譯器定義來選擇
#ifdef Q_COMPILER_NULLPTR
# define Q_NULLPTR nullptr
#else
# define Q_NULLPTR NULL
#endif//Qt高版本兼容低版本寫法比如Qt5/6都支持 *parent = 0 這種寫法。
三、酷碼專區
酷碼大佬(微信Kuma-NPC)
關于Qt事件傳遞的一個說明:
通常寫win32程序,鼠標消息應該是直接發給指定窗口句柄的,指定窗口沒有處理就會轉化成透傳消息,交給父窗口處理。你在一個普通文字label上點擊,父窗口也能收到鼠標事件。 Qt應該是所有消息都發給了頂層窗口,所以事件分發邏輯是自己處理,主窗口收到鼠標事件然后Qt自己分發給指定子控件,QEvent會有ignore或者accept表示自己處理了沒有,例如鼠標點擊事件,事件分發器發現沒有被處理,數據重新計算然后分發給父窗口。這樣父窗口收到的事件坐標就是基于自己窗口內的。用eventFilter就需要自己計算坐標。 再比如,當使用QDialog,放一個QLineEdit并設置焦點,按Esc時QDialog也會自動關閉,本質上就是因為QLineEdit并不處理Esc的按鍵事件,透傳給了QDialog。
四、Qt設計模式
讀《c++ Qt設計模式》書籍整理的一點經驗。此書和官方的《C++ GUI Qt4編程》一起的。
通常而言,好的做法是在包含了Qt頭文件之后再包含非Qt頭文件,由于Qt(為編譯器和預處理器)定義了許多符號,這使得避免名稱沖突變得更容易,也更容易找到文件。
#include "frminput2019.h"
#include "ui_frminput2019.h"#include "qdatetime.h"
#include "qdebug.h"#include "input2019.h"
#include "inputnumber.h"
一種好的編程實踐是在代碼中使用const實體而不是嵌入數字型常量(有時稱他們為“幻數”)。如果以后需要修改他的值時,就可以獲得這種靈活性。一般而言,將常量“孤立”出來,可提高程序的可維護性。
//不推薦寫法
for (int i = 0; i < 100; ++i) {...
}//推薦下面的寫法
const int count = 100;
for (int i = 0; i < count; ++i) {...
}
內存管理使程序員獲得了強大的能力,但是,“權力越大,責任越大”。 只要有可能,就應當使用列表而不是數組,比如應該使用 QList 代替 int [] ,在c++中數組被看成是“邪惡的”。
五、其他經驗
Qt界的中文亂碼問題,版本眾多導致的如何選擇安裝包問題,如何打包發布程序的問題,堪稱Qt界的三座大山!
在Qt的學習過程中,學會查看對應類的頭文件是一個好習慣,如果在該類的頭文件沒有找到對應的函數,可以去他的父類中找找,實在不行還有爺爺類,肯定能找到的。通過頭文件你會發現很多函數接口其實Qt已經幫我們封裝好了,有空還可以閱讀下他的實現代碼。
Qt安裝目錄下的Examples目錄下的例子,看完學完,月薪20K起步;Qt常用類的頭文件的函數看完學完使用一遍并加以融會貫通,月薪30K起步。
Qt在開發階段不支持中文目錄,切記,這是無數人可能犯的錯誤,在安裝Qt集成開發環境以及編譯器的時候,務必記得目錄必須英文,Qt項目源碼也必須是英文目錄,否則很可能不正常,建議盡量用默認的安裝位置。
如果出現崩潰和段錯誤,80%都是因為要么越界,要么未初始化,死扣這兩點,80%的問題解決了 。
Qt一共有幾百個版本,關于如何選擇Qt版本的問題,我一般保留四個版本,為了兼容Qt4用4.8.7,最后的支持XP的版本5.7.0,最新的長期支持版本比如5.15,最高的新版本比如5.15.2。強烈不建議使用4.7以前和5.0到5.3之間的版本(Qt6.0到Qt6.2之間、不含6.2的版本也不建議,很多模塊還沒有集成),太多bug和坑,穩定性和兼容性相比于之后的版本相當差,能換就換,不能換睡服領導也要換。如果沒有歷史包袱建議用5.15.2,目前新推出的6.0版本也強烈不建議使用,官方還在整合當中,好多類和模塊暫時沒有整合,需要等到6.2版本再用。
Qt和msvc編譯器常見搭配是Qt5.7+VS2013、Qt5.9+VS2015、Qt5.12+VS2017、Qt5.15+VS2019、Qt6.2+VS2019,按照這些搭配來,基本上常用的模塊都會有,比如webengine模塊,如果選用的Qt5.12+msvc2015,則很可能官方沒有編譯這個模塊,只是編譯了Qt5.12+msvc2017的。
Qt默認有對應VS版本,在下載對應VS插件的時候心里要有個數,官方默認提供的是原配的插件,如果想要Qt4.8+VS2015的插件,需要自行編譯。一般來說是Qt4.8原配VS2010,Qt5.6原配VS2013,Qt5.9原配VS2015,Qt5.12原配VS2017,切記:原配最好。
新版本Qt安裝包安裝的時候需要填寫注冊信息,如果不想填寫,先禁用網卡,在運行安裝包,可以直接跳過這一步進行安裝。
終極秘籍:如果遇到問題搜索Qt方面找不到答案,試著將關鍵字用JAVA C# android打頭,你會發現別有一番天地,其他人很可能做過!
如果Qt能從下面幾個方面努力,相信會更有發展前景。
QWidget支持CSS3,具有諸多的牛逼的效果,目前支持的是CSS2。 QWidget支持GPU繪制,可選切換CPU或者GPU,提升繪制效率,利用現在強大的硬件。 Qml無縫支持js,可以利用現在各種js輪子,指數級提升qml的項目范圍。 支持將程序轉成web運行,比如轉成cgi之類的程序,目前Qt for WebAssembly很雞肋,功能極其有限,sql/network/本地訪問等都不支持,首次加載速度超慢,大部分Qt類還不支持。
在Qt界始終有兩大陣營產生激烈的紛爭,那就是選用qml還是widget好,大量初學者也會問這個問題,有以下幾點總結。
widget屬于傳統界面開發,和VB/VC/Delphi等拖曳控件開發類似,走CPU繪制,能最大化的兼容現有的硬件和過去的相對偏低性能的硬件。 qml屬于新時代的產物,大概從2010年開始,和flutter/Electron等web開發框架及移動開發框架類似,為了適應各種移動端開發及動畫流暢性觸摸絲滑體驗、充分利用和“榨干”現在的GPU性能,把CPU留出來給用戶最大化發揮。 硬件性能越好,GPU越是強勁,qml的綜合性能越是完爆widget,反之對比也是指數級的。除了極其省成本的嵌入式硬件領域或者國產CPU等,其他領域的硬件性能都是暴增。 widget主要集中在金融、軍工、安防、航天、船舶、教育等領域,qml主要集中在汽車儀表、車機、直播等領域。 目前國內widget多于qml,國外可能偏向qml,這個不難看出,流行的移動端開發框架都是國外開發者居多。 可預見的十年內,這兩者將長期并存,官方基本不再更新widget而是主推qml,意味著將來對qml的性能優化只增不減,未來趨勢是qml。 沒有編程經驗的新手qml學習成本更低,而從VB/VC等傳統軟件開發轉過來的從業者更適合學習widget。 有的時候不禁要問,既生widget何生qml,學習成本和選擇又多了,其實這正是和這個世界的哲學一樣:世界是簡單的又是復雜的。為了適應各種需求和滿足需要。 總之,無論qml還是widget,和找老婆一樣,適合自己的就是最好的,自己擅長哪個就用哪個。 如果還不知道擅長哪個,有空就兩個都學,學習過程中自己就會有切身感受和對比,能者多勞多多益善。能夠順利的最快的完成老板的任務給老板賺錢才是王道。
寫程序過程中發現問題,比如有些問題是極端特殊情況下出現,最好找到問題的根源,有時候肯定多多少少會懷疑是不是Qt本身的問題,懷疑是對的,但是99.9%的問題最終證實下來還是自己的代碼寫的不夠好導致的,如果為了趕時間老板催的急,實在不行再用重啟或者復位大法,比如搞個定時器、線程、網絡通信啥的去檢測程序是否正常,程序中某個模塊或者功能是否正常,不正常就復位程序或者重啟程序,在嵌入式上還可以更暴力一點就是系統重啟和斷電重啟。
寫程序過程中尤其要注意32位的庫和64位的庫互不兼容,比如32位的程序引用64位的庫,64位的程序引用32位的庫,都是編譯通不過的,而在windows64位系統中是能夠運行32位程序的,因為64位的系統提供了32位的運行環境,一般目錄在Program Files(x86),32位的程序在64位的環境中最終引用的還是32位的庫。關于如何判斷自己的Qt庫是多少位,有個誤區就是很多人要么看成了QtCreator的關于信息中列出的位數,要么以為自己是64位的系統就認為是64位的Qt,最終要在Qt構建套件中查看具體位數,大概從Qt5.14開始基本上很少提供32位的庫,尤其是Qt6.0以后基本上默認就是只有64位的庫了,這也是順應時代潮流,畢竟不久的將來(個人預計2030年以前)基本上32位的系統占比不超過1%,放心大膽的用64位的庫吧,拋棄煩人的32位以及XP系統。
關于動態和靜態的一點個人理解:
在Qt程序中,分動態庫版本的Qt和靜態庫版本的Qt。 官方默認提供的二進制包就是動態庫版本的Qt,如果自行編譯則編譯的時候對應參數 -shared。 靜態庫版本的Qt需要自行編譯,編譯的時候對應參數 -static,(理論上個人用靜態庫的Qt也需要收費,因為靜態編譯后都看不到Qt的相關庫文件)。 使用動態庫的Qt支持編譯生成動態庫和靜態庫(CONFIG += staticlib)的程序。 使用動態庫的Qt程序支持動態庫的引用(引用的時候 LIB += ,運行的時候需要動態庫文件比如.dll .so 支持)。 使用動態庫的Qt程序支持靜態庫的引用(引用的時候 LIB += ,運行的時候無需庫文件支持,可以理解為該文件已經和可執行文件合二為一,缺點是可執行文件體積變大)。 上述動態庫的規則也通用于靜態庫。 此規則應該是通用于其他語言框架。 很多人有個誤區包括幾年前的我,以為要用Qt編寫靜態庫,前提是Qt庫必須靜態的。 如果要將Qt程序編譯成靜態的可執行文件(單個文件無依賴),前提是所用的Qt庫必須靜態的。
最后一條:珍愛生命,遠離編程。祝大家頭發濃密,睡眠良好,情緒穩定,財富自由!
六、七七八八
6.1 推薦開源主頁
名稱網址 Qt交流大會群 群號:853086607 Qt技術交流群 群號:46679801 Qt進階之路群 群號:734623697 QtWidget開源demo集合 QWidgetDemo: Qt編寫的一些開源的demo,支持Qt4、Qt5、Qt6,支持任意系統,預計會有100多個,一直持續更新完善,代碼簡潔易懂注釋詳細,每個都是獨立項目,非常適合初學者,代碼隨意傳播使用,拒絕打賞和捐贈,歡迎留言評論! QtQuick/Qml開源demo集合 TaoQuick: 一套酷炫的QtQuick/Qml基礎庫和示例(a cool QtQuick/qml component library and demo) QtQuick/Qml開源demo集合 QtQuickExamples: QtQuickExamples —— QtQuick相關的技術分享
6.2 推薦網站主頁
名稱網址 qtcn http://www.qtcn.org 豆子的空間 https://www.devbean.net yafeilinux http://www.qter.org feiyangqingyun Qt自定義控件大全+UI定制+輸入法+視頻監控+物聯網_feiyangqingyun_CSDN博客 Qt系列文章 https://blog.csdn.net/feiyangqingyun/category_11460485.html 一去二三里 青春不老,奮斗不止!_一去丶二三里_CSDN博客 烏托邦2號 烏托邦_烏托邦2號_CSDN博客 foruok 安曉輝生涯——聚焦程序員的職業規劃與成長_foruok_CSDN博客 jason Jason’s home_Jason188080501_CSDN博客 朝十晚八 朝十晚八 - 博客園 BIG_C_GOD BIG_C_GOD的博客_CSDN博客 公孫二狗 Qt 雜談 | 公孫二狗 雨田哥 雨田哥的博客_CSDN博客 鄭天佐 zhengtianzuo的博客_CSDN博客 寒山-居士 Esonpo的專欄_寒山-居士_CSDN博客 前行中小豬 前行之路還需前行_前行中的小豬_CSDN博客 濤哥的知乎專欄 Qt進階之路 - 知乎 Qt君 Qt君_CSDN博客
6.3 推薦學習網站
名稱網址 Qt老外視頻教程 _-莫離-_的個人空間_嗶哩嗶哩_Bilibili Qt維基補充文檔 Qt Wiki Qt源碼查看網站 qt5/ Source Tree - Woboq Code Browser Qt官方下載地址 https://download.qt.io Qt官方下載新地址 Index of /new_archive/qt Qt國內鏡像下載地址 Index of /qt/ Qt安裝包下載地址 qthub 精美圖表控件QWT Qwt User's Guide: Qwt - Qt Widgets for Technical Applications 精美圖表控件QCustomPlot Qt Plotting Widget QCustomPlot - Introduction 免費圖標下載 http://www.easyicon.net/ 圖形字體下載 iconfont-阿里巴巴矢量圖標庫 漂亮界面網站 UI-中國用戶體驗設計平臺 微信公眾號 官方公眾號:Qt軟件 ???亮哥公眾號:高效程序員
七、書籍推薦
C++入門書籍推薦《C++ primer plus》,進階書籍推薦《C++ primer》。 Qt入門書籍推薦霍亞飛的《Qt Creator快速入門》,Qt進階書籍推薦官方的《C++ GUI Qt4編程》,qml書籍推薦《Qt5編程入門》。 強烈推薦程序員自我提升、修養、規劃系列書《走出軟件作坊》《大話程序員》《程序員的成長課》《解憂程序員》,受益匪淺,受益終生!
總結
以上是生活随笔 為你收集整理的【转】飞扬青云_Qt开发经验 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。