2 摘要内容

20. 在开发时, 无论是出于维护的便捷性, 还是节省内存资源的考虑, 都应该有一个 qss 文件来存放所有 的样式表, 而不应该将 setStyleSheet 写的到处都是。如果是初学阶段或者测试阶段可以直接UI上右 键设置样式表,正式项目还是建议统一到一个qss样式表文件比较好,统一管理。

33. Qt最小化后恢复界面可能会出现假死冻结现象,加上代码

void showEvent(QShowEvent *e)
{
setAttribute(Qt::WA_Mapped);
QWidget::showEvent(e);
}

34. 获取标题栏高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight点进去你 会发现新大陆,有一堆玩意在里面。

46. 巧妙的用QEventLoop开启事件循环,可以使得很多同步获取返回结果而不阻塞界面。查看源码得 知,原来QEventLoop内部新建了线程执行。

QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();

47. 多种预定义变量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目 录,在pro文件中加一行 CONFIG -= debug_and_release。

48. 新版的Qtcreator增强了语法检查,会弹出很多警告提示等,可以在插件列表中关闭clang打头的几 个即可,Help》About Plugins。也可以设置代码检查级别,Tools》Options 》C++ 》Code Model。

53. 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);
}

56. 在很多网络应用程序,需要自定义心跳包来保持连接,不然断电或者非法关闭程序,对方识别不 到,需要进行超时检测,但是有些程序没有提供心跳协议,此时需要启用系统层的保活程序,此方 法适用于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));

58. 非常不建议tr中包含中文,尽管现在的新版Qt支持中文到其他语言的翻译,但是很不规范,也不知 道TMD是谁教的(后面发现我在刚学Qt的时候也发布了一些demo到网上也是tr包含中文的,当时 就狠狠的打了自己一巴掌),tr的本意是包含英文,然后翻译到其他语言比如中文,现在大量的初 学者滥用tr,如果没有翻译的需求,禁用tr,tr需要开销的,Qt默认会认为他需要翻译,会额外进 行特殊处理。

61. 很多人问Qt嵌入式平台用哪个好,这里统一回答(当前时间节点2018年):imx6+335x比较稳 定,性能高就用RK3288 RK3399,便宜的话就用全志H3,玩一玩可以用树莓派香橙派。

63. Qt打包发布,有很多办法,Qt5以后提供了打包工具windeployqt(linux上为linuxdeployqt,mac 上为macdeployqt)可以很方便的将应用程序打包,使用下来发现也不是万能的,有时候会多打包 一些没有依赖的文件,有时候又会忘记打包一些插件尤其是用了qml的情况下,而且不能识别第三 方库,比如程序依赖ffmpeg,则对应的库需要自行拷贝,终极大法就是将你的可执行文件复制到 Qt安装目录下的bin目录,然后整个一起打包,挨个删除不大可能依赖的组件,直到删到正常运行 为止。

75. 将 QApplication::style() 对应的drawPrimitive、drawControl、drawItemText、 drawItemPixmap等几个方法用熟悉了,再结合QStyleOption属性,可以玩转各种自定义委托,还 可以直接使用paint函数中的painter进行各种绘制,各种牛逼的表格、树状列表、下拉框等,绝对 屌炸天。QApplication::style()->drawControl 的第4个参数如果不设置,则绘制出来的控件不会应 用样式表。

78. 很多时候需要在已知背景色的情况下,能够清晰的绘制文字,这个时候需要计算对应的文字颜色。

//根据背景色自动计算合适的前景色
double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 *
color.blue()) / 255;
QColor textColor = gray > 0.5 ? Qt::black : Qt::white;

80. 从Qt4转到Qt5,有些类的方法已经废弃或者过时了,如果想要在Qt5中启用Qt4的方法,比如 QHeadVew的setMovable,可以在你的pro或者pri文件中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0

83. Qt中的QString和const char *之间转换,最好用toStdString().c_str()而不是 toLocal8Bit().constData(),比如在setProperty中如果用后者,字符串中文就会不正确,英文正 常。

84. Qt的信号槽机制非常牛逼,也是Qt的独特的核心功能之一,有时候我们在很多窗体中传递信号来实 现更新或者处理,如果窗体层级比较多,比如窗体A的父类是窗体B,窗体B的父类是窗体C,窗体C 有个子窗体D,如果窗体A一个信号要传递给窗体D,问题来了,必须先经过窗体B中转到窗体C再 到窗体D才行,这样的话各种信号关联信号的connect会非常多而且管理起来比较乱,可以考虑增 加一个全局的单例类AppEvent,公共的信号放这里,然后窗体A对应信号绑定到AppEvent,窗体D 绑定AppEvent的信号到对应的槽函数即可,干净清爽整洁。

86. Qt中有个全局的焦点切换信号focusChanged,可以用它做自定义的输入法。Qt4中默认会安装输 入法上下文,比如在main函数打印a.inputContext会显示值,这个默认安装的输入法上下文,会拦 截两个牛逼的信号QEvent::RequestSoftwareInputPanel和QEvent::CloseSoftwareInputPanel, 以至于就算你安装了全局的事件过滤器依然识别不到这两个信号,你只需要在main函数执行 a.setInputContext(0)即可,意思是安装输入法上下文为空。Qt5.7以后提供了内置的输入法,可以 通过在main函数最前面加上 qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); 来 启用。

88. Qt源码中内置了一些未公开的不能直接使用的黑科技,都藏在对应模块的private中,比如guiprivate 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();

92. 新版的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

94. 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);

95. QStyle内置了很多方法用处很大,比如精确获取滑动条鼠标按下处的值。

QStyle::sliderValueFromPosition(minimum(), maximum(), event->x(), width());

97. 用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();
}
}

98. QString内置了很多转换函数,比如可以调用toDouble转为double数据,但是当你转完并打印的时 候你会发现精确少了,只剩下三位了,其实原始数据还是完整的精确度的,只是打印的时候优化成 了三位,如果要保证完整的精确度,可以调用 qSetRealNumberPrecision 函数设置精确度位数即 可。

QString s1, s2;
s1 = "666.5567124";
s2.setNum(888.5632123, 'f', 7);
qDebug() << qSetRealNumberPrecision(10) << s1.toDouble() << s2.toDouble();

100. setPixmap是最糟糕的贴图方式,一般只用来简单的不是很频繁的贴图,频繁的建议painter绘制, 默认双缓冲,在高级点用opengl绘制,利用GPU。

102. 默认程序中获取焦点以后会有虚边框,如果看着觉得碍眼不舒服可以去掉,设置样式即可: setStyleSheet("*{outline:0px;}");

108. 在嵌入式linux上,如果设置了无边框窗体,而该窗体中又有文本框之类的,发现没法产生焦点进行 输入,此时需要主动激活窗体才行。

//这种方式设置的无边框窗体在嵌入式设备上无法产生焦点
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint |
Qt::X11BypassWindowManagerHint);
//需要在show以后主动激活窗体
w->show();
w->activateWindow();

109. QString的replace函数会改变原字符串,切记,他在返回替换后的新字符串的同时也会改变原字符 串,我的乖乖!

111. 在不同的平台上文件路径的斜杠也是不一样的,比如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

112. 巧用QMetaObject::invokeMethod方法可以实现很多效果,包括同步和异步执行,很大程度上解 决了跨线程处理信号槽的问题。比如有个应用场景是在回调中,需要异步调用一个public函数,如 果直接调用的话会发现不成功,此时需要使用 QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection); 这种方式来就可以。

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;
}

 116. c++11新引入了原始字符串格式,用户避免在字符串中加入转义字符\,可以用于表示json字符串等 场景。

QString s1 = R"(test\001.jpg)";
s1.replace("\\", "#");
qDebug()<< s1;
//结果 test#001.jpg

117. 安卓上打印信息建议使用 qInfo() 而不是 qDebug() ,qInfo()才有效果。

123. QVideoWidget播放视频,可能会遇到画面闪烁的情况,播放视频的窗体需要设置个属性。

QVideoWidget *videoWidget = new QVideoWidget;
videoWidget->setAttribute(Qt::WA_OpaquePaintEvent);

125. 在主QWidget窗体如果直接qss设置背景图片的话,预览是可见的,运行并没有效果,你需要在这 个主widget上再放个widget,在新的widget上设置qss图片就行,而如果是Dialog或者 QMainWindow窗体是支持直接设置qss背景图的,预览和运行效果一致。

126. Qt提供了qDebug机制直接输出打印信息,这个弥补了QtCreator调试很鸡肋的缺点,而且无缝对 接日志钩子,使得现场运行期间按照预定的打印信息输出到日志文件,有时候在开发阶段,又不想 要看到一堆堆的打印信息,最笨的做法是一行行注释掉qdebug的地方,其实还可以直接pro中加上 一行来禁用整个项目的qdebug输出。

#禁用qdebug打印输出
DEFINES += QT_NO_DEBUG_OUTPUT

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

129. Qt对操作系统层的消息也做了很多的封装,可以直接拿到进行处理(如果需要拦截处理要用对应操 作系统的API才行比如鼠标键盘钩子),比如系统休眠和唤醒做一些处理。

//主窗体头文件
protected:
bool nativeEvent(const QByteArray &eventType, void *message, long
*result);
#ifdef Q_OS_WIN
bool winEvent(MSG *message, long *result);
#endif
//主窗体实现函数
#ifdef Q_OS_WIN
#include "Windows.h"
#endif
bool frmMain::nativeEvent(const QByteArray &eventType, void *message, long
*result)
{
if (eventType == "windows_generic_MSG") {
#ifdef Q_OS_WIN
MSG *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

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

133. qDebug输出打印信息,默认会完整打印转义字符,例如:\ " \t \n" 等,所以当你发现你明明设置 了转义字符以后打印确还是转义前的字符,这就懵逼了,其实这是qdebug为了方便调试将各种字 符都打印输出。无可否认,很多时候,我们极其兴奋的享受着Qt带来的各种轮子各种便利,但是偶 尔,稍不留意,这些便利可能也会坑你一把。要做的就是擦亮眼睛,时刻谨慎,一步一个脚印踏踏 实实码代码。

QString s1 = R"(\:device0)";
//TNND居然输出的是 \\:device0
qDebug() << s1;
//这次终于正确的输出 \:device0
qDebug().noquote() << s1;

134. 很多人有疑问为何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>

135. 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();
}

137. 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.groupEna
bled)
.arg(functionInfo.action).arg(functionInfo.group);
debug << info;
return debug;
}
};

138. 对高分屏不同缩放比例的自适应处理方法。

//方法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
//下面这行用来解决Qt高DPI下文字显示有锯齿的问题
WindowsArguments = fontengine=freetype
//方法3:在main函数最前面设置Qt内部的环境变量
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1.5");
//方法4:新版本的Qt比如Qt5.14修正了对高分屏的处理支持不是整数的缩放
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorR
oundingPolicy::PassThrough);
//禁用缩放
//测试发现AA_Use96Dpi属性在Qt5.9以上版本完全正常,以下版本比如5.7有部分控件在175%缩放不
正常比如QTextEdit,需要外层套个widget才行。
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
QApplication::setAttribute(Qt::AA_Use96Dpi);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactor
RoundingPolicy::Floor);
#endif

140. QMainWindow的分割线默认尺寸比较大,有时候想设置小一点或者不想要,最开始的时候以为是 QSplitter,打印所有子元素找遍了也没找到影子,最后发现样式表中有对应设置的内容。

//真的是做梦也没想到要这样设置
QMainWindow::separator{width:1px;height:1px;margin:1px;padding:1px;background
:#FF0000;}

142. 在停靠窗体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);
#endif
QApplication a(argc, argv);
...
}

145. 通过酷码大哥(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

146. 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();
}
148. QtCreator集成开发环境,也内置了对快速添加注释的支持,比如最常用的在头文件开头添加一大
段通用模板的注释,标注文件创建者、时间等信息。
菜单->工具->选项->文本编辑器->右侧tab页面片段(snippets);
组选择C++, 可以看到这里面已经内置了不少定义比如foreach,可以依葫芦画瓢;
添加一个片段, 比如名字是fun, 触发种类是这个片段的简单描述;
当我们在代码文件中键入fun时, 会自动弹出智能提醒, 选择我们的代码片段回车, 自动填充代码;
按tab可以在变量间切换, 输入完成后回车, 完成编辑;
/**
* @brief $name$
* @param $param$
* @author feiyangqingyun
* @date $date$
*/
$ret$ $name$($param$)
{
$$
}

150. Qt样式表有多种运行机制,主要是考虑到各种需求场景,继承自QWidget的类和qApp类都支持 setStyleSheet方法,还可以统一将样式表放在文件,或者将样式文件加入到资源文件。

斗气:qss内容写得到处都是,哪里需要就写在哪里,各种控件调用 setStyleSheet方法传入样式表
内容,或者直接对应控件鼠标右键弹出菜单选择改变样式表填入内容;
斗者:qss内容放在文件,读取文件内容设置样式表,程序发布的时候带上qss文件;
斗师:qss文件作为资源文件放到qrc文件,直接编译到可执行文件中,防止篡改;
斗灵:在qss文件中自定义一些标志充当变量使用,读取以后替换对应的变量为颜色值,类似动态
换肤;
斗王:放在文件容易被篡改,集成到可执行文件不够灵活,一旦样式表更新需要重新编译文件,如
何做到既能只更新样式表文件,又不需要重新编译可执行文件,又能防止被篡改:采用rcc命令将
资源文件编译生成二进制,只需要替换该二进制文件即可;
斗皇:继承qstyle类自己实现完成所有样式接口,统一整体风格,大名鼎鼎的UOS系统默认规则就
是如此,不允许用样式表,全部painter绘制;

151. 当Qt中编译资源文件太大时,效率很低,或者需要修改资源文件中的文件比如图片、样式表等,需 要重新编译可执行文件,这样很不友好,当然Qt都给我们考虑好了策略,此时可以将资源文件转化 为二进制的rcc文件,这样就将资源文件单独出来了,可在需要的时候动态加载。

//Qt中使用二进制资源文件方法如下
//将qrc编译为二进制文件rcc,在控制台执行下列命令
rcc -binary main.qrc -o main.rcc
//在应用程序中注册资源,一般在main函数启动后就注册
QResource::registerResource(qApp->applicationDirPath() + "/main.rcc");

152. 关于设置字体,大概都会经历一个误区,本来是打算设置整个窗体包括子控件的字体大小的,结果 发现只有主窗体自己应用了字体而子控件没有。

//假设窗体中有子控件,默认字体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);

153. 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");

155. 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());

156. Qt的UI界面在resize以后有个BUG,悬停样式没有取消掉,需要主动模拟鼠标动一下。

void frmMain::on_btnMenu_Max_clicked()
{
......
//最大化以后有个BUG,悬停样式没有取消掉,需要主动模拟鼠标动一下
QEvent event(QEvent::Leave);
QApplication::sendEvent(ui->btnMenu_Max, &event);
}

158. Qt的文本控件比如QTextEdit默认加载大文本比如10MB的文本,很容易卡死甚至崩溃,那是因为 默认一个属性开启了,需要屏蔽掉就好很多。

ui->textEdit->setUndoRedoEnabled(false)
159. 其他几点常规小经验,本人在这几个地方摔跤过很多次。
有返回值的函数,一定要主动return返回值,有部分编译器在没有返回值的情况下也能正常编译通
过,但是运行的时候会出问题,得不到想要的结果,因为没有return对应的值。
定义的局部变量,主动给定个初始值,是个必须养成的好习惯,不然编译器给的初始值很可能不是
你想要的,比如int变量默认0,有时候随机变成一个很大的数值,bool变量的初始值不同编译器不
同值,有些是true有些是false,主动给一个初始值更可靠。
某些函数参数很多,而且后期可能还会修改和增加,这就导致了源头修改以后,关联信号槽的地方
也要修改,参数类型和位置必须保持完全一致,对应槽函数处理也要修改等,改动的工作量非常大
而且极不友好,所以对于非固定参数的函数,建议用结构体,这样非常容易增加其他的参数,而且
不用修改信号槽关联和信号槽函数定义等,比如学生信息表、商品信息表作为参数传输,最佳方案
就是结构体。

160. 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{minwidth:%1px;}").arg(100);
list << QString("QTabWidget[tabCount=\"3\"]>QTabBar::tab{minwidth:%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;qpropertydocumentMode:true;qproperty-expanding:true;}");
//在5.9以前开启这个设置后,貌似选项卡个数按照真实个数+1计算宽度,也就是永远会留空一个tab的
占位。
//5.9以后貌似修复了这个BUG,按照理想中的拉伸填充等分设置tab的宽度。

162. Qt提供了N种窗体属性比如无边框属性FramelessWindowHint、不在任务栏显示属性Tool等,有 时候我们需要对窗口的属性进行动态设置,比如增加一个属性或者移除一个属性,Qt5.9以前需要 拿到原有的窗体属性做运算,后面可以用新的方法。

//增加一个无边框属性
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
//移除无边框属性
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
//下面是5.9以后新增的方法
//增加一个无边框属性到窗体属性链表
setWindowFlag(Qt::FramelessWindowHint, true);
//从窗体属性链表中移除无边框属性
setWindowFlag(Qt::FramelessWindowHint, false);

164. 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 头文件,你会发现新大陆。

166. 有时候需要暂时停止某个控件发射信号(比如下拉框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);

167. 项目代码文件数量如果很多的话,全部包含在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"。

168. 在网络通信中,无论是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);

169. 关于网络通信,tcp和udp是两种不同的底层的网络通信协议,两者监听和通信的端口互不相干的, 不同的协议或者不同的网卡IP地址可以用相同的端口。之前有个人说他的电脑居然可以监听一样的 端口进行通信,颠覆了他以前的认知,书上说的明明是不可以相同端口的,后面远程一看原来选择 的不同的网卡IP地址,当然可以的咯。

tcp对网卡1监听了端口6000,还可以对网卡2监听端口6000。
tcp对网卡1监听了端口6000,udp对网卡1还可以继续监听端口6000。
tcp对网卡1监听了端口6000,在网卡1上其他tcp只能监听6000以外的端口。
udp协议也是上面的逻辑。
170. 开源的图表控件QCustomPlot很经典,作者至少是八星斗圣级别,在曲线数据展示这块性能彪悍,
总结了一些容易忽略的经验要点。
可以将XY轴对调,然后形成横向的效果,无论是曲线图还是柱状图,分组图、堆积图等,都支持这
个特性。
不需要的提示图例可以调用 legend->removeItem 进行移除。
两条曲线可以调用 setChannelFillGraph 设置合并为一个面积区域。
可以关闭抗锯齿 setAntialiased 加快绘制速度。
可以设置不同的线条样式(setLineStyle)、数据样式(setScatterStyle)。
坐标轴的箭头样式可更换 setUpperEnding。
可以用 QCPBarsGroup 实现柱状分组图,这个类在官方demo中没有,所以非常容易忽略。
V2.0开始支持数据排序设置,默认是交给QCustomPlot排序,也可以设置setData第三个参数为
true表示已经排序过,这样可以绘制往回走的曲线。
频繁绘制数据可以设置排队绘制参数 replot(QCustomPlot::rpQueuedReplot),可以避免重复的
replot和提高性能。如果不开启很可能绘制出错。
//对调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->setNoAntialiasingOnDrag(true);
customPlot->graph()->setAntialiased(false);
customPlot->graph()->setAntialiasedFill(false);
customPlot->graph()->setAntialiasedScatters(false);
//设置快速绘制可以大大加快画笔宽度大于1的线条
customPlot->setPlottingHint(QCP::phFastPolylines);
//多种设置数据的方法
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);
//绘制往回走的曲线
QVector<double> keys, values;
keys << 0 << 1 << 2 << 3 << 4 << 5 << 4 << 3;
values << 5 << 4 << 6 << 7 << 7 << 6 << 5 << 4;
customPlot->graph(0)->setData(keys, values, true);
//频繁绘制数据开启排队绘制可以提高性能
customPlot->replot(QCustomPlot::rpQueuedReplot);
QCPAxis *axis = customPlot->xAxis;
double lower = axis->range().lower;
double upper = axis->range().upper;
double origin = (upper - lower) / 2;
//设置刻度线按照设置优先而不是可读性优先
axis->ticker()->setTickStepStrategy(QCPAxisTicker::tssMeetTickCount);
//设置原点值为范围值的中心点
axis->ticker()->setTickOrigin(origin);

171. 在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");
#else
QTextCodec *code = QTextCodec::codecForName("gbk");
#endif
const char *name = code->fromUnicode(fileName).constData();
#else
const char *name = fileName.toUtf8().constData();
#endif

172. 在查阅和学习Qt源码的过程中,发现了一些趋势和改变。 数据类型这块尽量用Qt内部的数据类型,哪怕是重定义过的比如quint8其实unsigned char,qreal 就是double,以前翻看源码的时候可能还有些是double,现在慢慢改成了qreal。 循环结构用 for(;;) 替代 while(1),因为转成汇编指令后 for(;;) 只有一条指令而 while(1) 确有4条, 指令少不占用寄存器而且不用跳转,理论上速度要更快。 其实Qt中就重定义了 forever 关键字表示 for(;;) ,我的乖乖,想的真周到。 自动c++11以及后续的标准都支持auto万能数据类型,发现Qt的源码中也慢慢的改成了auto,这样 加快了编写代码的效率,不用自己去指定数据类型而是让编译器自己推导数据类型。而且其实也不 影响编译器编译的速度,因为无论指定和没有指定数据类型,编译器都要推导右侧的数据类型进行 判断。不过有个缺点就是影响了阅读代码的成本,很多时候需要自己去理解推导。

173. Qt中设置或者打开加载本地文件需要用到QUrl类,本地文件建议加上 file:/// 前缀。

QString url = "file:///c:/1.html";
//浏览器控件打开本地网页文件
webView->setUrl(QUrl(url));
//打开本地网页文件,下面两种方法都可以
QDesktopServices::openUrl(QUrl::fromLocalFile(url));
QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode));

174. 在网络请求中经常涉及到超时时间的问题,因为默认是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);
#endif
QNetworkReply *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();
}
}

175. Qt中基本上有三大类型的项目,控制台项目对应QCoreApplication、传统QWidget界面程序对应 QApplication、quick/qml项目程序对应QGuiApplication。有很多属性的开启需要在main函数的 最前面执行才有效果,比如开启高分屏支持、设置opengl模式等。不同类型的项目需要对应的 QApplication。

//如果是控制台程序则下面的QApplication换成QCoreApplication
//如果是quick/qml程序则下面的QApplication换成QGuiApplication
int main(int argc, char *argv[])
{
//可以用下面这行测试Qt自带的输入法 qtvirtualkeyboard
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
//设置不应用操作系统设置比如字体
QApplication::setDesktopSettingsAware(false);
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
//设置高分屏缩放舍入策略
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRou
ndingPolicy::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_UseOpenGLES
AA_UseSoftwareOpenGL
//在一些很旧的设备上或者对opengl支持很低的设备上需要使用AA_UseOpenGLES表示禁用硬件
加速
//如果开启的是AA_UseOpenGLES则无法使用硬件加速比如ffmpeg的dxva2
//QApplication::setAttribute(Qt::AA_UseOpenGLES);
//设置opengl共享上下文
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
QApplication a(argc, argv);
QWidget w;
w.show();
return a.exec();
}

177. 很多时候需要在窗体首次显示的时候加载一些东西,而且只加载一次,当窗体再次显示的时候不加 载。为什么不是在构造函数呢?因为很多玩意都是要在显示后才能确定,比如控件的尺寸,部分样 式表的应用。

void Widget::showEvent(QShowEvent *)
{
static bool isLoad = false;
if (!isLoad) {
isLoad = true;
//执行对应的处理
}
}

179. 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");

180. 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();

182. 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);
#else
qInstallMsgHandler(Log);
#endif
}
//卸载日志钩子
void SaveLog::stop()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
qInstallMessageHandler(0);
#else
qInstallMsgHandler(0);
#endif
}

184. 由于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
185. 在使用QString转换到char *或者const char *的时候,务必记得分两步来完成,血的教训,在一个
场景中,就因为没有分两步走,现象是msvc的debug异常release正常,mingw和gcc的debug和
release都正常,这就很无语了,找问题找半天,对比法排除法按道理要么都有问题才对。
转换前QString的内容无关中文还是英文,要出问题都一样。
转换中QByteArray无关具体类型,toUtf8、toLatin1、toLocal8Bit、toStdString等方法,要出问
题都一样。
转换后无关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();

190. 关于QTableView(采用model数据源)、QTableWidget列名列宽设置,有时候发现没有起作用, 原来是对代码设置的顺序有要求,比如setColumnWidth前必须先setColumnCount,不然列数都 没有,哪来的列宽,包括setHorizontalHeaderLabels设置列标题集合也是,前提都要先有列。

void frmSimple::initForm()
{
//实例化数据模型
model = new QStandardItemModel(this);
//设置行数列数
row = 100;
column = 10;
//设置列名列宽
for (int i = 0; i < column; ++i) {
columnNames << QString("列%1").arg(i + 1);
columnWidths << 60;
}
}
void frmSimple::on_btnLoad1_clicked()
{
//先设置数据模型,否则 setColumnWidth 不起作用
ui->tableView->setModel(model);
//设置列数及列标题和列宽
model->setColumnCount(column);
//简便方法设置列标题集合
model->setHorizontalHeaderLabels(columnNames);
for (int i = 0; i < column; ++i) {
ui->tableView->setColumnWidth(i, columnWidths.at(i));
}
//循环添加行数据
QDateTime now = QDateTime::currentDateTime();
model->setRowCount(row);
for (int i = 0; i < row; ++i) {
for (int j = 0; j < column; ++j) {
QStandardItem *item = new QStandardItem;
//最后一列显示时间区别开来
if (j == column - 1) {
item->setText(now.addSecs(i).toString("yyyy-MM-dd
HH:mm:ss"));
} else {
item->setText(QString("%1_%2").arg(i + 1).arg(j + 1));
}
model->setItem(i, j, item);
}
}
}
void frmSimple::on_btnLoad2_clicked()
{
//设置列标题和列数及列宽
ui->tableWidget->setColumnCount(column);
//简便方法设置列标题集合
ui->tableWidget->setHorizontalHeaderLabels(columnNames);
for (int i = 0; i < column; ++i) {
ui->tableWidget->setColumnWidth(i, columnWidths.at(i));
}
//添加数据
QDateTime now = QDateTime::currentDateTime();
ui->tableWidget->setRowCount(row);
for (int i = 0; i < row; ++i) {
for (int j = 0; j < column; ++j) {
QTableWidgetItem *item = new QTableWidgetItem;
//最后一列显示时间区别开来
if (j == column - 1) {
item->setText(now.addSecs(i).toString("yyyy-MM-dd
HH:mm:ss"));
} else {
item->setText(QString("%1_%2").arg(i + 1).arg(j + 1));
}
ui->tableWidget->setItem(i, j, item);
}
}
}

191. 关于QList队列的处理中,我们最常用的就是调用append函数添加item,往前插入item很多人第一 印象就是调用insert(0,xxx)来插入,其实QList完全提供了往前追加item的函数prepend、 push_front。

QStringList list;
list << "aaa" << "bbb" << "ccc";
//往后追加 等价于 append
list.push_back("ddd");
//往前追加 等价于 prepend
list.push_front("xxx");
//往后追加
list.append("ddd");
//往前追加
list.prepend("xxx");
//指定第一个位置插入 等价于 prepend
list.insert(0, "xxx");
//输出 QList("xxx", "aaa", "bbb", "ccc", "ddd")
qDebug() << list;

192. Qt内置了一些QList、QMap、QHash相关的类型,可以直接用,不用自己写个长长的类型。

//qwindowdefs.h
typedef QList<QWidget *> QWidgetList;
typedef QList<QWindow *> QWindowList;
typedef QHash<WId, QWidget *> QWidgetMapper;
typedef QSet<QWidget *> QWidgetSet;
//qmetatype.h
typedef QList<QVariant> QVariantList;
typedef QMap<QString, QVariant> QVariantMap;
typedef QHash<QString, QVariant> QVariantHash;
typedef QList<QByteArray> QByteArrayList;

193. Qt的布局的边距间隔,如果在没有改动过的情况下,是会根据系统分辨率以及缩放比来决定对应的 默认值,是变化的,比如在1080P分辨率是9px,在2K分辨率又变成了11px,所有你会发现你在 1080P电脑编译的程序,明明看到的是6px、9px,怎么到2K、4K分辨率下间隔和边距就变得好 大,如果要保持无论何种分辨率都一样,你需要手动重新设置这些值,这里有个坑,比如默认是是 9,你想其他分辨率也是9,你必须先把9改成其他值比如10,然后再改成9,这样才表示真的改 动,你直接9改成9是不会变化的,在属性设计器中右侧有个小箭头恢复值的,也是灰色,只有加深 显示,并且出现了恢复默认值箭头,才表示你确实是改过了值。

194. Qt对高分屏以及dpi缩放的支持越来越成熟,在Qt4时代默认的策略就是跟随系统的缩放,从Qt5.6 开始提供了 AA_EnableHighDpiScaling 的属性设置开启高分屏,到了5.14以后还可以指定缩放的 策略 HighDpiScaleFactorRoundingPolicy 比如支持浮点数的缩放比而不是之前的整数倍,从Qt6 开始默认永远开启了 AA_EnableHighDpiScaling 属性,没法取消。很多时候我们需要两种模式, 一种就是永远不应用高分屏及缩放,一种就是自动应用高分屏及缩放。

//永远不应用高分屏及缩放
int main(int argc, char *argv[])
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
QApplication::setAttribute(Qt::AA_Use96Dpi);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRou
ndingPolicy::Floor);
#endif
QApplication a(argc, argv);
....
return a.exec();
}
//自动应用高分屏及缩放
//方法很多,综合对比下来还是采用配置文件指定缩放策略最适中。
//新建qt.conf文件放到可执行文件同一目录
[Platforms]
WindowsArguments = dpiawareness=0
//有时候想让用户去选择何种策略,需要开启高分屏的之后只需要将qt.conf文件放到可执行文件同一
目录即可,就算代码中设置了不应用高分屏及缩放,也无效,也是优先取qt.conf文件的策略。

195. 关于QSS要注意的坑。

qss源自css,相当于css的一个子集,主要支持的是css2标准,很多网上的css3的标准的写法在qss
这里是不生效的,所以不要大惊小怪。
qss也不是完全支持所有的css2,比如text-align官方文档就有说明,只支持 QPushButton and
QProgressBar,务必看清楚。
有时候偷懒直接来一句 *{xxx},你会发现大部分是应用了,也有小部分或者极个别没有应用,你可
能需要在对应的窗体中 this->setStyleSheet() 来设置。
qss的执行是有优先级的,如果没有指定父对象,则对所有的应用,比如在窗体widget中
{color:#ff0000;} 这样会对widget以及widget的所有子对象应用该样式,这种问题各大群每周都有
人问,你会发现各种奇奇怪怪的异样不正常,怎么办呢,你需要指定类名或者对象名,比如
#widget{color:#ff0000;} 这样就只会对widget对象应用该样式,另一种写法
QWidget#widget{color:#ff0000;},只想对窗体本身而不是子控件按钮标签等
.QWidget{color:#ff0000;} ,具体详细规则参见官方说明。

196. 关于Qt延时的几种方法。

void QUIHelperCore::sleep(int msec)
{
if (msec <= 0) {
return;
}
#if 1
//非阻塞方式延时,现在很多人推荐的方法
QEventLoop loop;
QTimer::singleShot(msec, &loop, SLOT(quit()));
loop.exec();
#else
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
//阻塞方式延时,如果在主线程会卡住主界面
QThread::msleep(msec);
#else
//非阻塞方式延时,不会卡住主界面,据说可能有问题
QTime endTime = QTime::currentTime().addMSecs(msec);
while (QTime::currentTime() < endTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
#endif
#endif
}
199. Qt一直在持续升级迭代,尽管新增加的代码质量明显不如诺基亚时代,但最起码有行动,慢慢完
善。目前主要的升级改善在qml模块,底层也有完善,毕竟无论是widget还是qml都是公用一套底
层逻辑类,底层基础一定要扎实稳固,个人这几年一直对比测试过不同Qt版本(从旧版本到新版
本)很多类和函数的性能,发现官网列出来的新版本对应类和方法的性能提升改善,确实没有说
谎,至于提升了多少这块有没有吹牛逼那就不清楚。
base64算法性能提升很大。
QStringList等凡是使用了QList相关的类,性能提升巨大。
对比测试大概从5.12版本开始QStringList和QMap性能相当。
早期版本QStringList如果查找的值先插入则时间越短,QMap则没有这个区别。
QStringList list1, list2;

QMap<QString, QString> map;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
for (int i = 0; i < 100000; ++i) {
QString s1 = QString("%1").arg(i);
QString s2 = QString("A%1").arg(i);
list1 << s1;
list2 << s2;
map.insert(s1, s2);
}
}
void MainWindow::on_pushButton_clicked()
{
QElapsedTimer time;
time.start();
qDebug() << "111" << time.nsecsElapsed() <<
list2.at(list1.indexOf("9999"));
}
void MainWindow::on_pushButton_2_clicked()
{
QElapsedTimer time;
time.start();
qDebug() << "222" << time.nsecsElapsed() << map.value("9999");
}

201. 编译生成debug版本动态库,文件末尾自动加上d结尾。

CONFIG(debug, debug|release) {
win32: TARGET = $$join(TARGET,,,d)
mac: TARGET = $$join(TARGET,,,_debug)
unix:!mac: TARGET = $$join(TARGET,,,d)
}

203. 如果发现之前编译正常,突然之间再编译就一直死循环的样子,停留在一行提示并疯狂不停的打 印,或者提示文件时间在未来,这说明你很可能改过开发环境的时间(比如测试某个授权文件失 效),导致有修改过文件的保存时间在未来,你只需要将时间调整回来,将最后更新时间不正确的 代码文件重新保存下就行。Qt的增量编译是根据文件的最后修改时间来判定的,最后的修改时间比 上一次的修改时间还要新则认为该文件被修改过,需要重新编译该文件。

206. 有时候需要对文本进行分散对齐显示,相当于无论文字多少,尽可能占满整个空间平摊占位宽度, 但是在对支持对齐方式的控件比如QLabel调用 setAlignment(Qt::AlignJustify | Qt::AlignVCenter) 设置分散对齐会发现没有任何效果,这个时候就要考虑另外的方式比如通过控制字体的间距来实现 分散对齐效果。

QString text = "测试分散对齐内容";
//计算当前文本在当前字体下占用的宽度
QFont font = ui->label->font();
int textWidth = ui->label->fontMetrics().width(text);
//显示文本的区域宽度=标签的宽度-两边的边距
int width = ui->label->width() - 12;
//需要-1相当于中间有几个间隔
int count = text.count() - 1;
//计算每个间距多少
qreal space = qreal(width - textWidth) / count;
//设置固定间距
font.setLetterSpacing(QFont::AbsoluteSpacing, space);
ui->label->setFont(font);
ui->label->setText(text);
207. 随着需求的不断增加,程序不断变大,用到的动态库也越来越多,到了发布程序的时候你会发现和
可执行文件同一目录下文件数量真多,此时可能会考虑如何将一些库文件分门别类的存放,这样方
便管理。
Qt提供的设置动态库路径的方法setLibraryPaths是用来搜索插件动态库的,而不是程序直接依赖
的动态库。
很多人以为这个可以设置Qt的库或者程序中依赖的第三方库的路径,其实想想也知道,因为程序依
赖这个库,找不到的话根本跑不起来,程序跑不起来怎么应用执行这个代码呢?
Qt默认是可用通过setLibraryPaths的方式设置Qt插件的动态库目录位置,比如数据库插件
sqldrivers,因为这些库文件是真正在Qt程序跑起来以后通过插件形式去加载的。
还可以通过qt.conf文件设置 Plugins="config" 指定所有插件在可执行文件下的config目录下。
要想设置程序直接依赖的动态库在其他目录,找遍全宇宙也只有一个办法,那就是设置环境变量,
除此别无他法。
至于如何设置环境变量方式很多,比如手动在电脑上设置,或者搞个批处理文件执行命令行,在程
序安装的时候自动执行,或者程序打包目录下用户手动运行这个批处理。
大神补充:设置插件的目录还可以通过在main函数最前面写 qputenv("PATH",
QString("%1;%2").arg(qgetenv("PATH"), pluginFileInfo.path()).toLocal8Bit()); 来实现。
网友补充:最终找插件的路径其实就是这个 QT_PLUGIN_PATH 环境变量。

208. 进度条控件如果设置的垂直方向,就算你设置了文本可见,会发现根本看不到进度文本,经过多方 百折不挠的试探,以及和酷码大佬深入的探讨,发现只要设置下border样式(border:1px solid #ff0000、border:none、border-style:solid、border-radius:0px 任意一种)就行,就可以把文本 显示出来,这TM就不知道Qt为什么总是不统一规则,这个BUG通用于任何版本,这个可能是因为 边框的solid样式冲突了导致无法继续绘制,确切的说这必须是BUG,这个锅Qt必须背。

209. 我们在使用QFileDialog::getOpenFileName、QFileDialog::getExistingDirectory等方法时,有时 候会发现首次打开很卡,尤其是在默认目录很多文件的时候,此时你可以考虑设置这些函数最末尾 的参数为QFileDialog::DontUseNativeDialog,表示不采用本地系统对话框,这样的话会采用Qt的 对话框,速度快很多,估计系统的对话框在打开的时候会做很多初始化加载处理。

QFileDialog::getOpenFileName(this, "", "", "", 0,
QFileDialog::DontUseNativeDialog);
QFileDialog::getExistingDirectory(this, "", "",
QFileDialog::DontUseNativeDialog);

211. QMainWindow 在对停靠窗体进行排列的时候,有些不常用的设置容易遗忘,建议将 QMainWindow 的头文件函数过一遍一目了然。详细介绍各种停靠参数文章参见 https://zhuanlan.zhihu.com/p/388544168。

//设置允许各种嵌套比如上下排列左右排列非常灵活 //此设置会和下面的 setDockOptions 中的参数覆盖所以要注意顺序 //this->setDockNestingEnabled(true); //设置停靠参数,不允许重叠,只允许拖动和嵌套 this->setDockOptions(AnimatedDocks | AllowNestedDocks); //将底部左侧作为左侧区域,底部右侧作为右侧区域,否则底部区域会填充拉伸 this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

212. 当我们在对QModelIndex取数据的时候,常规的角色的数据(QVariant类型支持to的比如 toString、toInt、toDouble等)可以很方便的取出来,特定的数据类型需要用的万能取值模板函数 T value() 取出来。

//显示文本
QString text = index.data(Qt::DisplayRole).toString();
//文本对齐
int align = index.data(Qt::TextAlignmentRole).toInt();
//文字字体
QFont font = index.data(Qt::FontRole).value<QFont>();
//前景色
QColor color = index.data(Qt::ForegroundRole).value<QColor>();
//背景色
QColor color = index.data(Qt::BackgroundRole).value<QColor>();

213. 很多人以为拖曳只要在dropEvent事件就可以了,其实不行的,没有效果的,需要先在 dragEnterEvent事件中执行event->accept()才行,不然根本没有效果,很多人尤其是初学者都挂 在这里,我就是在这里摔了一跤,好疼!

void frmMain::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
}
void frmMain::dragEnterEvent(QDragEnterEvent *event)
{
if(event->mimeData()->hasFormat("application/xqabstractitemmodeldatalist")) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->ignore();
}
}

216. Qt的网络库支持udp广播搜索和组播搜索,其中组播搜索可以跨网段搜索,有时候你会发现失灵, 此时你可以尝试把本地的虚拟机的网卡禁用试试,估计就好了。还有就是在本地开启了代理啥的, 先关掉试试。近期在使用tcpsocket连接的时候,发现在Qt4和Qt5中正常的程序,到了Qt6中就不 行了,报错提示 The proxy type is invalid for this operation ,原来是本地设置了代理导致的,可 能在Qt6以前会默认跳过去不处理。

//也可以通过代码设置跳过代理
#include <QNetworkProxy>
QNetworkProxyFactory::setUseSystemConfiguration(false);
//下面这样每次设置也可以
tcpSocket->setProxy(QNetworkProxy::NoProxy);
//查阅到文章 h
//从5.8开始socket默认代理类型是DefaultProxy而不是NoProxy,不知道出于什么考虑。

222. 在读取文本文件的时候,有时候会发现读取出来的中文乱码,这个时候就需要识别文件编码格式, 然后主动设置对应的编码去读取就不会乱码。

//检查文件编码 0=ANSI 1=UTF-16LE 2=UTF-16BE 3=UTF-8 4=UTF-8BOM
int DataCsv::findCode(const QString &fileName)
{
//假定默认编码utf8
int code = 3;
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
//读取3字节用于判断
QByteArray buffer = file.read(3);
quint8 b1 = buffer.at(0);
quint8 b2 = buffer.at(1);
quint8 b3 = buffer.at(2);
if (b1 == 0xFF && b2 == 0xFE) {
code = 1;
} else if (b1 == 0xFE && b2 == 0xFF) {
code = 2;
} else if (b1 == 0xEF && b2 == 0xBB && b3 == 0xBF) {
code = 4;
} else {
//尝试用utf8转换,如果可用字符数大于0,则表示是ansi编码
QTextCodec::ConverterState state;
QTextCodec *codec = QTextCodec::codecForName("utf-8");
codec->toUnicode(buffer.constData(), buffer.size(), &state);
if (state.invalidChars > 0) {
code = 0;
}
}
file.close();
}
return code;
}

223. 在连接远程数据库进行查询数据的时候,有时候会发现很慢,尤其是表数据量越多越慢,本地的话 同等数据量快很多,可以尝试开启只前进属性,query.setForwardOnly(true);这样的话只会缓存一 次的数据,大大提高远程数据库的查询效率,据说可以提高几十倍百倍的速度。当然前提是对查询 的数据之前向前取数据的需求,如果还要往后取数据或者在数据模型QSqlQueryModel中使用,则 不能开启此属性。原因在每次利用QSqlQuery获取下一条记录时,若不开启isForwardOnly属性 (很遗憾默认就是不开启),则每次都开辟新的内存空间,来存储已经访问及未访问的记录,这 样,每次都会浪费好多存储空间。

224. Qt中的painter绘制非常灵活强大,接口丰富,但是对于很多初学者来说还是有一定的难度,尤其 是各种奇奇怪怪的复杂格式,而这些格式用html确很好描述,比如控制行间距、字符间距等,此时 可以用QTextDocument传入html格式内容交给QPainter绘制,非常完美、简单、强大,包括一些 数学公式啥的。

void Form::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QTextDocument doc;
doc.setHtml(html);
//设置文本宽度
doc.setTextWidth(200);
//指定绘制区域
doc.drawContents(&painter, QRect(0, 0, 200, 70));
}

225. Qt中样式表对选中颜色和悬停颜色是有优先级的,根据对操作系统默认样式的观察,当处于选中状 态+悬停状态的时候,默认取悬停状态,也就是鼠标移动到选中的列表item上,颜色取悬停状态颜 色。而Qt中如果两种颜色都设置了,根据设置的顺序来,取最后的为准,如果最后设置的选中状态 颜色,则当item处于选中状态+悬停状态的时候,取选中状态颜色而不是悬停状态颜色,切记!

//下面这样设置则当鼠标停留在选中的item上时背景颜色=#00FF00
QTableView::item:selected{background:#FF0000;}
QTableView::item:hover{background:#00FF00;}
//下面这样设置则当鼠标停留在选中的item上时背景颜色=#FF0000
QTableView::item:hover{background:#00FF00;}
QTableView::item:selected{background:#FF0000;}

227. 有时候文本框中的内容过长,而文本框默认光标在尾部,所以要主动设置下将光标移到最前面

//三种方法都可以
ui->lineEdit->setSelection(0, 0);
ui->lineEdit->setCursorPosition(0);
//样式表方式
"QLineEdit{qproperty-cursorPosition:0;}
默认oracle的插件驱动代码是按照oracle12的函数写的,如果链接的是oracle11,则需要改动两行
代码才能编译成功。打开qsql_oci.cpp文件大概在1559行代码左右,有个OCIBindByPos2函数改成
OCIBindByPos,下面还有一行bindColumn.lengths改成(ub2*)bindColumn.lengths。
Qt数据库程序打包发布,所有前提:注意区分32/64位,你的程序是32位的就必须带上32位的库,
64位的必须带上64位的库,这点Qt的库也是这个要求。mysql发布最简单,带上一个mysql的动态
库文件就行(windows上的是libmysql.dll),非常简单。sqlserver不用带,因为是微软的亲儿
子,一般操作系统自带。postgres需要带上libpq.dll、libintl-8.dll、libiconv-2.dll、libeay32.dll、
ssleay32.dll这几个文件就行。oracle需要带上oci.dll、oraociei11.dll(这个文件很大有
130MB+),如果不行建议直接安装个oracle client客户端软件,然后对应bin目录设置到环境变量
就好。
不同数据库在执行sql脚本的时候,会自动将表名或者字段名转成大写或小写,mysql会将表名转成
小写、postgresql会将表名和字段名转成小写、oracle会将表名和字段名转成大写。这就导致使用
QSqlTableModel调用setTable设置数据库表名的时候,一定要和数据库中的表名一致,区分大小
写,所以就是在对postgresql和oracle数据库的时候一定要注意,本人就是在这里卡了很久,差点
要把这巨大的屎盆扣在Qt的BUG上。
void DbHelper::bindTable(const QString &dbType, QSqlTableModel *model, const
QString &table)
{
//postgresql全部小写,oracle全部大写,这两个数据库严格区分表名字段名的大小写卧槽
QString flag = dbType.toUpper();
if (flag == "POSTGRESQL") {
model->setTable(table.toLower());
} else if (flag == "ORACLE") {
model->setTable(table.toUpper());
} else {
model->setTable(table);
}
}

Qt支持不指定数据库名打开数据库,因为有时候是要在连接数据库服务器后,执行sql语句创建数 据库。数据库都还没存在怎么连接呢,测试发现sqlite、mysql、sqlserver、postgresql都支持这 个特性。在删除和创建数据库的前提是该数据库没有被其他程序占用,比如其他程序已经打开了该 数据库则会执行失败。这里我就折磨过很多次,为什么执行失败呢?后面发现第三方数据库工具已 经打开了该数据库,把工具关掉就ok了。

QSqlDatabase database = QSqlDatabase::addDatabase("QMYSQL");
//database.setDatabaseName("dbtool");
database.setHostName("127.0.0.1");
database.setPort(3306);
database.setUserName("root");
database.setPassword("root");
if (database.open()) {
QSqlQuery query(database);
qDebug() << "删除数据库" << query.exec("drop database dbtool");
qDebug() << "创建数据库" << query.exec("create database dbtool");
if (query.exec("select * from userinfo")) {
while (query.next()) {
qDebug() << "查询数据库" << query.value(0);
}
}
} else {
qDebug() << "打开数据库" << database.lastError().text();
}
用QSqlQueryModel+QTableView显示数据,int类型的数据,如果超过100万,会变成科学计数显
示,这就很恼火了,肯定不是自己想要的结果。找遍网络搜索,终于找到一个同样问题的哥们,需
要对这一列加个空的委托就行。后面发现空委托也不行,超过1000万条又屌样了,需要终极大法重
载数据模型显示。
ui->tableView->setItemDelegateForColumn(0, new QItemDelegate);
//下面是终极大法
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
QVariant value = QSqlQueryModel::data(index, role);
//超过100万的数值会被科学计数显示需要这里转成字符串显示
if (role == Qt::DisplayRole) {
int result = value.toInt();
if (result >= 1000000) {
value = QString::number(result);
}
}
return value
}

mysql数据库有多种数据库引擎,其中MyIsam不支持数据库事务,默认一般是这个引擎,所以当
你使用Qt中的transaction方法后commit提交时候,会发现不成功,其实事实上又是成功的,去数
据库里面查看对应的结果又是正确的。有两个办法,第一就是将数据库引擎改成InnoDB,第二就
是在提交后做个错误判断 if (database.commit() || !database.lastError().isValid()) ,错误不可用
也说明是成功的。


mysql有个分支叫mariadb,比mysql更纯正,据说各方面都吊打mysql
人对比测试下来也是确实批量插入和查询性能要好不少,并且完全兼容mysql,甚至库文件直接重命名也可以直接使用,比如将
libmariadb.dll改成libmysql.dll可以直接使用,而且体积还小了八倍,这个好,发布的时候又少了
好几兆。

基类函数加了virtual时,实现的时重写。用基类指针或子类指针调用时,调用到的都是子类的函 数。

233. Qt中如何避免和第三方的signals、slots等关键字冲突。
第一步:在pro中加上 CONFIG += no_keywords 。
第二步:项目中之前所有的 signals 改成 Q_SIGNALS,slots 改成 Q_SLOTS 等。
第三步:彻底重新编译项目,这样就关键字不冲突了。
236. 关于在头文件中定义函数使用static关键字的血的教训。
有时候我们需要将一些常用函数写在一个文件中供很多地方调用,如果写的是 int doxxx{} 这种,
在你多个地方引用的时候,肯定会编译报错提示 “重复定义” 的错误。
此时你需要在函数前面加上static关键字,变成 static int doxxx{} 这种,能够正常编译和运行,以
为一切万事大吉,还是我太年轻。
如果仅仅是一个类中在使用,或者函数中没有静态变量,也不会出问题,问题就在static修饰的函
数在每个引入头文件的时候都会拷贝一份,导致函数里面的static静态变量会重复初始化,这样就
不正确了。
为了解决这个问题,终极办法就是在外面套个类,所有的函数和变量放到类中,完美,再也不会睡
不着了,真香。
关于C/C++ 中的static关键字,建议大家参考这篇文章写得 https://zhuanlan.zhihu.com/p/37439
983,醍醐灌顶。
//文件名 test.h
//下面这个函数 编译报错提示 “重复定义”
void test() {}
//下面4个函数在每个引入头文件的时候都会被拷贝一份
static void test1() {}
inline void test2() {}
static inline void test3() {}
inline static void test4() {}
//保证没问题的写法
class tt {
void test() {}
static void test1() {}
inline void test2() {}
static inline void test3() {}
inline static void test4() {}
}
237. 在数据库查询中,一般会建立索引以便加快查询速度,比如常用的条件字段作为索引字段。但是有
些时候如果查询语句没写好,就算where中有索引字段也会引起全表扫描,也就是说根本没用上索
引,这点要积极的避免。
模糊查询like,全模糊 like '%...%' 和左模糊 like '%...' 无法直接使用索引,右模糊查询 like '...%' 会
使用索引。
查询条件中含有is null的select语句执行慢,is not null 时永远不会使用索引,一般数据量大的表不
要用is null查询。
不等于操作符 <> 和 != 会限制索引,引起全表扫描,即使比较的字段上有索引。
where子句中比较的两个条件,一个有索引,一个没索引,使用or则会引起全表扫描。
select count(*) from table 这样不带任何条件的count会引起全表扫描。
in 和 not in 也要慎用,否则会导致全表扫描,能用 between 就不要用 in。
用 >= 替代 >,比如 高效写法:select * from table where id >= 4,低效写法:select * from
table where id > 3。
如果表数据量很小,比如就几千行,请忽略上述警告,加不加索引问题不大,甚至某些时候加索引
反而大大增加了数据库文件的体积,影响更新数据库的速度。
2. 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

3. 默认Qt6开启了高分屏支持,界面会变得很大,甚至字体发虚,很多人会不习惯,因为这种模式如 果程序很多坐标计算没有采用devicePixelRatio进行运算的话,100%会出现奇奇怪怪的问题,因为 坐标不准确了。要取消这种效果可以设置高分屏缩放因子。

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorR oundingPolicy::Floor); #endif


8. QWheelEvent的 delta() 改成 angleDelta().y(),pos() 改成 position() 。
17. 获取当前屏幕索引以及尺寸需要分别处理。
//获取当前屏幕索引
int QUIHelper::getScreenIndex()
{
//需要对多个屏幕进行处理
int screenIndex = 0;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
int screenCount = qApp->screens().count();
#else
int screenCount = qApp->desktop()->screenCount();
#endif
if (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)) {
#else
if (qApp->desktop()->screenGeometry(i).contains(pos)) {
#endif
screenIndex = 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();
#else
rect = qApp->desktop()->availableGeometry(screenIndex);
#endif
} else {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
rect = qApp->screens().at(screenIndex)->geometry();
#else
rect = qApp->desktop()->screenGeometry(screenIndex);
#endif
}
return rect;
}
37. Qt6将enterEvent的参数QEvent改成了QEnterEvent也不打个招呼。这种改变编译也不会提示的。

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
void enterEvent(QEnterEvent *);
#else
void enterEvent(QEvent *);
#endif
//后面经过JasonWong大佬的指点,从父类重新实现的virtual修饰的函数,建议都加上override关
键字。
//这样的话一旦父类的函数或者参数变了则会提示编译报错,而不是编译通过但是运行不正常会一脸懵
逼茫然,从而把锅扣给Qt。
//下面是父类函数
virtual void enterEvent(QEvent *event);
//子类建议加上override
void enterEvent(QEvent *event) override;

38. Qt6中多个类进行了合并,比如现在QVector就成了QList的别名,意味着这两个类是同一个类没有 任何区别,可能Qt内部对两种的优点都集中在一起,并尽量重写算法或者其他处理规避缺点。同理 QStringList现在也成了 QList 的别名,是同一个类,没有单独的类。


39. 在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 这种写法。

40. 对于委托的进度条样式QStyleOptionProgressBar类的属性,在Qt4的时候不能设置横向还是垂直 样式,默认横向样式,要设置orientation需要用另外的QStyleOptionProgressBarV2。从Qt5开始 新增了orientation和bottomToTop属性设置。在Qt6的时候彻底移除了orientation属性,只有 bottomToTop属性,而且默认进度是垂直的,很操蛋,理论上默认应该是横向的才对,绝大部分 进度条场景都是横向的。这个时候怎么办呢,原来现在的处理逻辑改了,默认垂直的,如果要设置 横向的直接设置 styleOption.state |= QStyle::State_Horizontal 这种方式设置才行,而Qt6以前默认方向是通过 orientation 值取得,这个State_Horizontal从Qt4就一直有,Qt6以后要主动设置下 才是横向的就是。

43. QModelIndex的查找子节点child函数去掉了,但是查找父节点parent函数保留,查阅代码得知之 前的child函数就是封装的model->index(row, column, QModelIndex)函数。

//下面两个函数等价 如果要兼容Qt456则用下面这个方法
QModelIndex index = indexParent.child(i, 0);
QModelIndex index = model->index(i, 0, indexParent);
//下面两个函数等价 如果要兼容Qt456则用下面这个方法
QModelIndex indexChild = index.child(i, 0);
QModelIndex indexChild = model->index(i, 0, index);

45. QProcess中的start方法以前直接支持传入完整的命令,到了Qt6严格要求必须拆分后面的参数。

//Qt6以前支持执行完整命令
QProcess p;
p.start("wmic cpu get Name");
//Qt6需要改成下面的方法,此方法也兼容Qt4、5、6
p.start("wmic", QStringList() << "cpu" << "get" << "Name");

12. 由于AndroidManifest.xml文件每个程序都可能不一样,为了做成通用的组件,这就要求可能不能 带上AndroidManifest.xml文件,这样的话每个Qt安卓程序都启动默认内置的Activity,如果依赖 Activity上下文的执行函数需要传入Qt的Activity才行,这里切记Qt的Activity包名是 Lorg/qtproject/qt5/android/bindings/QtActivity; 之前顺手想当然的写的 Landroid/app/Activity; 发现死活不行,原来是包名错了。

15. 建议搭配 android studio 工具开发,因为在 android studio 中写代码都有自动语法提示,包名会 提示自动引入,可以查看有那些函数方法等,还可以校验代码是否正确,而如果在QtCreator中手 写有时候可能会写错,尤其是某个字母写错,当然这种错误是编译通不过的,会提示错误在哪行。


18. 测试发现GetStringUTFChars方法对应的数据字符串中不能带有temp字样,否则解析有问题,不知 什么原因。

22. android studio 新建并生产jar包步骤。
第一步:文件(File)-》新建(new)-》项目(new project)-》空白窗体(empty activity)。
第二步:刚才新建好的项目鼠标右键新建(new)-》模块(new module)-》安卓库(android
library)。
说明:如果选择的不是安卓库(android library)而是java库(Java Library),则直接编译出来的
就是jar文件,默认包名 com.example.lib.MyClass。推荐选择java库,编译后不用去一堆文件中找
jar文件。
第三步:写好库名字,根据项目需要选择好最低sdk版本-》完成。
第四步:在刚才新建好的库项目mylibrary,依次找到子节点
src/main/java/com.example.mylibrary上鼠标右键新建-》class类。切记是这个节点不是java节点
或者其他节点。
第五步:写好你的类方法函数等。
package com.example.mylibrary;
public class Test {
public static int add(int a, int b) {
return a + b;
}
}
第六步:选中库项目mylibrary,菜单执行编译(build)-》编译库(make module xxx)。
第七步:此时在mylibrary/build目录下有outputs目录和intermediates目录,其中outputs/aar目
录下是生成的Android库项目的二进制归档文件,包含所有资源,class以及res资源文件全部包
含。有时候我们仅仅需要jar文件,只包含了class文件与清单文件 ,不包含资源文件,如图片等所
有res中的文件。需要到intermediates/aar_main_jar/debug目录下,可以看到classes.jar,将这
个拷贝出来使用即可。当然你也可以对刚才的aar文件用解压缩软件解压出来也能看到classes.jar,
是同一个文件。
其他:调用jar包非常简单,只需要将jar文件放在你的项目的libs目录下即可,对应的包名和函数一
般jar包提供者会提供,没有提供的话,可以在android studio中新建空白项目,切换到project视图,找到libs目录,鼠标右键最下面作为包动态库添加到项目,导入包完成以后会自动在libs目录列
出,双击刚刚导入的包然后就自动列出对应的类和函数。

1. 通常而言,好的做法是在包含了Qt头文件之后再包含非Qt头文件,由于Qt(为编译器和预处理 器)定义了许多符号,这使得避免名称冲突变得更容易,也更容易找到文件。

#include "frminput2019.h" #include "ui_frminput2019.h" #include "qdatetime.h" #include "qdebug.h" #include "input2019.h" #include "inputnumber.h"

2. 一种好的编程实践是在代码中使用const实体而不是嵌入数字型常量(有时称他们为“幻数”)。如果 以后需要修改他的值时,就可以获得这种灵活性。一般而言,将常量“孤立”出来,可提高程序的可 维护性。

//不推荐写法 for (int i = 0; i < 100; ++i) { ... } //推荐下面的写法 const int count = 100; for (int i = 0; i < count; ++i) { ... }


关于Qt事件传递的一个说明:
通常写win32程序,鼠标消息应该是直接发给指定窗口句柄的,指定窗口没有处理就会转化成透传
消息,交给父窗口处理。你在一个普通文字label上点击,父窗口也能收到鼠标事件。
Qt应该是所有消息都发给了顶层窗口,所以事件分发逻辑是自己处理,主窗口收到鼠标事件然后Qt
自己分发给指定子控件,QEvent会有ignore或者accept表示自己处理了没有,例如鼠标点击事
件,事件分发器发现没有被处理,数据重新计算然后分发给父窗口。这样父窗口收到的事件坐标就
是基于自己窗口内的。用eventFilter就需要自己计算坐标。
再比如,当使用QDialog,放一个QLineEdit并设置焦点,按Esc时QDialog也会自动关闭,本质上
就是因为QLineEdit并不处理Esc的按键事件,透传给了QDialog。

2. Qt真正的核心:元对象系统、属性系统、对象模型、对象树、信号槽。往死里啃这五大特性,在你 的项目中,逐渐的设法加入这些特性,多多练习使用它们,长此以往你会收获意想不到的效果。

3. Qt安装目录下的Examples目录下的例子,看完学完,月薪20K起步;Qt常用类的头文件的函数看 完学完使用一遍并加以融会贯通,月薪30K起步。

5. 如果出现崩溃和段错误,80%都是因为要么越界,要么未初始化,死扣这两点,80%的问题解决 了。

7. Qt和msvc编译器常见搭配是Qt5.7+VS2013、Qt5.9+VS2015、Qt5.12+VS2017、 Qt5.15+VS2019、Qt6.2+VS2019,按照这些搭配来,基本上常用的模块都会有,比如webengine 模块,如果选用的Qt5.12+msvc2015,则很可能官方没有编译这个模块,只是编译了 Qt5.12+msvc2017的,如果一定要用msvc2015不想换msvc2017则只能选择Qt5.9+msvc2015套 件,或者自行源码重新编译(这个难度超大,初学者绕过)。

11. 终极秘籍:如果遇到问题搜索Qt方面找不到答案,试着将关键字用JAVA C# android打头,你会发 现别有一番天地,其他人其他语言其他领域很可能做过!

 Qml无缝支持js,可以利用现在各种js轮子,指数级提升qml的项目范围。

8 书籍推荐 1. C++入门书籍推荐《C++ primer plus》,进阶书籍推荐《C++ primer》。 2. Qt入门书籍推荐霍亚飞的《Qt Creator快速入门》,Qt进阶书籍推荐官方的《C++ GUI Qt4编 程》,qml书籍推荐《Qt5编程入门》,Qt电子书强烈推荐《Qt5.10 GUI完全参考手册》。 3. 强烈推荐程序员自我提升、修养、规划系列书《走出软件作坊》《大话程序员》《程序员的成长 课》《解忧程序员》,受益匪浅,受益终生