0%

Qt的一些开发技巧

Lambda 匿名函数

  • 有时候槽函数代码辑逻辑非常简单,可以直接用下面的Lambda匿名函数处理信号,简捷明了.需c++11支持,不支持自身递归调用.

    1
    2
    3
    4
    5
    6
    7
    QComboBox *cb = new QComboBox(this);
    QObject::connect(cb,&QComboBox::currentTextChanged,[=](QString txt){
    [...]
    baseform->changeJsonValue(btn,uname,
    baseform->mWindow->mItemMap.key(txt));
    [...]
    });
  • 函数内部的匿名函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    void ActionList::onCustomContextMenu(const QPoint &pos)
    {

    [...]
    auto sawp_lambda_func = [this](int src,int dst) {

    QVariantList oldvals;
    for(int i = 0 ; i < mTable->columnCount();i++)
    {
    QWidget *w = mTable->cellWidget(src,i);


    if(!QString::compare(w->metaObject()->className(),"QComboBox"))
    {
    oldvals.append(((QComboBox*)w)->currentText());
    }else{
    oldvals.append(((QLineEdit*)w)->text());
    }
    QObject::disconnect(w);
    mTable->removeCellWidget(src,i);
    w->deleteLater();

    }
    mTable->removeRow(src);
    [...]
    };


    QAction up(QIcon(":/icon/icons/act_up.png") ,
    QString("上移一行"),this);
    QObject::connect(&up,
    &QAction::triggered,[=](){
    sawp_lambda_func(line,line-1);
    });
    [...]
    }

模拟事件

  • 一些GUI的操作,需要一些事件来完成,下面模拟一个鼠标Release的事件

    1
    2
    3
    4
    5
    [...]
    QMouseEvent *event = new QMouseEvent(QMouseEvent::MouseButtonRelease,QCursor::pos(),
    Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
    QApplication::postEvent(this,event);
    [...]
  • 把事件转换成对其它控件的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void NewGrid::wheelEvent(QWheelEvent *event)
{

if(sliderOrientation == Qt::Horizontal)
{
if(event->orientation() != Qt::Horizontal)
{
QWheelEvent *evt = new QWheelEvent(event->pos(),event->globalPos(),
event->delta(),
event->buttons(),
event->modifiers(),Qt::Horizontal);
QApplication::postEvent(mainScroll->horizontalScrollBar(),evt);
}
}

}

元对像,动态属性

Meta-Object System 的基本功能

  • Meta Object System的设计基于以下几个基础设施:
  • QObject
    • 作为每一个需要利用元对象系统的类的基类
  • QOBJECT
    • 定义在每一个类的私有数据段,用来启用元对象功能,比如,动态属性,信号和槽
  • 元对象编译器moc (the Meta Object Complier)
    • moc分析C++源文件,如果它发现在一个头文件(header file)中包含Q_OBJECT宏定义,然后动态的生成另外一个C++源文件,这个新的源文件包含Q_OBJECT实现代码,这个新的C++源文件也会被编译、链接到这个类的二进制代码中去,因为它也是这个类的完整的一部分.通常,这个新的C++源文件会在以前的C++源文件名前面加上moc作为新文件的文件名.其具体过程如下图所示:
  • 除了提供在对象间进行通讯的机制外,元对象系统还包含以下几种功能:
    • QObject::metaObject()方法
      • 它获得与一个类相关联的meta-object
        -QMetaObject::className()方法
      • 在运行期间返回一个对象的类名,它不需要本地C++编译器的RTTI(run-time type information)支持
    • QObject::inherits()方法
      • 它用来判断生成一个对象类是不是从一个特定的类继承出来,当然,这必须是在QObject类的直接或者间接派生类当中
    • QObject::tr()andQObject::trUtf8()
      • 这两个方法为软件的国际化翻译字符串
    • QObject::setProperty()andQObject::property()
      • 这两个方法根据属性名动态的设置和获取属性值,读写属性
1
2
3
4
5
6
QWidget *w = new QWidget()
w->setProperty("ABC1",4);
w->setProperty("ABC2","dddddd");

qDebug() << w->property("ABC1") << w->property("ABC2");

布署安装

  • 对于开发QT程序来说,最好布署方法就是编译成静态执行文件,只是文件大一点.如果是动态编译就会有一些动态库链接路径的问题.比较特别的它要信赖platforms这个文件路经.可以通过 **QCoreApplication::addLibraryPath(“C:/WINDOWS/System32”);**来指定.

  • 下载安装

  • 参考文档,从样本模版里复制一份出来做相应的修改.

  • 一个安装包的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
D:\QtDev\QtInstaller\netmon-root>tree /F
卷 新加卷 的文件夹 PATH 列表
卷序列号码为 0006EE44 E462:E6DB
D:.
│ build.bat

├─config
│ config.xml

└─packages
└─com.vendor.product
├─data
│ │ cares.dll
│ │ libcjson.dll
│ │ libcrypto-1_1.dll
│ │ libcurl.dll
│ │ libeay32_.dll
│ │ libgcc_s_dw2-1.dll
│ │ libmosquitto.dll
│ │ libssl-1_1.dll
│ │ libssp-0.dll
│ │ libstdc++-6.dll
│ │ libwinpthread-1.dll
│ │ libz_.dll
│ │ netmon-tray.exe
│ │ netmonui_i686.dll
│ │ print-mon_i686.dll
│ │ qt.conf
│ │ Qt5Core.dll
│ │ Qt5Gui.dll
│ │ Qt5Network.dll
│ │ Qt5Widgets.dll
│ │ ssleay32_.dll
│ │
│ ├─imageformats
│ │ qdds.dll
│ │ qgif.dll
│ │ qicns.dll
│ │ qico.dll
│ │ qjpeg.dll
│ │ qsvg.dll
│ │ qtga.dll
│ │ qtiff.dll
│ │ qwbmp.dll
│ │ qwebp.dll
│ │
│ └─platforms
│ qminimal.dll
│ qoffscreen.dll
│ qwindows.dll

└─meta
installscript.qs
license.txt
package.xml
page.ui
  • package.xml
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<Package>
<DisplayName>显示名字</DisplayName>
<Description>随便写一些东西</Description>
<Version>0.1.0-1</Version>
<ReleaseDate>2017-05-11</ReleaseDate>
<Default>script</Default>
<Script>installscript.qs</Script>
<UserInterfaces>
<UserInterface>page.ui</UserInterface>
</UserInterfaces>
</Package>
  • config.xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<Installer>
<Name>程序名字</Name>
<Version>1.0.0</Version>
<Title>程序的抬头</Title>
<Publisher>yjdwbj@gmail.com</Publisher>
<StartMenuDir>网络打印客户端</StartMenuDir>
<TargetDir>@ApplicationsDir@/NetMon</TargetDir>
</Installer>
  • isntallscript.qs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

function Component()
{
// constructor
component.loaded.connect(this, Component.prototype.loaded);
// 安装完成后自动连接到自动运行的函数
installer.installationFinished.connect(this,Component.prototype.InstallationFinishedAndRun);
installer.finishButtonClicked.connect(this,Component.prototype.installationFinished);
// if (!installer.addWizardPage(component, "Page", QInstaller.TargetDirectory))
// console.log("Could not add the dynamic page.");

}

Component.prototype.createOperations = function()
{
try {
// call the base create operations function
component.createOperations();
component.addOperation("CreateShortcut","C:/WINDOWS/System32/netmon-tray.exe",
"@StartMenuDir@/网络打印机客户端/网络打印机客户端.lnk","@workDirectory=@TargetDir@");
component.addOperation("CreateShortcut","@TargetDir@/maintenancetool.exe",
"@StartMenuDir@/网络打印机客户端/卸载.lnk","@workDirectory=@TargetDir@");

} catch (e) {
console.log(e);
}
}


Component.prototype.createOperationsForArchive = function(archive)
{
# packages\com.vendor.product\data 下面文件全部解压到system32里.
component.addOperation("Extract", archive, "C:/WINDOWS/System32");
}


Version:1.0 StartHTML:0000000107 EndHTML:0000006210 StartFragment:0000000471 EndFragment:0000006172
Component.prototype.InstallationFinishedAndRun = function()
{
try {
if(installer.isInstaller() && installer.status == QInstaller.Success)
{
installer.addWizardPageItem(component,"Page",QInstaller.InstallationFinished);
}
} catch (e) {
console.log(e);
}
}

Component.prototype.installationFinished = function()
{
try {
if(installer.isInstaller() && installer.status == QInstaller.Success)
{
var isRun = component.userInterface("Page").runCheckbox.checked;
if(isRun)
{
QDesktopServices.openUrl("C:/WINDOWS/System32/netmon-tray.exe");
}
}
} catch (e) {
console.log(e);
}
}
  • Page.ui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Page</class>
<widget class="QWidget" name="Page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dynamic page example</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="m_pageLabel">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="runCheckbox">
<property name="text">
<string>安装完成后,运行该程序</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
  • 最后这里用一行脚本来生成一键安装包文件.
1
binarycreator --offline-only -c D:\QtDev\QtInstaller\netmon-root\config\config.xml -p D:\QtDev\QtInstaller\netmon-root\packages  "Z:/upload/appname-测试版%DATE%-%TIME::=_%.exe"

调试变量

  • Qt plugin版本不兼容的问题,有时Qt莫名闪退,需设置export QT_DEBUG_PLUGINS=1,方可以看下面的加载.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    Found metadata in lib /fullpath/plugins/sqldrivers/libqsqlite.so, metadata=
    {
    "IID": "org.qt-project.Qt.QSqlDriverFactoryInterface",
    "MetaData": {
    "Keys": [
    "QSQLITE"
    ]
    },
    "archreq": 0,
    "className": "QSQLiteDriverPlugin",
    "debug": false,
    "version": 331264
    }


    Got keys from plugin meta data ("QSQLITE")
    QFactoryLoader::QFactoryLoader() looking at "/fullpath/plugins/sqldrivers/libsqlitecipher.
    Found metadata in lib /fullpath/plugins/sqldrivers/libsqlitecipher.so, metadata=
    {
    "IID": "org.qt-project.Qt.QSqlDriverFactoryInterface",
    "MetaData": {
    "Keys": [
    "SQLITECIPHER"
    ]
    },
    "className": "SqliteCipherDriverPlugin",
    "debug": false,
    "version": 330499
    }
  • 使用export QT_FATAL_WARNINGS=1,可以让程序的在警告的位置崩溃退出,并打印调用stack frame.

  • Could not initialize GLX 错误,尝试设置 export QT_XCB_GL_INTEGRATION=none是可以处理.

  • export USE_WOLFRAM_LD_LIBRARY_PATH=1

1
2
QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled

  • 关于跨平台的一些全局宏定义如:Q_OS_WIN,Q_OS_MAC,...可以查询参考https://doc.qt.io/qt-5/qtglobal.html

  • 对于工程内的模块,不要加上CONFIG += debug_and_release或者CONFIG += build_all配置,除非是要把它做动态库发布出去的文件,否则这只会增加编译时间,编译了一些必要的对像.

QtCreator调试设置

  • QtCreator的调试插件Using Debugging Helpers,可以参考它,对GDB做一些调脚本.常规选项如,Debugger -> Locals & Expressions -> Display string legnth,默认100字节,可能对于调一些大的json结构体会不够.

Visual Studio上的开发

安装

导入Qt工程

  • msvc也是一个很复杂,很高效的开发工具,如果在windows上调试,还是为它最合适,比竟都是它自家的东西.所以可以通过使用qmake -tp vc -r <your project>.pro的方式,把Qt工程转换成msvc工程,转换后的工程,可能需要少许的手动修改.

  • cl.exe所在位置加入到,系统Path变量里去,用power shell运行qmake -tp vc ../your-project.pro.

  • 或者,通过开始菜单,找到Visual Studio 2019目录,运行x86 Native Tools Command Prompt for VS 2019, 切换到工程目录运行qmake -tp vc.

  • 或者,进入C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build,运行相应的环境Shell.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build"
    PS C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build> ls

    目录: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build

    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    d----- 2021/5/11 9:26 14.16
    -a---- 2021/5/11 9:20 13 Microsoft.VCRedistVersion.default.txt
    -a---- 2021/5/11 9:20 393 Microsoft.VCToolsVersion.default.props
    -a---- 2021/5/11 9:20 13 Microsoft.VCToolsVersion.default.txt
    -a---- 2021/5/11 9:26 401 Microsoft.VCToolsVersion.v141.default.props
    -a---- 2021/5/11 9:26 13 Microsoft.VCToolsVersion.v141.default.txt
    -a---- 2021/5/11 9:20 401 Microsoft.VCToolsVersion.v142.default.props
    -a---- 2021/5/11 9:20 13 Microsoft.VCToolsVersion.v142.default.txt
    -a---- 2021/5/11 9:20 39 vcvars32.bat
    -a---- 2021/5/11 9:20 39 vcvars64.bat
    -a---- 2021/5/11 9:20 9859 vcvarsall.bat
    -a---- 2021/5/11 9:20 43 vcvarsamd64_x86.bat
    -a---- 2021/5/11 9:20 43 vcvarsx86_amd64.bat
  • 命令行编译QT,
    Build QT Command Line for Window

    1
    qmake -spec win32-msvc "CONFIG+=qtquickcompiler" ../src/youduqt.pro
  • UTF-8类型添加BOM标识,因为windows下有些程序需要认它.

    1
    2
    printf '\xEF\xBB\xBF' > report_new.csv
    cat report >> report_new.csv
  • 或者这样

    1
    sed -i '1s/^/\xef\xbb\xbf/' file-encoded-with-utf8.txt
  • vs2019奇怪错误,vs2019 C1075, 这个问题在Linux下编译正常,在windows下出错,这是因为这个文件是在Linux下创建,不是UTF8-BOM,所以才出错.这是在不系统下切换开发环境才会碰到的.

调试设置

  • IDE_Debug_Helpers
  • Natvis
  • 把一些*.natvis文件放入到C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Packages\Debugger\Visualizers里.可以支持调试Qt工程序时,查看对像内的内容,而不是一个指针值.

一些工程总结

  • 工程拆分模块,尽量可以动/静态库的方式链接或者耦合,面向接口编程.
  • 对于可以共用的模块功能,中间的类名,尽量以抽像方式命名,不要带有具体名称,尤其是产品名,公司名.如果要把它移到别的产品就会有点别扭.
  • 对于一些相同功能的类,又没有共同继父类的,类之间尽量不要有全名匹配相同的函数名,当工程到了一规模时,搜索函数名时可以高效过滤找到.
  • 对于大的工程,尽量不要滥用,如:use namespace std;, 对于规模比较大的工程,文件行数多时,在review时无法直观知道它所属的命名空间,而因此碰到命名冲突的概率也很高.
  • 成员变量必须要有一个前缀,如:m_XXXXX,方便有别于局部变量区分.

错误总结

  • Qt5加载QSS资源问题,这个是因为QSS的语法或者配置错误所造成的,会使整个QSS环境坏了.遇到过因为border-image: url(:/x_wnd/home_086@2x.png);而出现下面错误.因为url里的路径里含有@字符,对于路径最好是用双引号(“”)包括起来.而一般的UI工程师切图导出的文件,就是带@符号来区分尺寸的.但是发现在QT C++里写obj->setButtonImage(":/main_wnd/new/02_003_01@2x.png");是可以识别,是正常可行的.
    1
    2
    3
    4
    5
    Could not parse stylesheet of object 0x55dabc30bd60
    Could not parse stylesheet of object 0x55dabc2e8220
    Could not parse stylesheet of object 0x55dabc30bd60
    Could not parse stylesheet of object 0x55dabc30bd60
    Could not parse stylesheet of object 0x55dabc30bd60

QWebChannel使用

谢谢支持