天天看點

Qt 開發經驗總結

當編譯發現大量錯誤的時候,從第一個看起,一個一個的解決,不要急着去看下一個錯誤,往往後面的錯誤都是由于前面的錯誤引起的,第一個解決後很可能都解決了。

定時器是個好東西,學會好使用它,有時候用QTimer::singleShot可以解決意想不到的問題。

打開creator,在建構套件的環境中增加MAKEFLAGS=-j8,可以不用每次設定多線程編譯。珍愛時間和生命。新版的QtCreator已經預設就是j8。

如果你想順利用QtCreator部署安卓程式,首先你要在AndroidStudio 裡面配置成功,把坑全部趟平。

很多時候找到Qt對應封裝的方法後,記得多看看該函數的重載,多個參數的,你會發現不一樣的世界,有時候會恍然大悟,原來Qt已經幫我們封裝好了。

點選領取Qt學習資料+視訊教程~「連結」

可以在pro檔案中寫上标記版本号+ico圖示(Qt5才支援)

VERSION  = 2020.10.25
RC_ICONS = main0.ico
           

管理者運作程式,限定在MSVC編譯器。

QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" #以管理者運作
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" #VS2013 在XP運作
           

運作檔案附帶調試輸出視窗 CONFIG += console pro

繪制平鋪背景QPainter::drawTiledPixmap,繪制圓角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();

移除舊的樣式

//移除原有樣式
style()->unpolish(ui->btn);
//重新設定新的該控件的樣式。
style()->polish(ui->btn);
           

擷取類的屬性

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,
...
           

根據作業系統位數判斷加載

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 = parentWidget.findChildren<QWidget *>("widgetname");
//查找所有QPushButton
QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();
//查找一級子控件,不然會一直周遊所有子控件
QList<QPushButton *> childButtons = parentWidget.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樣式表檔案比較好,統一管理。

如果出現Z-order assignment: is not a valid widget.錯誤提示,用記事本打開對應的ui檔案,找到為空的地方,删除即可。

善于利用QComboBox的addItem的第二個參數設定使用者資料,可以實作很多效果,使用itemData取出來。

如果用了webengine子產品,釋出程式的時候帶上QtWebEngineProcess.exe+translations檔案夾+resources檔案夾。

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寫法:./HelloQt --platform xcb

Qtcreator軟體的配置檔案存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有時候如果發現出問題了,将這個檔案夾删除後打開creator自動重新生成即可。

QMediaPlayer是個殼,依賴本地解碼器,視訊這塊預設基本上就播放個MP4,如果要支援其他格式需要下載下傳k-lite或者LAV Filters安裝即可(WIN上,其他系統上自行搜尋)。如果需要做功能強勁的播放器,初學者建議用vlc、mpv,終極大法用ffmpeg。

判斷編譯器類型、編譯器版本、作業系統。

//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最小化後恢複界面假死當機,加上代碼

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);
#endif
           

如果運作程式出現 Fault tolerant heap shim applied to current process. This is usually due to previous crashes. 錯誤。 辦法:打開系統資料庫,找到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可以設定滾動的一些參數
           

如果使用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->setStyleSheet("qproperty-text:hello;");
ui->label->setProperty("text", "hello");
ui->label->setText("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);

在某些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)等。

很多人問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]");
           

在我們使用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);
#else
    ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
#endif
           

從Qt4轉到Qt5,有些類的方法已經廢棄或者過時了,如果想要在Qt5中啟用Qt4的方法,比如QHeadVew的setMovable,可以在你的pro或者pri檔案中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0

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.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中的沒有顯示的頁面的控件,你去擷取寬高很可能也是不正确的,萬無一失的辦法就是首次顯示以後去擷取。

資料庫處理一般建議在主線程,如果非要在其他線程,務必記得打開資料庫也要在那個線程,即在那個線程使用資料庫就在那個線程打開,不能打開資料庫在主線程,執行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);
#else
    void 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_UseDesktopOpenGL
    QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
    //QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#endif
    QApplication 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 0
    while(!file.atEnd()) {
        QString line = file.readLine();
        appendName(line);
    }
#else
    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine();
        appendName(line);
    }
#endif
    file.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());
#endif
        QString 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。

如果需要在尺寸改變的時候不重繪窗體,則設定屬性即可 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);
#else
    tableView->horizontalHeader()->setClickable(false);
#endif

    //滑鼠按下即進入編輯模式
    if (edit) {
        tableView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked);
    } else {
        tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    }
}
           

在一些大的項目中,可能嵌套了很多子項目,有時候會遇到子項目依賴其他子項目的時候,比如一部分子項目用來生成動态庫,一部分子項目依賴這個動态庫進行編譯,此時就需要子項目按照順序編譯。

TEMPLATE = subdirs
#設定ordered參數以後會依次編譯 demo designer examples
CONFIG  += ordered
SUBDIRS += demo
SUBDIRS += designer
SUBDIRS += examples
           

MSVC編譯器的選擇說明

1.如果是32位的Qt則編譯器選擇x86開頭的

2.如果是64位的Qt則編譯器選擇amd64開頭的

3.具體是看安裝的Qt建構套件版本以及目标運作平台的系統位數和架構

4.一般現在的電腦預設以64位的居多,選擇amd64即可

5.如果使用者需要相容32位的系統則建議選擇32位的Qt,這樣即可在32位也可以在64位系統運作

6.諸葛大佬補充: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);
           

很多初學者甚至幾年工作經驗的人,對多線程有很深的誤解和濫用,尤其是在序列槽和網絡通信這塊,什麼都往多線程裡面丢,一旦遇到界面卡,就把資料收發啥的都搞到多線程裡面去,殊不知絕大部分時候那根本沒啥用,因為沒找到出問題的根源。

  1. 如果你沒有使用wait***函數的話,大部分的界面卡都出在資料處理和展示中,比如傳過來的是一張圖檔的資料,你需要将這些資料轉成圖檔,這個肯定是耗時的;
  2. 還有就是就收到的資料曲線繪制出來,如果過于頻繁或者間隔過短,肯定會給UI造成很大的壓力的,最好的辦法是解決如何不要頻繁繪制UI比如合并資料一起繪制等;
  3. 如果是因為繪制UI造成的卡,那多線程也是沒啥用的,因為UI隻能在主線程;
  4. 序列槽和網絡的資料收發預設都是異步的,由作業系統排程的,如果資料處理複雜而且資料量大,你要做的是将資料處理放到多線程中;
  5. 如果沒有嚴格的資料同步需求,根本不需要調用wait***之類的函數來立即發送和接收資料,實際需求中大部分的應用場景其實異步收發資料就足夠了;
  6. 有嚴格資料同步需求的場景還是放到多線程會好一些,不然你wait***就卡在那邊了;
  7. 多線程是需要占用系統資源的,理論上來說,如果線程數量超過了CPU的核心數量,其實多線程排程可能花費的時間更多,各位在使用過程中要權衡利弊;

在嵌入式linux上,如果設定了無邊框窗體,而該窗體中又有文本框之類的,發現沒法産生焦點進行輸入,此時需要主動激活窗體才行。

//這種方式設定的無邊框窗體在嵌入式裝置上無法産生焦點
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);

//需要在show以後主動激活窗體
w->show();
w->activateWindow();
           

QString的replace函數會改變原字元串,切記,他在傳回替換後的新字元串的同時也會改變原字元串,我的乖乖!

QGraphicsEffect類的相關效果很炫,可以實作很多效果比如透明、漸變、陰影等,但是該類很耗CPU,如果不是特别需要一般不建議用,就算用也是要用在該部件後期不會發生頻繁繪制的場景,不然會讓你哭暈在廁所。

在不同的平台上檔案路徑的斜杠也是不一樣的,比如linux系統一般都是 / 斜杠,而在windows上都是 \ 兩個反斜杠,Qt本身程式内部無論在win還是linux都支援 / 斜杠的路徑,但是一些第三方庫的話可能需要轉換成對應系統的路徑,這就需要用到斜杠轉換,Qt當然内置類方法。

QString path = "C:/temp/test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:\\temp\\test.txt

QString path = "C:\\temp\\test.txt";
path = QDir::toNativeSeparators(path);
//輸出 C:/temp/test.txt
           

巧用QMetaObject::invokeMethod方法可以實作很多效果,包括同步和異步執行,比如有個應用場景是在回調中,需要異步調用一個public函數,如果直接調用的話會發現不成功,此時需要使用 QMetaObject::invokeMethod(obj, “fun”, Qt::QueuedConnection); 這種方式來就可以。invokeMethod函數有很多重載參數,可以傳入傳回值和執行方法的參數等。

Qt5中的信号是public的,可以在需要的地方直接emit即可,而在Qt4中信号是protected的,不能直接使用,需要定義一個public函數來emit。

其他經驗

Qt界的中文亂碼問題,版本衆多導緻的如何選擇安裝包問題,如何打包釋出程式的問題,堪稱Qt界的三座大山!

在Qt的學習過程中,學會檢視對應類的頭檔案是一個好習慣,如果在該類的頭檔案沒有找到對應的函數,可以去他的父類中找找,實在不行還有爺爺類,肯定能找到的。通過頭檔案你會發現很多函數接口其實Qt已經幫我們封裝好了,有空還可以閱讀下他的實作代碼。

Qt安裝目錄下的Examples目錄下的例子,看完學完,月薪20K起步;Qt常用類的頭檔案的函數看完學完使用一遍并加以融會貫通,月薪30K起步。

Qt在開發階段不支援中文目錄,切記,這是無數人可能犯的錯誤,在安裝Qt內建開發環境以及編譯器的時候,務必記得目錄必須英文,否則很可能不正常,建議盡量用預設的安裝位置。

如果出現崩潰和段錯誤,80%都是因為要麼越界,要麼未初始化,死扣這兩點,80%的問題解決了。

Qt一共有幾百個版本,關于如何選擇Qt版本的問題,我一般保留四個版本,為了相容Qt4用4.8.7,最後的支援XP的版本5.7.0,最新的長期支援版本比如5.12,最高的新版本比如5.14.2。強烈不建議使用4.7以前和5.0到5.3之間的版本,太多bug和坑,穩定性和相容性相比于之後的版本相當差,能換就換,不能換睡服上司也要換。

繼續閱讀