刘典武Qt开发经验摘录
刘典武经验中个人感觉后期可能会用到的,或之前用过容易遗忘的,分类摘录,方便后期查阅,侵删。
项目地址
飞扬青云/Qt开发经验
UI
6.可以在pro文件中写上标记版本号+ico图标(Qt5才支持),其实在windows上就是qmake的时候会自动将此信息转换成rc文件
1 |
|
9. 绘制平铺背景QPainter::drawTiledPixmap,绘制圆角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect()
10. 移除旧的样式
1 |
|
15. 可以将控件A添加到布局,然后控件B设置该布局,这种灵活性提高了控件的组合度,比如可以在文本框左侧右侧增加一个搜索按钮,按钮设置图标即可
1 |
|
16. 对QLCDNumber控件设置样式,需要将QLCDNumber的segmentstyle设置为flat,不然你会发现没效果
21. 如果出现Z-order assignment: is not a valid widget.错误提示,用记事本打开对应的ui文件,找到 为空的地方,删除即可
26. 可以对整体的指示器设置样式,而不需要单独对每个控件的指示器设置
1 |
|
27. 可以指定位置设置背景图片(本人注:可以调整button中图标的位置)
1 |
|
33. Qt最小化后恢复界面假死冻结,加上代码
1 |
|
34. 获取标题栏高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight点进去你会发现新大陆
35. 设置高分屏属性以便支持2K4K等高分辨率,尤其是手机app。必须写在main函数的QApplication a(argc, argv);的前面
1 |
|
38. qml播放视频在linux需要安装 sudo apt-get install libpulse-dev
40. Qt5以后提供了类QScroller直接将控件滚动
1 |
|
50. 如果需要指定无边框窗体,但是又需要保留操作系统的边框特性,可以自由拉伸边框,可以使用 setWindowFlags(Qt::CustomizeWindowHint)
53. Qt中继承QWidget之后,样式表不起作用,解决办法有三个。强烈推荐方法一
- 方法一:设置属性 this->setAttribute(Qt::WA_StyledBackground, true);
- 方法二:改成继承QFrame,因为QFrame自带paintEvent函数已做了实现,在使用样式表时会进行解析和绘制。
- 方法三:重新实现QWidget的paintEvent函数时,使用QStylePainter绘制。
1 |
|
54. 有时候在界面上加了弹簧,需要动态改变弹簧对应的拉伸策略,对应方法为changeSize,很多人会选择使用set开头去找,找不到的
58. 非常不建议tr中包含中文,尽管现在的新版Qt支持中文到其他语言的翻译,但是很不规范,也不知道TMD是谁教的,tr的本意是包含英文,然后翻译到其他语言比如中文,现在大量的初学者滥用tr,如果没有翻译的需求,禁用tr,tr需要开销的,Qt默认会认为他需要翻译,会额外进行特殊处理
68. 很多控件都带有viewport,比如QTextEdit/QTableWidget/QScrollArea,有时候对这些控件直接处理的时候发现不起作用,需要对其viewport()设置才行,比如设置滚动条区域背景透明,需要使用scrollArea->viewport()->setStyleSheet(“background-color:transparent;”);而不是scrollArea->setStyleSheet(“QScrollArea{background-color:transparent;}”)
69. 有时候设置了鼠标跟踪setMouseTracking为真,如果该窗体上面还有其他控件,当鼠标移到其他控件上面的时候,父类的鼠标移动事件MouseMove识别不到了,此时需要用到HoverMove事件,需要先设置 setAttribute(Qt::WA_Hover, true)
72. 如果是dialog窗体,需要在exec以后还能让其他代码继续执行,请在dialog窗体exec前增加一行代码,否则会阻塞窗体消息
1 |
|
74. 在QTableView控件中,如果需要自定义的列按钮、复选框、下拉框等其他模式显示,可以采用自定义委托QItemDelegate来实现,如果需要禁用某列,则在自定义委托的重载createEditor函数返回0即可。自定义委托对应的控件在进入编辑状态的时候出现,如果想一直出现,则需要重载paint函数用drawPrimitive或者drawControl来绘制
75. 将 QApplication::style() 对应的drawPrimitive、drawControl、drawItemText、drawItemPixmap等几个方法用熟悉了,再结合QStyleOption属性,可以玩转各种自定义委托,还可以直接使用paint函数中的painter进行各种绘制,各种牛逼的表格、树状列表、下拉框等,绝对屌炸天。QApplication::style()->drawControl 的第4个参数如果不设置,则绘制出来的控件不会应用样式表
79. 对QTableView或者QTableWidget禁用列拖动
1 |
|
90. 在构造函数中获取控件的宽高很可能是不正确的,需要在控件首次显示以后再获取才是正确的,控件是在首次显示以后才会设置好正确的宽高值,记住是在首次显示以后,而不是构造函数或者程序启动好以后,如果程序启动好以后有些容器控件比如QTabWidget中的没有显示的页面的控件,你去获取宽高很可能也是不正确的,万无一失的办法就是首次显示以后去获取
103. Qt表格控件一些常用的设置封装,QTableWidget继承自QTableView,所以下面这个函数支持传入QTableWidget
1 |
|
106. 很多时候用QDialog的时候会发现阻塞了消息,而有的时候我们希望是后台的一些消息继续运行不要终止,此时需要做个设置
1 |
|
108. 在嵌入式linux上,如果设置了无边框窗体,而该窗体中又有文本框之类的,发现没法产生焦点进行输入,此时需要主动激活窗体才行
1 |
|
110. QGraphicsEffect类的相关效果很炫,可以实现很多效果比如透明、渐变、阴影等,但是该类很耗CPU,如果不是特别需要一般不建议用,就算用也是要用在该部件后期不会发生频繁绘制的场景,不然会让你哭晕在厕所
119. QGraphicsEffect相关类很耗CPU,甚至在绘制的时候和某些地方有冲突干扰,基本上不建议使用,情非得已只建议少量使用和非频繁触发绘制的地方使用
121. QLineEdit除了单纯的文本框以外,还可以做很多特殊的处理用途
- 限制输入只能输入IP地址。
- 限制输入范围,强烈推荐使用 QRegExpValidator 正则表达式来处理。
1 |
|
122. 在继承自QAbstractItemView的控件中,比如QTableView、QTableWidget,如果文本超过对应item的宽度,则会自动省略号显示,想要快速显示完整的文本,可以在该列和下一列分割线中间双击即可,会自动自适应显示最大宽度,如果是Qt5.14或者更高版本,你会发现显示省略号的计算规则变了,如果是rtsp、http之类的开头的英文字符串,同样的列宽下,会提前就显示省略号,比如字符串 rtmp://58.200.131.2:1935/livetv/cctv1,会显示成 rtmp://… ,而在旧版本的Qt中会显示成 rtmp://58.200.131… ,很多时候我们并不想看到烦人的省略号,可以设置取消
1 |
|
在主QWidget窗体如果直接qss设置背景图片的话,预览是可见的,运行并没有效果,你需要在这个主widget上再放个widget,在新的widget上设置qss图片就行,而如果是Dialog或者QMainWindow窗体是支持直接设置qss背景图的,预览和运行效果一致
138. 对高分屏不同缩放比例的自适应处理方法
1 |
|
139. QTabWidget选项卡有个自动生成按钮切换选项卡的机制,有时候不想看到这个烦人的切换按钮,可以设置usesScrollButtons为假,其实QTabWidget的usesScrollButtons属性最终是应用到QTabWidget的QTabBar对象上,所以只要设置全局的QTabBar的这个属性关闭即可。为啥要设置全局的呢,因为如果只是对QTabWidget设置了该属性,而在QMainWindow窗体中QDockWidget合并自动形成的选项卡只有QTabBar对象导致依然是有切换按钮
1 |
|
140. QMainWindow的分割线默认尺寸比较大,有时候想设置小一点或者不想要,最开始的时候以为是QSplitter,打印所有子元素找遍了也没找到影子,最后发现样式表中有对应设置的内容
1 |
|
142. 在停靠窗体QDockWidget和QOpenGLWidget同时使用的时候,从嵌入状态切换到浮动状态或者浮动状态切换到嵌入状态,QOpenGLWidget的上下文会被打乱导致白屏失效,需要在main函数中开头位置设置下共享OpenGL上下文
1 |
|
143. 关于Qt中文乱码的问题,个人也稍微总结了一点,应该可以解决99%以上的Qt版本的乱码问题
- 第一步:代码文件选择用utf8编码带bom。
- 第二步:在有中文汉字的代码文件顶部加一行(一般是cpp文件) #pragma execution_character_set(“utf-8”) 可以考虑放在head.h中,然后需要的地方就引入head头文件就行,而不是这行代码写的到处都是;这行代码是为了告诉msvc编译器当前代码文件用utf8去编译。
- 第三步:main函数中加入设置编码的代码,以便兼容Qt4,如果没有Qt4的场景可以不用,从Qt5开始默认就是utf8编码。
1 |
|
145. 通过酷码大哥(Qt开发者交流群)的指点,到今天才知道,Qt设置样式表支持直接传入样式表文件路径,亲测4.7到5.15任意版本,通过查看对应函数的源码可以看到内部会检查是否是 ‘file:///‘ 开头,是的话则自动读取样式表文件进行设置,无需手动读取
1 |
|
146. Qt中自带的很多控件,其实都是由一堆基础控件(QLabel、QPushButton等)组成的,比如日历面板 QCalendarWidget 就是 QToolButton+QSpinBox+QTableView 等组成,妙用 findChildren 可以拿到父类对应的子控件集合,可以直接对封装的控件中的子控件进行样式的设置,其他参数的设置比如设置中文文本(默认可能是英文)等
1 |
|
147. Qt内置了各种对话框,比如文件对话框-QFileDialog ,颜色对话框-QColorDialog ,默认都会采用系统的对话框风格样式,这样可以保持和系统一致,如果不需要的话可以取消该特性,取消以后会采用Qt自身的对话框,这样才能进行美化和其他处理
1 |
|
150. Qt样式表有多种运行机制,主要是考虑到各种需求场景,继承自QWidget的类和qApp类都支持setStyleSheet方法,还可以统一将样式表放在文件,或者将样式文件加入到资源文件
- 斗气:qss内容写得到处都是,哪里需要就写在哪里,各种控件调用 setStyleSheet方法传入样式表内容,或者直接对应控件鼠标右键弹出菜单选择改变样式表填入内容;
- 斗者:qss内容放在文件,读取文件内容设置样式表,程序发布的时候带上qss文件;
- 斗师:qss文件作为资源文件放到qrc文件,直接编译到可执行文件中,防止篡改;
- 斗灵:在qss文件中自定义一些标志充当变量使用,读取以后替换对应的变量为颜色值,类似动态换肤;
- 斗王:放在文件容易被篡改,集成到可执行文件不够灵活,一旦样式表更新需要重新编译文件,如何做到既能只更新样式表文件,又不需要重新编译可执行文件,又能防止被篡改:采用rcc命令将资源文件编译生成二进制,只需要替换该二进制文件即可;
- 斗皇:继承qstyle类自己实现完成所有样式接口,统一整体风格,大名鼎鼎的UOS系统默认规则就是如此,不允许用样式表,全部painter绘制;
151. 当Qt中编译资源文件太大时,效率很低,或者需要修改资源文件中的文件比如图片、样式表等,需要重新编译可执行文件,这样很不友好,当然Qt都给我们考虑好了策略,此时可以将资源文件转化为二进制的rcc文件,这样就将资源文件单独出来了,可在需要的时候动态加载
1 |
|
152. 关于设置字体,大概都会经历一个误区,本来是打算设置整个窗体包括子控件的字体大小的,结果发现只有主窗体自己应用了字体而子控件没有
1 |
|
156. Qt的UI界面在resize以后有个BUG,悬停样式没有取消掉,需要主动模拟鼠标动一下
1 |
|
158. Qt的文本控件比如QTextEdit默认加载大文本比如10MB的文本,很容易卡死甚至崩溃,那是因为默认一个属性开启了,需要屏蔽掉就好很多
1 |
|
160. QTabWidget选项卡控件,生成的tabbar选项卡宽度是按照文本自动设置的,文本越长选项卡的宽度越大,很多时候,我们需要的是一样的宽度或者等分填充
1 |
|
163. 如果对窗体设置了固定尺寸,窗体会变得大小不可拉伸,如果需要重新还原可拉伸,必须重新设置最小尺寸和最大尺寸
1 |
|
166. 有时候需要暂时停止某个控件发射信号(比如下拉框combobox添加数据的时候会触发当前元素改变信号),有多种处理,推荐用 blockSignals 方法
1 |
|
177. 很多时候需要在窗体首次显示的时候加载一些东西,而且只加载一次,当窗体再次显示的时候不加载。为什么不是在构造函数呢?因为很多玩意都是要在显示后才能确定,比如控件的尺寸,部分样式表的应用
1 |
|
189. 对于QListView(QListWidget)、QTreeView(QTreeWidget)、QTableView(QTableWidget)这种类型的控件,可以通过setChecked来让对应的item产生复选框效果,很多人(包括曾经的自己)误以为这就是复选框控件,其实不是的,他是对应控件的indicator指示器,所以想要更换样式,不能说设置了QCheckBox的样式就有效果,而要单独对齐indicator指示器设置样式才行
1 |
|
190. 关于QTableView(采用model数据源)、QTableWidget列名列宽设置,有时候发现没有起作用,原来是对代码设置的顺序有要求,比如setColumnWidth前必须先setColumnCount,不然列数都没有,哪来的列宽,包括setHorizontalHeaderLabels设置列标题集合也是,前提都要先有列
1 |
|
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 属性,没法取消。很多时候我们需要两种模式,一种就是永远不应用高分屏及缩放,一种就是自动应用高分屏及缩放
1 |
|
195. 关于QSS要注意的坑
- qss源自css,相当于css的一个子集,主要支持的是css2标准,很多网上的css3的标准的写法在qss这里是不生效的,所以不要大惊小怪。
- qss也不是完全支持所有的css2,比如text-align官方文档就有说明,只支持 QPushButton and QProgressBar,务必看清楚。
- 有时候偷懒直接来一句 *{xxx},你会发现大部分是应用了,也有小部分或者极个别没有应用,你可能需要在对应的窗体中 this->setStyleSheet() 来设置。
- qss的执行是有优先级的,如果没有指定父对象,则对所有的应用,比如在窗体widget中 {color:#ff0000;} 这样会对widget以及widget的所有子对象应用该样式,这种问题各大Qt群每周都在上演,你会发现各种奇奇怪怪的异样不正常,怎么办呢,你需要指定类名或者对象名,比如 #widget{color:#ff0000;} 这样就只会对widget对象应用该样式,另一种写法 QWidget#widget{color:#ff0000;},只想对窗体本身而不是子控件按钮标签等 .QWidget{color:#ff0000;} ,具体详细规则参见官方说明。
- qss整体来说还是可以的,尽管有着有那的BUG,怀着包容的心对待它。
图形绘制
65. 在绘制无背景颜色只有边框颜色的圆形时候,可以用绘制360度的圆弧替代,效果完全一致
1 |
|
78. 很多时候需要在已知背景色的情况下,能够清晰的绘制文字,这个时候需要计算对应的文字颜色
1 |
|
100. setPixmap是最糟糕的贴图方式,一般只用来简单的不是很频繁的贴图,频繁的建议painter绘制,默认双缓冲,在高级点用opengl绘制,利用GPU
101. 如果需要在尺寸改变的时候不重绘窗体,则设置属性即可 this->setAttribute(Qt::WA_StaticContents, true); 这样可以避免可以避免对已经显示区域的重新绘制
153. Qt中封装的QImage异常的强大,提供了各种图片格式的转换,还可以对每个像素的颜色值进行替换,有时候我们需要将单色的图片换成另外一种颜色,要注意的是如果带有透明值的颜色需要进行格式转化,比如转成Format_ARGB32或者Format_RGBA8888
1 |
|
161. 经常有人说Qt垃圾,说用Qt在1毫秒绘制几千个数据点卡成屎。其实显示器最高刷新频率一般才60帧,1毫秒就绘制一次有意义吗?不仅显示器没刷新过来,人肉眼也看不过来(有人可能又要抬杠说这是老板要求的,显示归显示,至于人看不看那是另外一回事,我想说的是显示不就是给人看的吗?给程序看可以直接后台绘制图片让程序识别啊没必要显示的),程序中要做的应该是尽量降低程序的绘制刷新频率到显示器的频率(其实一秒钟30帧都足够),一次搞多一点的数据一次性绘制(数据量很大还可以考虑重采样,比如平均值法等,毕竟要考虑显示器的分辨率就那么大,搞个几十万的数据点挤一块没啥意思,可以将一整块区域内的数据点换成一个点),而不是绘制多次,尽管两种办法都可以将收到的数据绘制完成,但是效率相差的不是一点点,信号也是如此,不建议太频繁的发送信号,Qt内部1秒钟处理信号的个数也是有限制的,太频繁高并发的信号,很可能会丢失或者合并一部分,比如网络请求接收到的学生信息表,应该是在该应答数据内的所有学生信息解析完一次性发送,而不是解析一条发送一条
QtCore
46. 巧妙的用QEventLoop开启事件循环,可以使得很多同步获取返回结果而不阻塞界面。查看源码得知,原来QEventLoop内部新建了线程执行
1 |
|
70. Qt封装的QDateTime日期时间类非常强大,可以字符串和日期时间相互转换,也可以毫秒数和日期时间相互转换,还可以1970经过的秒数和日期时间相互转换等
1 |
|
71. 在我们使用QList、QStringList、QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍《C++ GUI Qt 4编程(第二版)》的书中有特别的强调说明,此教材的原作者据说是Qt开发的核心人员编写的,所以还是比较权威,至于使用 at() 与使用 [] 操作符速度效率的比较,网上也有网友做过此类对比。原文在书的212页,这样描述的:Qt对所有的容器和许多其他类都使用隐含共享,隐含共享是Qt对不希望修改的数据决不进行复制的保证,为了使隐含共享的作用发挥得最好,可以采用两个新的编程习惯。第一种习惯是对于一个(非常量的)向量或者列表进行只读存取时,使用 at() 函数而不用 [] 操作符,因为Qt的容器类不能辨别 [] 操作符是否将出现在一个赋值的左边还是右边,他假设最坏的情况出现并且强制执行深层赋值,而 at() 函数则不被允许出现在一个赋值的左边
73. 安全的删除Qt的对象类,强烈建议使用deleteLater而不是delete,因为deleteLater会选择在合适的时机进行释放,而delete会立即释放,很可能会出错崩溃。如果要批量删除对象集合,可以用qDeleteAll,比如 qDeleteAll(btns)
主要是记录后半句
82. QVariant类型异常的强大,可以说是万能的类型,在进行配置文件的存储的时候,经常会用到QVariant的转换,QVariant默认自带了toString、toFloat等各种转换,但是还是不够,比如有时候需要从QVariant转到QColor,而却没有提供toColor的函数,这个时候就要用到万能办法
1 |
|
83. Qt中的QString和const char *之间转换,最好用toStdString().c_str()而不是toLocal8Bit().constData(),比如在setProperty中如果用后者,字符串中文就会不正确,英文正常
85. QTextEdit右键菜单默认英文的,如果想要中文显示,加载widgets.qm文件即可,一个Qt程序中可以安装多个翻译文件,不冲突
87. 在Qt5.10以后,表格控件QTableWidget或者QTableView的默认最小列宽改成了15,以前的版本是0,所以在新版的qt中,如果设置表格的列宽过小,不会应用,取的是最小的列宽。所以如果要设置更小的列宽需要重新设置ui->tableView->horizontalHeader()->setMinimumSectionSize(0)
95. QStyle内置了很多方法用处很大,比如精确获取滑动条鼠标按下处的值
1 |
|
97. 用QFile.readAll()读取QSS文件默认是ANSI格式,不支持UTF8,如果在QtCreator中打开qss文件来编辑保存,这样很可能导致qss加载以后没有效果
1 |
|
98. QString内置了很多转换函数,比如可以调用toDouble转为double数据,但是当你转完并打印的时候你会发现精确少了,只剩下三位了,其实原始数据还是完整的精确度的,只是打印的时候优化成了三位,如果要保证完整的精确度,可以调用 qSetRealNumberPrecision 函数设置精确度位数即可
1 |
|
118. 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毫秒。
155. Qt5.10以后提供了新的类 QRandomGenerator QRandomGenerator64 管理随机数,使用更方便,尤其是取某个区间的随机数
1 |
|
164. Qt内置了很多全局的对象参数可以直接获取,这样在使用的时候方便的不要不要的,比如判断当前鼠标左键还是右键可以直接用qApp->mouseButtons(),全局的鼠标坐标可以用QCursor::pos()
1 |
|
171. 在Qt编程中经常会遇到编码的问题,由于跨平台的考虑兼容各种系统,而windows系统默认是gbk或者gb2312编码,当然后期可能msvc编译器都支持utf8编码,所以在部分程序中传入中文目录文件名称的时候会发现失败,因为可能对应的接口用了早期的fopen函数而不是fopen_s函数,比如fmod中也是这个情况。这个时候就需要转码处理
1 |
|
175. Qt中基本上有三大类型的项目,控制台项目对应QCoreApplication、传统QWidget界面程序对应QApplication、quick/qml项目程序对应QGuiApplication。有很多属性的开启需要在main函数的最前面执行才有效果,比如开启高分屏支持、设置opengl模式等。不同类型的项目需要对应的QApplication。
1 |
|
178. Qt获取当前所用的Qt版本、编译器、位数等信息
1 |
|
179. QDateTime可以直接格式化输出星期几周几,Qt6默认按照英文输出比如 ddd = 周二 Tue dddd = 星期二 Tuesday ,此时如果只想永远是中文就需要用到QLocale进行转换
1 |
|
182. Qt自带的日志重定向机制非常简单好用,自从用了以后再也不用什么断点调试啥的了,在需要的地方支持qdebug输出对应的信息,而且发布程序以后也可以开启调试日志将其输出查看等
1 |
|
184. 由于Qt版本众多,有时候为了兼容多个版本甚至跨度Qt4/Qt5/Qt6的兼容,有些头文件或者类名等变了或者新增了,需要用到Qt版本的判断。需要注意的是如果在头文件中使用 QT_VERSION_CHECK 需要先引入#include “qglobal.h”不然编译失败,因为 QT_VERSION_CHECK 这个函数在 qglobal.h 头文件中
1 |
|
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则不会。
1
2
3
4
5
6
7QString text = "xxxxx";
//下面这样转换很可能会有问题
char *data = text.toUtf8().data();
//分两步转换肯定不会有问题
QByteArray buffer = text.toUtf8();
char *data = buffer.data();
const char *data = buffer.constData();
192. Qt内置了一些QList、QMap、QHash相关的类型,可以直接用,不用自己写个长长的类型
1 |
|
196. 关于Qt延时的几种方法
1 |
|
pro
7.管理员运行程序
,限定在MSVC编译器
1 |
|
8. 运行文件附带调试输出窗口,这个非常有用,很多时候当我们发布程序阶段,我们会遇到程序双击无法运行也不报错提示(开发机器上一切正常),都不知道发生了什么,甚至任务管理器可以看到运行了但是没有界面弹出来,此时就需要在项目的pro文件中加上这个,带界面的程序也会自动弹出调试窗口打印输出信息,方便找问题,一般没法正常运行的程序都会打印一些提示信息缺啥之类的
1 |
|
13. 根据操作系统位数判断加载
1 |
|
31. 判断编译器类型、编译器版本、操作系统
1 |
|
32. 在pro中判断Qt版本及构建套件位数
1 |
|
47. 多种预定义变量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目录 CONFIG -= debug_and_release
52. Qt默认不支持大资源文件,比如添加了字体文件,需要pro文件开启 CONFIG += resources_big
80. 从Qt4转到Qt5,有些类的方法已经废弃或者过时了,如果想要在Qt5中启用Qt4的方法,比如QHeadVew的setMovable,可以在你的pro或者pri文件中加上一行即可:DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
104. 在一些大的项目中,可能嵌套了很多子项目,有时候会遇到子项目依赖其他子项目的时候,比如一部分子项目用来生成动态库,一部分子项目依赖这个动态库进行编译,此时就需要子项目按照顺序编译或者设置好依赖规则
1 |
|
115. 有时候我们需要判断当前Qt版本有没有某个模块可以使用qtHaveModule(Qt5新引入的判断)来判断,如果要判断自己的项目中有没有 QT += 的方式添加的模块,可以用 contains来判断
1 |
|
126. Qt提供了qDebug机制直接输出打印信息,这个弥补了QtCreator调试很鸡肋的缺点,而且无缝对接日志钩子,使得现场运行期间按照预定的打印信息输出到日志文件,有时候在开发阶段,又不想要看到一堆堆的打印信息,最笨的做法是一行行注释掉qdebug的地方,其实还可以直接pro中加上一行来禁用整个项目的qdebug输出
1 |
|
127. 在使用 QT_NO_DEBUG_OUTPUT 关键字禁用了所有打印信息以后,可以节约不少的开销,有时候又想在禁用打印信息后,极少地方还需要看到打印信息,怎么办呢?其实 QT_NO_DEBUG_OUTPUT 禁用的 qdebug 的输出,Qt还有其他几种打印信息比如 qInfo、qWarning、qCritical,这些是不受影响的,也就是说在极少部分需要打印的地方用 qInfo 来输出信息就好。特别注意:qFatal 打印完信息程序会自动结束
1 |
|
128. Qt的pro文件可以添加各种处理来使得配置更方便,比如指定输出文件路径等,这样就不会全部在一堆编译生成的临时文件中找来找去
1 |
|
130. Qt的pro项目管理配置文件中也可添加各种编译前后的操作及配置,主要通过 QMAKE_POST_LINK和QMAKE_PRE_LINK,他们支持的函数以及写法,可以在QtCreator的帮助中搜索 qmake Function Reference 查看详情说明
- QMAKE_PRE_LINK 表示编译前执行内容
- QMAKE_POST_LINK 表示编译后执行内容
1 |
|
132. Qt6.0发布了,是个比较大的改动版本,很多基础的类或者组件都放到单独的源码包中,需要自行官网下载并编译,默认不提供集成在开发目录下,需要手动编译并集成,比如QRegExp,QTextCodec类,需要编译集成后pro文件 QT += core5compat 才能用, 具体说明在https://doc.qt.io/qt-6/qtcore5-index.html
157. 项目中启用c++11语法支持
1 |
|
165. Qt对区分不同的编译器也做了非常细致的处理
1 |
|
167. 项目代码文件数量如果很多的话,全部包含在pro项目文件中会显得非常凌乱,甚至滚动条都要拉好久,有两个方法可以处理的更好,推荐方法2
1 |
|
元对象
2. 定时器是个好东西(QMetaObject::invokeMethod的使用
)
定时器是个好东西,学会好使用它,有时候用QTimer::singleShot单次定时器和QMetaObject::invokeMethod可以解决意想不到的问题。
1 |
|
11. 获取类的属性
1 |
|
18. 巧妙的使用inherits判断是否属于某种类
1 |
|
112. 巧用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 执行,哇咔咔。
1 |
|
webengine
23. 如果用了webengine模块,发布程序的时候带上QtWebEngineProcess.exe+translations文件夹+resources文件夹
94. QWebEngineView控件由于使用了opengl,在某些电脑上可能由于opengl的驱动过低会导致花屏或者各种奇奇怪怪的问题,比如showfullscreen的情况下鼠标右键失效,需要在main函数启用软件opengl渲染
1 |
|
另外一个方法解决 全屏+QWebEngineView控件一起会产生右键菜单无法弹出的bug,需要上移一个像素
1 |
|
属性
19. 使用弱属性机制,可以存储临时的值用于传递判断。可以通过widget->dynamicPropertyNames()列出所有弱属性名称,然后通过widget->property(“name”)取出对应的弱属性的值
22. 善于利用QComboBox的addItem的第二个参数设置用户数据,可以实现很多效果,使用itemData取出来
162. Qt提供了N种窗体属性比如无边框属性FramelessWindowHint、不在任务栏显示属性Tool等,有时候我们需要对窗口的属性进行动态设置,比如增加一个属性或者移除一个属性,Qt5.9以前需要拿到原有的窗体属性做运算,后面可以用新的方法
1 |
|
IDE
29. Qtcreator软件的配置文件存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有时候如果发现出问题了,将这个文件夹删除后打开creator自动重新生成即可
48. 新版的Qtcreator增强了语法检查,会弹出很多警告提示等,可以在插件列表中关闭clang打头的几个即可,Help》About Plugins。也可以设置代码检查级别,Tools》Options 》C++ 》Code Model
148. QtCreator集成开发环境,也内置了对快速添加注释的支持,比如最常用的在头文件开头添加一大段通用模板的注释,标注文件创建者、时间等信息
- 菜单->工具->选项->文本编辑器->右侧tab页面片段(snippets);
- 组选择C++, 可以看到这里面已经内置了不少定义比如foreach,可以依葫芦画瓢;
- 添加一个片段, 比如名字是fun, 触发种类是这个片段的简单描述;
- 当我们在代码文件中键入fun时, 会自动弹出智能提醒, 选择我们的代码片段回车, 自动填充代码;
- 按tab可以在变量间切换, 输入完成后回车, 完成编辑;
1 |
|
网络
51. 在某些http post数据的时候,如果采用的是&字符串连接的数据发送,中文解析乱码的话,需要将中文进行URL转码
1 |
|
56. 在很多网络应用程序,需要自定义心跳包来保持连接,不然断电或者非法关闭程序,对方识别不到,需要进行超时检测,但是有些程序没有提供心跳协议,此时需要启用系统层的保活程序,此方法适用于TCP连接
1 |
|
89. 理论上串口和网络收发数据都是默认异步的,操作系统自动调度,完全不会卡住界面,网上那些说收发数据卡住界面主线程的都是扯几把蛋,真正的耗时是在运算以及运算后的处理,而不是收发数据,在一些小数据量运算处理的项目中,一般不建议动用线程去处理,线程需要调度开销的,不要什么东西都往线程里边扔,线程不是万能的。只有当真正需要将一些很耗时的操作比如编码解码等,才需要移到线程处理
92. 新版的QTcpServer类在64位版本的Qt下很可能不会进入incomingConnection函数,那是因为Qt5对应的incomingConnection函数参数变了,由之前的int改成了qintptr,改成qintptr有个好处,在32位上自动是quint32而在64位上自动是quint64,如果在Qt5中继续写的参数是int则在32位上没有问题在64位上才有问题,所以为了兼容Qt4和Qt5,必须按照不一样的参数写
1 |
|
107. 很多初学者甚至几年工作经验的人,对多线程有很深的误解和滥用,尤其是在串口和网络通信这块,什么都往多线程里面丢,一旦遇到界面卡,就把数据收发啥的都搞到多线程里面去,殊不知绝大部分时候那根本没啥用,因为没找到出问题的根源
- 如果你没有使用wait***函数的话,大部分的界面卡都出在数据处理和展示中,比如传过来的是一张图片的数据,你需要将这些数据转成图片,这个肯定是耗时的;
- 还有就是就收到的数据曲线绘制出来,如果过于频繁或者间隔过短,肯定会给UI造成很大的压力的,最好的办法是解决如何不要频繁绘制UI比如合并数据一起绘制等;
- 如果是因为绘制UI造成的卡,那多线程也是没啥用的,因为UI只能在主线程;
- 串口和网络的数据收发默认都是异步的,由操作系统调度的,如果数据处理复杂而且数据量大,你要做的是将数据处理放到多线程中;
- 如果没有严格的数据同步需求,根本不需要调用wait***之类的函数来立即发送和接收数据,实际需求中大部分的应用场景其实异步收发数据就足够了;
- 有严格数据同步需求的场景还是放到多线程会好一些,不然你wait***就卡在那边了;
- 多线程是需要占用系统资源的,理论上来说,如果线程数量超过了CPU的核心数量,其实多线程调度可能花费的时间更多,各位在使用过程中要权衡利弊;
- 再次强调,不要指望Qt的网络通信支持高并发,最多到1000个能正常工作就万事大吉,一般建议500以内的连接数。有大量高并发的需求请用第三方库比如swoole等。
168. 在网络通信中,无论是tcp客户端还是udp客户端,其实都是可以绑定网卡IP和端口的,很多人只知道服务端可以指定网卡监听端口。客户端如果没有绑定通信端口则由客户端所在的操作系统随机递增分配的,这里为啥这么强调,因为无数人,甚至不乏一些多年经验的新时代农名工,以为客户端的端口是服务端分配的,因为他们看到在服务端建立连接后可以打印出不同的端口号。网络通信的双方自己决定自己要用什么端口,服务器端只能决定自己监听的是哪个端口,不能决定客户端的端口,同理客户端也只能决定自己的端口。端口随机分配一般是按照顺序递增的,比如先是45110端口,连接重新建立就用45111端口,只要端口没被占用就这样递增下去,所以很多人会问是否可以复用一些端口,不然端口一直这样频繁的分配下去不妥,甚至有些特定的场景和需求也是会要求客户端绑定网卡和端口来和服务器通信的
1 |
|
169. 关于网络通信,tcp和udp是两种不同的底层的网络通信协议,两者监听和通信的端口互不相干的,不同的协议或者不同的网卡IP地址可以用相同的端口。之前有个人说他的电脑居然可以监听一样的端口进行通信,颠覆了他以前的认知,书上说的明明是不可以相同端口的,后面远程一看原来选择的不同的网卡IP地址,当然可以的咯
- tcp对网卡1监听了端口6000,还可以对网卡2监听端口6000。
- tcp对网卡1监听了端口6000,udp对网卡1还可以继续监听端口6000。
- tcp对网卡1监听了端口6000,在网卡1上其他tcp只能监听6000以外的端口。
- udp协议也是上面的逻辑。
174. 在网络请求中经常涉及到超时时间的问题,因为默认是30秒钟,一旦遇到网络故障的时候要等好久才能反应过来,所以需要主动设置下超时时间,超过了就直接中断结束请求。从Qt5.15开始内置了setTransferTimeout来设置超时时间,非常好用
1 |
|
其他
30. QMediaPlayer是个壳(也可以叫框架),依赖本地解码器,视频这块默认基本上就播放个MP4甚至连MP4都不能播放,如果要支持其他格式需要下载k-lite或者LAV Filters安装即可(k-lite或者LAV Filters是指windows上的,其他系统上自行搜索,貌似嵌入式linux上依赖GStreamer,并未完整验证)。如果需要做功能强劲的播放器,初学者建议用vlc、mpv,终极万能大法用ffmpeg(解码出来的视频可以用QOpenGLWidget走GPU绘制或者转成QImage绘制,音频数据可以用QAudioOutput播放)
41. 如果使用sqlite数据库不想产生数据库文件,可以创建内存数据库
1 |
|
49. QSqlTableModel的rowCount方法,默认最大返回256,如果超过256,可以将表格拉到底部,会自动加载剩余的,每次最大加载256条数据,如果需要打印或者导出数据,记得最好采用sql语句去查询,而不是使用QSqlTableModel的rowCount方法。不然永远最大只会导出256条数据
如果数据量很小,也可以采用如下方法:
1 |
|
62. 对于大段的注释代码,建议用 #if 0 #endif 将代码块包含起来,而不是将该段代码选中然后全部双斜杠注释,下次要打开这段代码的话,又需要重新选中一次取消,如果采用的是 #if 0则只要把0改成1即可,开发效率提升很多
88. Qt源码中内置了一些未公开的不能直接使用的黑科技,都藏在对应模块的private中,比如gui-private widgets-private等,比如zip文件解压类QZipReader、压缩类QZipWriter就在gui-private模块中,需要在pro中引入QT += gui-private才能使用
1 |
|
91. 数据库处理一般建议在主线程,如果非要在其他线程,务必记得打开数据库也要在那个线程,即在那个线程使用数据库就在那个线程打开,不能打开数据库在主线程,执行sql在子线程,很可能出问题
105. 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系统上运行 |
116. c++11新引入了原始字符串格式,用户避免在字符串中加入转义字符\,可以用于表示json字符串等场景
1 |
|
120. 用QSettings设置注册表,如果不是管理员身份运行会打印 QSettings: failed to set subkey “xxx” (拒绝访问。),你需要手动鼠标右键管理员身份运行就可以
129. Qt对操作系统层的消息也做了很多的封装,可以直接拿到进行处理(如果需要拦截处理要用对应操作系统的API才行比如鼠标键盘钩子),比如系统休眠和唤醒做一些处理
1 |
|
135. Qt的ini配置文件默认不支持直接读写中文,需要手动设置下编码格式才行,强烈建议统一用utf-8编码,包括代码文件
1 |
|
137. Qt重载qDebug输出自定义的信息
1 |
|
144. 关于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未做测试,不清楚是否机制一样;
154. 在数据库相关的应用中,如果仅仅是单机版本,没有特别的需要(比如领导指定,或者需要远程存放数据),强烈建议使用sqlite数据库,这是本人经过无数次的对比测试和N个商业项目应用得出的结论
- Qt天生内置了sqlite数据库,只需要发布的时候带上插件就行(可以看到插件动态库文件比其他几种都要大,那是因为直接将数据库的源码都编译进去了,而其他只编译了中间通信交互的插件源码),其他数据库要么还要带上动态库,要么还需要创建数据源;
- 速度上,绝对无与伦比的出类拔萃,同样的数据库结构(表结构、索引等完全一致),查询速度和批量更新速度、数据库事务等,速度都是其他几种的至少3倍以上,而且随着数据量的增大对比越发明显;
- 几千万的数据量完全没问题,而且速度和性能都还可以,不要以讹传讹网上部分菜鸡说的不支持百万以上的数据量,本人亲测亿级别,数据量建议千万级别以下,着重注意数据库表和索引的设计;
- 其他数据库还要注意版本的区别,ODBC数据源形式还容易出错和执行失败;
- sqlite数据库也有几个重大缺点:不支持加密,不支持网络访问,不支持部分数据库高级特性,不支持海量数据(亿级别以上),但是对于绝大部分Qt项目还是足够;
- 数据库支持友好度大致是 sqlite > postgresql > mysql > odbc ;
- 以上都是在Qt环境中个人测试得出的结论,结果未必正确,作为参考即可,其他编程环境比如C#、JAVA请忽略,也许差别可能在中间通信的效率造成的;
170. 开源的图表控件QCustomPlot很经典,在曲线数据展示这块性能彪悍,总结了一些容易忽略的经验要点
- 可以将XY轴对调,然后形成横向的效果,无论是曲线图还是柱状图,分组图、堆积图等,都支持这个特性。
- 不需要的提示图例可以调用 legend->removeItem 进行移除。
- 两条曲线可以调用 setChannelFillGraph 设置合并为一个面积区域。
- 可以关闭抗锯齿 setAntialiased 加快绘制速度。
- 可以设置不同的线条样式(setLineStyle)、数据样式(setScatterStyle)。
- 坐标轴的箭头样式可更换 setUpperEnding。
- 可以用 QCPBarsGroup 实现柱状分组图,这个类在官方demo中没有,所以非常容易忽略。
1 |
|
180. QSqlTableModel大大简化了对数据库表的显示、添加、删除、修改等,唯独对数据库分页操作有点绕弯
1 |
|
183. 自从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指针。
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!