您现在的位置是:网站首页> C/C++

QT学习笔记归纳

  • C/C++
  • 2024-12-15
  • 2735人已阅读
摘要

QT学习笔记归纳


QT学习总结

Qt5.14编译

VS开发QT

Qt Creator中,include路径包含过程(或如何找到对应的头文件)

QT富文本编辑QTextDocument

Qt学习感悟

Qt编译一般的EXE

Qt编译qml应用程序

Qt程序打包发布方法(使用官方提供的windeployqt工具)

全网最靠谱的QML程序发布

Qt6构建于打包发布

Qt宏判断平台

Qt pro平台判断

Qt加库

Qt Android基本配置

Qt Java数据类型对照

Qt android调用jar包,字节数组参数类型的函数

用Qt第一次开发IOS的记录

QString中文乱码问题

Qt Android启动黑屏处理

Qt Android开发资料收集

Android Services

QML ajax请求

QMediaPlayer视频没有画面显示,或播放卡顿问题解决(DirectShowPlayerService::doRender: Unresolved error code 80040266)

QT读写NFC

Qt中低功耗蓝牙模块使用

Qt 串口通信(QSerialPort)

C++中的关键字explicit

QVariant使用

QString的数据转换

QT MDI

Qt 中文显示

QT定位组件

Qt 动态加载动态库

Qt 之 pro 配置详解

Qt自学经验

QT中文乱码问题处理

QT配置加载头文件及库

QT根据类名动态创建类

qt代码中判断windows/linux/arm等系统

QT类属性介绍

Qt进程与线程

Qt-QProcess-启动子进程-控制台进程隐藏-获取子进程标准输出和返回码

Qt 之进程间通信(IPC)

Qt 之进程间通信(TCP/IP)

Qt 之进程间通信(Windows 消息)

Qt 之进程间通信(共享内存)

Qt 之进程间通信(QProcess)

Qt线程同步的几种方法

Qt6

Qt6 C++ 实现按住窗口拖拽移动

Qt6 C++ 右键弹出菜单栏

Qt6 C++基础入门4 事件处理与eventFilter检测

Qt6 C++基础入门3 对话框与MainWindow

Qt6 C++基础入门2 文件结构与信号和槽

Qt6 C++基础入门1 定时器与QTimer

Qt6 C++ 网络请求实例

Qt资源文件相关

QT之qss教程-qss文件使用方法

QT皮肤(QSS)编程

QT qss界面美化总结

Qt 之资源系统

QT中资源文件的使用

QT 直接添加资源

Qt中使用qrc管理和使用资源文件

qt开发的APP发布到appstore

qt编写触摸事件的关键



Qt5.14编译

Qt5.14编译

用Android Studio 3.2编译

首先改地址

1.png

distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip

build.gradle文件该动


数字:

repositories {

    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }

    maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }

    maven { url 'http://maven.aliyun.com/nexus/content/repositories/google' }

    maven { url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }


}

特别注意编译出X86 等

2.png


Qt Creator中,include路径包含过程(或如何找到对应的头文件)

Qt Creator中,include路径包含过程(或如何找到对应的头文件)

QT的include目录里的目录在pro里对应包含

QT += core gui  network //表示使用QT include目录下的QtCore QtGui  QtNetwork

在.pro中自定义INCLUDE包含路径

INCLUDEPATH += <your path>//c://123//1

INCLUDEPATH += $$PWD/lib/x64-win64

固有变量

$$PWD的意思为当前目录,就是pro文件所在的目录



QT富文本编辑QTextDocument

Qt富文本使用-QTextEdit,QTextBlock,QTextFrame,QTextTable,QTextList,QTextImage


Qt宏判断平台

#if defined(Q_OS_ANDROID)

我们在使用QT编程的时候,有时会遇到跨平台的状况(毕竟QT本身就是因优秀的跨平台特性而出名),在这种情况下,判断当前是哪一种平台就非常必要了,这里介绍一下QT提供的各种判断操作系统和编译平台的做法:


1、如果仅仅想要当前是windows、mac还是linux系统的话,可以直接使用宏:


Q_OS_LINUX:定义在linux系统中


Q_OS_WIN:定义在windows系统中


Q_OS_OSX:定义在OS X系统中


2、如果还想更细致的判断系统,比如当前是windows 32位系统还是64位系统,是IOS系统还是Android系统,又或者当前是否在ARM环境中?当然也是可以的:


Q_OS_WIN64:定义在windows 64位系统中


Q_OS_IOS:定义在IOS系统中


Q_OS_ANDROID:定义在Android系统中


Q_PROCESSOR_ARM:是否为ARM处理器编译环境


3、如果还不满意,比如我想知道当前的windows系统是winXP、win7、还是win10,那肯定也是有的,不过这就不在预编译里面判断了,需要在代码中进行判断,相对于普通的C++,QT封装了很多好用的类,有些类可以帮助我们更快速更方便的使用C++,比如QString类,从此妈妈再也不用担心我不会用std::string,又比如QSysInfo类,看名字就知道这个类和系统信息有关,而我们用来识别操作系统就是用这个类进行判断的。首先看看QT文档中的介绍:


翻译:


QSysInfo类提供有关系统的信息。

WordSize:判断编译应用程序的平台的指针大小(注:不同操作系统和编译环境下指针大小不同)。

ByteOrder:判断当前平台是大端还是小端。

WindowsVersion:判断运行应用程序的Windows操作系统的版本。

MacintoshVersion:判断运行应用程序的Macintosh操作系统的版本。

某些常量仅在指定平台上定义。 您可以使用预处理器符号Q_OS_WIN和Q_OS_OSX来测试应用程序当前是在Windows还是OS X下编译。

看完上面的介绍大概就清楚了,利用QSysInfo可以得到各种操作系统的信息,比如,我最近想要将一个软件放在windows XP中运行,其中有一个dll库仅支持windows7及以上的windows版本,那么可以在代码中这样判断:


    if(QSysInfo::windowsVersion() == QSysInfo::WV_XP) {

        QMessageBox msgBox;

        msgBox.setText(QStringLiteral("产品暂不支持windows XP操作系统"));

        msgBox.exec();

        return false;

    }

这样就很方便的将不支持XP系统的功能避开,避免出现类似于【无法定位程序输入点XXX于动态链接库KERNEL32.dll上】这样的问题


ApplicationWindow {

    visible: true

    width:  Qt.platform.os === "android"? Screen.width: 640

    height:  Qt.platform.os === "android"? Screen.height: 480

    title: qsTr( "Android Camera" )



Qt pro平台判断


ios

{

   LIBS += -framework Foundation -framework UIKit -framework Contacts -framework ContactsUI

   OBJECTIVE_SOURCES += \

        SamsonQt_IOS.mm

}


android:contains(QT_ARCH, arm) {

  message("arm32!");

}


android:contains(QT_ARCH, arm64) {

  message("arm64!");

}


unix

{

}


win32

{

contains(QT_ARCH, i386) {

        message("32-bit")

    }else {

        message("64-bit")

    }

}



Qt加库

向QT环境中添加FFMPEG的库和头文件

打开QT的工程文件(xx.pro),加入FFMPEG的库路径和头文件路径


#指定库文件的路径

unix:LIBS += -L$$PWD/so_file -lavcodec

unix:LIBS += -L$$PWD/so_file -lavfilter

unix:LIBS += -L$$PWD/so_file -lavutil

unix:LIBS += -L$$PWD/so_file -lavdevice

unix:LIBS += -L$$PWD/so_file -lavformat

unix:LIBS += -L$$PWD/so_file -lpostproc

unix:LIBS += -L$$PWD/so_file -lswscale

unix:LIBS += -L$$PWD/so_file -lswresample

 

#指定头文件的路径

INCLUDEPATH+=$$PWD/so_file/include

然后再做最重要的一步,向Android里添加用到的FFMPEG动态库:

1.png


如果这里不添加,当程序部署到Android设备上时,会因为找不到运行需要的动态库而崩溃。

添加成功之后,在xx.pro文件里会自动生成代码:(下面是我自己添加的库,需要用到 的库)

contains(ANDROID_TARGET_ARCH,arm64-v8a) {

    ANDROID_EXTRA_LIBS = \

        $$PWD/so_file/libavcodec.so \

        $$PWD/so_file/libavfilter.so \

        $$PWD/so_file/libavformat.so \

        $$PWD/so_file/libavutil.so \

        $$PWD/so_file/libpostproc.so \

        $$PWD/so_file/libswresample.so \

        $$PWD/so_file/libswscale.so \

        $$PWD/so_file/libavdevice.so \

        /home/wbyq/work_pc/AndroidPath/android-ndk-r19c/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/8.0.2/lib/linux/libclang_rt.ubsan_standalone-aarch64-android.so

库添加之后,就可以正常的开发程序了。


如果需要在代码里调用FFMPEG打开Android设备的摄像头,如果是直接打开/dev/videoX设备。在程序里是打不开的,需要使用ADB命令进入到Android设备里,修改/dev/videoX设备具有可读可写的权限,才能正常打开使用。

程序编译时,需要选择正确的编译器,FFMPEG库使用什么编译器版本,QT编译时就得对应。

比如: 编译FFMPEG时使用arm64-v8a 那么,QT的编译器也得一样,否则会导致库的版本不对,而链接失败

}



Qt Android基本配置

Qt 申请Android 权限的类是在 QtAndroid 中,要使用该类,首先需要在工程文件中声明该模块

QT += androidextras


动态获取权限的接口。

bool MainWindow::requestPermission()

{

    QtAndroid::PermissionResult r = QtAndroid::checkPermission("android.permission.WRITE_EXTERNAL_STORAGE");

    if(r == QtAndroid::PermissionResult::Denied) {

        QtAndroid::requestPermissionsSync(QStringList()<<"android.permission.WRITE_EXTERNAL_STORAGE");

        r = QtAndroid::checkPermission("android.permission.CAMERA");

        if(r == QtAndroid::PermissionResult::Denied) {

             return false;

        }

   }

   return true;

}

使用权限前调用函数requestPermission();就可以了

但是里面要修改成相应权限

android.permission.WRITE_EXTERNAL_STORAGE

android.permission.READ_EXTERNAL_STORAGE

android.permission.CAMERA



Android常用权限:AndroidManifest.xml中

android:usesCleartextTraffic="true" //  application内2、照相权限:<uses-permission android:name="android.permission.CAMERA"/>3、写权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>4、定位权限:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>其中照相和写权限仅仅这样声明是不可以的,还得在代码中动态申请权限


#include “permissions.h”

QtAndroid::PermissionResult r = QtAndroid::checkPermission(“android.permission.WRITE_EXTERNAL_STORAGE”);

if(r == QtAndroid::PermissionResult::Denied) {

QtAndroid::requestPermissionsSync( QStringList() << “android.permission.WRITE_EXTERNAL_STORAGE” );

r = QtAndroid::checkPermission(“android.permission.WRITE_EXTERNAL_STORAGE”);

if(r == QtAndroid::PermissionResult::Denied) {

return false;

}

}

QtAndroid::PermissionResult p = QtAndroid::checkPermission(“android.permission.CAMERA”);

if(p == QtAndroid::PermissionResult::Denied) {

QtAndroid::requestPermissionsSync( QStringList() << “android.permission.CAMERA” );

p = QtAndroid::checkPermission(“android.permission.CAMERA”);

if(p == QtAndroid::PermissionResult::Denied) {

return false;

}



QString中文乱码问题

QString中文乱码问题

乱码问题通常会在有中文的时候出现,有两种写法可以避免乱码:


QString str1 = QString::fromLocal8Bit("你好");

QString str2 = QStringLiteral("你好");


通常情况下,需要中文的QString串的时候会使用这两种写法,且比较推荐QStringLiteral宏的方式进行创建


fromLocal8Bit使用

在Qt中,fromLocal8Bit()是QString类的一个成员函数,用于将本地编码(Local 8-bit)的字符串转换为QString对象。它的使用方法如下:


QByteArray localData = "你好"; // 本地编码的字符串

QString str = QString::fromLocal8Bit(localData); // 将本地编码的字符串转换为QString对象


在上述示例中,我们首先将本地编码的字符串存储在QByteArray对象中,然后使用fromLocal8Bit()函数将其转换为QString对象。


该函数的目的是解决在不同平台和不同编译环境中使用本地编码时可能出现的字符集问题。它会自动根据当前环境的本地编码进行字符集转换,以正确地表示字符串。


需要注意的是,fromLocal8Bit()函数只能用于处理本地编码的字符串,而不适用于其他编码格式。如果您的字符串使用的是其他编码,例如UTF-8或GBK,可以使用其他适当的函数进行转换,如fromUtf8()或fromUtf16()。


QByteArray utf8Data = "你好"; // UTF-8编码的字符串

QString str = QString::fromUtf8(utf8Data); // 将UTF-8编码的字符串转换为QString对象


QByteArray gbkData = "你好"; // GBK编码的字符串

QString str = QString::fromLocal8Bit(gbkData); // 将GBK编码的字符串转换为QString对象


从不同编码转换为QString时,确保使用适当的函数来处理相应的编码格式,以保证正确的字符集转换。


QStringLiterial使用

QStringLiteral是Qt提供的一个宏,用于创建编译时优化的QString对象,以提高性能和减少内存使用。下面是QStringLiteral的使用方法:


创建QString对象:

QString str1 = QStringLiteral("Hello"); // 直接使用QStringLiteral创建字符串

QString str2 = QStringLiteral("World");


字符串拼接:

QString str = str1 + QStringLiteral(" ") + str2; // 字符串拼接

str.append(QStringLiteral(" Qt")); // 使用append()函数拼接字符串


使用QStringLiteral创建的字符串对象在编译时会进行优化,避免了运行时的内存分配和拷贝操作,提高了性能和效率。它特别适用于常量字符串的创建和使用,特别是在频繁创建和使用字符串对象的场景。


请注意,QStringLiteral只能用于创建静态字符串,不能用于动态生成的字符串。此外,在某些特定情况下,编译器可能会自动将字符串常量隐式转换为QStringLiteral,因此在使用QStringLiteral时应注意编译器的行为。


QString 转 char* 中文乱码解决

先将QString转换为标准库中的string类型,然后将string转换为char* ;


std::string str = filename.toStdString();

const char* ch = str.c_str();



Qt android调用jar包,字节数组参数类型的函数

带字节数组的jar包

jar包中字节数组参数的函数定义

4.png


Qt android调用jar包,字节数组参数类型的函数

该函数所在类的定义

5.png




调用jar包中的字节数组参数的函数

定义类的变量

6.png


QByteArray转换成jbyteArray

7.png


字节数组对于的signature为[B

8.png



使用callMethod方法调用函数


9.png

整体代码

10.png



Android Services

从Qt 5.7开始,你可以使用Qt创建Android服务。服务是一个在后台运行的组件,所以,它没有用户界面。它对于执行长期的操作非常有用,比如记录GPS,等待社交媒体通知等等。即使启动它的应用程序退出,一个服务也会继续运行。


组装服务

首先,按照Qt Creator:将应用程序部署到Android设备中的说明创建一个Android软件包目录。此目录包含 AndroidManifest.xml 文件。在包目录中,创建一个 src 目录,将在其中创建所有Java包和类。


创建服务类

您可以通过将类 QtService 或Android:Service扩展到Java类来创建服务。根据您是要在服务中使用Qt功能还是要从Java调用本机C ++函数,您需要扩展 QtService 或 Service 。让我们从一个简单的服务开始,如下所示:


import android.content.Context;

import android.content.Intent;

import android.util.Log;

import org.qtproject.qt.android.bindings.QtService;


public class QtAndroidService extends QtService

{

    private static final String TAG = "QtAndroidService";


    @Override

    public void onCreate() {

        super.onCreate();

        Log.i(TAG, "Creating Service");

    }


    @Override

    public void onDestroy() {

        super.onDestroy();

        Log.i(TAG, "Destroying Service");

    }


    @Override

    public int onStartCommand(Intent intent, int flags, int startId) {

        int ret = super.onStartCommand(intent, flags, startId);


        // 做一些工作


        return ret;

    }

}

启动服务

Android允许按需或在启动时启动服务。你也可以使用Qt来实现这两点。


开始按需服务

您可以通过以下方式启动服务。


直接从 C++ 使用QAndroidIntent和QJniObject,通过创建服务Intent并调用应用程序的主要活动方法startService():

auto activity = QJniObject(QNativeInterface::QAndroidApplication:context());

QAndroidIntent serviceIntent(activity.object(),

                             "org/qtproject/example/qtandroidservice/QtAndroidService");

QJniObject result = activity.callObjectMethod(

            "startService",

            "(Landroid/content/Intent;)Landroid/content/ComponentName;",

            serviceIntent.handle().object());

通过调用一个Java方法来启动服务。最简单的方法是在你的服务类中创建一个静态方法。

public static void startQtAndroidService(Context context) {

        context.startService(new Intent(context, QtAndroidService.class));

}

你可以使用以下JNI调用从C++中调用它。


QJniObject::callStaticMethod<void>(

    "org/qtproject/example/qtandroidservice/QtAndroidService",

    "startQtAndroidService",

    "(Landroid/content/Context;)V",

    QtAndroid::androidActivity().object());

在启动时启动服务

要在引导时运行服务,您需要一个BroadcastReceiver。


创建一个自定义的Java类。


public class QtBootServiceBroadcastReceiver extends BroadcastReceiver {

    @Override

    public void onReceive(Context context, Intent intent) {

        Intent startServiceIntent = new Intent(context, QtAndroidService.class);

        context.startService(startServiceIntent);

    }

}

在 AndroidManifest.xml 文件的 <manifest> 部分的正文中添加以下 uses-permission :


<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

另外,在 <application> 部分的正文中添加 receiver 方定义:


<receiver android:name=".QtBootServiceBroadcastReceiver">

    <intent-filter>

        <action android:name="android.intent.action.BOOT_COMPLETED" />

    </intent-filter>

</receiver>

注意: Android 8.0 对运行后台服务引入了一些限制,这意味着使用普通的 Service 类可能无法正常工作。有关更多信息,请参阅 Android 建议使用Foreground 服务或JobIntentService。


管理AndroidMnifest.xml中的服务。

为了使该服务在Android应用程序中可用,您必须在 AndroidManifest.xml 文件中对其进行声明。让我们从添加服务部分开始:


扩展 Service 时,只需将service部分声明为正常的Android服务。在 <application> 部分中添加以下内容:

<service android:name=".QtAndroidService">

    <!-后台运行->

    <meta-data android:name="android.app.background_running" android:value="true"/>

    <!-后台运行->

</service>

这样,该服务将在与 QtActivity 相同的过程中启动,该过程使您可以使用Java代码中的本机C ++调用。您可以在一个单独的进程中运行它,但是那样一来您就不能使用任何本地调用进行通信,因为Qt库没有为该进程加载。要在单独的进程上运行,请将其添加到服务标签中:


android:process=":qt_service"

扩展 QtService 时,需要声明其他项来加载Qt所需的所有必需的库,这些项主要与 QtActivity 的 <activity> 部分中的项相同。添加以下内容:

<service android:process=":qt_service" android:name=".QtAndroidService">

    <meta-data android:name="android.app.lib_name" android:value="service"/>

    <meta-data android:name="android.app.background_running" android:value="true"/>

</service>

注意:确保定义以下内容以在后台运行服务:


<meta-data android:name="android.app.background_running" android:value="true"/>

关于如何声明服务,有一些变化。其中一些已经在前面的manifest片段中使用过了。根据你的用例,将服务运行在与QtActivity相同的进程中或单独的进程中。


与QtActivity在同一进程中的服务。

要在与QtActivity相同的进程中运行一个服务,声明服务头如下。


<service android:name=".QtAndroidService">

单独程序送达

要在专用进程中运行服务,请声明服务头如下。


<service android:process=":qt_service" android:name=".QtAndroidService">

Qt加载 android.app.lib_name meta-data 定义的 .so 文件,并使用 android.app.arguments meta-data 设置的所有参数调用 main() 函数。在单独的进程中运行时,可以使用与主要活动相同的lib文件或单独的lib文件启动服务。


使用相同的.so Lib文件

使用与主要活动相同的 .so lib文件意味着服务将使用带有额外参数的相同入口点来将其与主要活动区分开。您可以根据提供的参数在 main() 函数中处理应用程序的执行。将以下参数声明添加到您的服务主体:


<!-应用程序参数->

<meta-data android:name="android.app.arguments" android:value="-service"/>

<!-应用程序参数->

然后确保服务 android.app.lib_name 与主要活动相同,并添加以下内容:


<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>

当使用相同的 .so lib文件时,应用程序的 main() 函数将执行两次,一次是启动主活动,第二次是启动服务。因此,您必须根据提供的参数处理每次执行。实现这一目标的一种方法如下:


if (argc <= 1) {

    //处理主要活动执行的代码

} else if (argc > 1 && strcmp(argv[1], "-service") == 0) {

    qDebug() << "Service starting with from the same .so file";

    QAndroidService app(argc, argv);

    return app.exec();

} else {

    qWarning() << "Unrecognized command line argument";

    return -1;

}

使用单独的.so Lib文件

在这种情况下,您需要一个带有 lib 模板的子项目,该模板为服务提供不同的可执行文件。一个示例项目 .pro 是:


TEMPLATE = lib

TARGET = service

CONFIG += dll

QT += core core-private


SOURCES += \

    service_main.cpp


HEADERS += servicemessenger.h

在 service_main.cpp 中,您可以具有以下内容:


#include <QDebug>

#include <QAndroidService>


int main(int argc, char *argv[])

{

    qWarning() << "Service starting from a separate .so file";

    QAndroidService app(argc, argv);


    return app.exec();

}

在 AndroidManifest.xml 中为服务定义 android.app.lib_name :


<meta-data android:name="android.app.lib_name" android:value="service"/>

与该处的沟通

Qt for Android提供了多种进程间通信(IPC)方法来与Android服务进行通信。根据你的项目结构,你可以使用Java服务的本机C++调用或Android BroadcastReceiver。


来自Java服务的本地C++调用

即使 Service 已扩展,这也可以与以与 QtActivity 相同的进程运行的服务使用。


有关详细信息,请参阅Qt Android 通知程序示例。


使用Android BroadcastReceiver

Android BroadcastReceiver支持在Android系统,应用程序,活动和服务之间交换消息。与其他Android功能类似,Qt可以使用广播接收器在 QtActivity 和您的服务之间交换消息。让我们从发送服务消息的逻辑开始。将以下内容添加到您的服务实现中,该实现调用sendBroadcast():


@Override

public int onStartCommand(Intent intent, int flags, int startId) {

    int ret = super.onStartCommand(intent, flags, startId);


    Intent sendToUiIntent = new Intent();

    sendToUiIntent.setAction(ActivityUtils.BROADCAST_CUSTOM_ACTION);

    sendToUiIntent.putExtra("message", "simple_string");


    Log.i(TAG, "Service sending broadcast");

    sendBroadcast(sendToUiIntent);


    return ret;

}

然后,您需要从Qt的主要活动中创建并注册广播接收器。最简单的方法是使用方法创建自定义类,并在Java中实现所有逻辑。在以下示例中,服务通过调用本机方法 sendToQt() 将消息 "simple_string" 发送到Qt :


public class ServiceBroadcastUtils {


    private static native void sendToQt(String message);


    private static final String TAG = "ActivityUtils";

    public static final String BROADCAST_CUSTOM_ACTION = "org.qtproject.example.qtandroidservice.broadcast.custom";


    public void registerServiceBroadcastReceiver(Context context) {

        IntentFilter intentFilter = new IntentFilter();

        intentFilter.addAction(BROADCAST_CUSTOM_ACTION);

        context.registerReceiver(serviceMessageReceiver, intentFilter);

        Log.i(TAG, "Registered broadcast receiver");

    }


    private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {

        @Override

        public void onReceive(Context context, Intent intent) {

            Log.i(TAG, "In OnReceive()");

            if (BROADCAST_CUSTOM_ACTION.equals(intent.getAction())) {

                String message = intent.getStringExtra("message");

                sendToQt(data);

                Log.i(TAG, "Service sent back message to C++: " + message);

            }

        }

    };

}

要利用所有这些,请按启动服务中所示启动服务,然后通过调用方法 registerServiceBroadcastReceiver() 注册广播接收器:


QJniEnvironment env;

jclass javaClass = env.findClass("org/qtproject/example/qtandroidservice/ActivityUtils");

QJniObject classObject(javaClass);


classObject.callMethod<void>("registerServiceBroadcastReceiver",

                             "(Landroid/content/Context;)V",

                             QtAndroid::androidContext().object());

使用Qt远程对象

Qt远程对象提供了一种在Qt进程之间共享API的简便方法。主要概念是服务过程中的服务器,并在Qt应用程序中具有副本,然后这两个部分便能够使用信号和插槽在彼此之间交换数据。


准备好复制品

让我们考虑一个带有单独的 .so lib文件的服务示例。定义一个 .rep 文件,该文件定义我们的通信类:


class ServiceMessenger {

    SLOT(void ping(const QString &message));

    SIGNAL(pong(const QString &message));

}

在服务子项目中将类定义为 servicemessenger.h :


#include "rep_servicemessenger_source.h"


class ServiceMessenger : public ServiceMessengerSource {

public slots:

    void ping(const QString &name) override {

        emit pong("Hello " + name);

    }

};

然后,在主应用程序中将 .rep 文件添加到主应用程序和service .pro 文件中:


QT += remoteobjects

REPC_REPLICA += servicemessenger.rep

而在服务子项目中。


QT += remoteobjects

REPC_SOURCE += servicemessenger.rep

连接源和副本

在服务子项目的 main() 函数中定义Qt Remote Objects源节点:


#include "servicemessenger.h"


#include <QDebug>

#include <QAndroidService>


int main(int argc, char *argv[])

{

    qWarning() << "QtAndroidService starting from separate .so";

    QAndroidService app(argc, argv);


    QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));

    ServiceMessenger serviceMessenger;

    srcNode.enableRemoting(&serviceMessenger);


    return app.exec();

}

然后,在应用程序的 main() 函数中,连接到源节点:


QRemoteObjectNode repNode;

repNode.connectToNode(QUrl(QStringLiteral("local:replica")));

QSharedPointer<ServiceMessengerReplica> rep(repNode.acquire<ServiceMessengerReplica>());

bool res = rep->waitForSource();

Q_ASSERT(res);


QObject::connect(rep.data(), &ServiceMessengerReplica::pong, [](const QString &message){

    qDebug() << "Service sent: " << message;

});

rep->ping("Qt and Android are friends!");

这个例子从主应用程序的进程向服务发送了一条消息。服务用同样的消息进行回复,并打印在debug logcat上。


注意:当使用相同的 .so lib文件时,可以使用相同的方法。有关更多信息,请参见使用相同的.so Lib文件。


Using QAndroidBinder

QAndroidBinder是一个便利类,它通过实现Android中最重要的方法 Binder 来实现进程间通信。它允许在进程之间发送QByteArray或QVariant对象。



QML ajax请求

function request() {

        let xhr = new XMLHttpRequest();

        xhr.onreadystatechange = function() {

            //XMLHttpRequest.DONE为状态枚举值4

            if(xhr.readyState === 4) {

                print('request DONE',xhr.status);

                if(xhr.status === 200){

                    txt.text = xhr.responseText.toString();

                }

            }

        }

        //【get】

        //xhr.open("GET", "http://127.0.0.1:54321/text?arg=Haha");

        //xhr.send();

        //【post】

        xhr.open("POST", "http://127.0.0.1:54321/text");

        //如果要使用post提交数据,添加此行

        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

        xhr.send("arg=Xixi");

    }

 //请求网络数据

    function request() {

        let xhr = new XMLHttpRequest();

        xhr.onreadystatechange = function() {

            if(xhr.readyState === 4) {

                print('request DONE',xhr.status);

                if(xhr.status === 200){

                    print(xhr.responseText.toString())

                    view.model = JSON.parse(xhr.responseText.toString());

                }

            }

        }

        xhr.open("POST", "http://127.0.0.1:54321/json");

        xhr.setRequestHeader("Content-type", "application/json");

        //xhr.send("['red','green','blue','orange','cyan']");

        xhr.send(JSON.stringify(['red','green','blue','orange','cyan']));

    }

    //访问本地文件

    function requestLocal() {

        let xhr = new XMLHttpRequest();

        xhr.onreadystatechange = function() {

            if(xhr.readyState === 4) {

                print('request DONE',xhr.status);

                if(xhr.status === 200){

                    print(xhr.responseText.toString())

                    view.model = JSON.parse(xhr.responseText.toString());

                }

            }

        }

        //json文件的文本内容,字符串双引号:["red","green","blue"]

        xhr.open("GET", "qrc:/localfile.json");

        xhr.send();

    }



QMediaPlayer视频没有画面显示,或播放卡顿问题解决(DirectShowPlayerService::doRender: Unresolved error code 80040266)

原因

Qt 中的多媒体播放,底层是使用DirectShowPlayerService,所以安装一个DirectShow解码器,例如LAV Filters,或者k-lite解码器,就可以解决运行出错问题

推荐:下载安装K-Lite

安装k-lite解码器:

http://www.codecguide.com/download_k-lite_codec_pack_standard.htm

点击直接下载



Qt学习感悟

当编译发现大量错误的时候,从第一个看起,一个一个的解决,不要急着去看下一个错误,往往后面的错误都是由于前面的错误引起的,第一个解决后很可能都解决了。


定时器是个好东西,学会好使用它,有时候用QTimer::singleShot可以解决意想不到的问题。


打开creator,在构建套件的环境中增加MAKEFLAGS=-j8,可以不用每次设置多线程编译。珍爱时间和生命。新版的QtCreator已经默认就是j8。


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


很多时候找到Qt对应封装的方法后,记得多看看该函数的重载,多个参数的,你会发现不一样的世界,有时候会恍然大悟,原来Qt已经帮我们封装好了。


可以在pro文件中写上标记版本号+ico图标(Qt5才支持)


VERSION  = 2020.10.25

RC_ICONS = main0.ico

管理员运行程序,限定在MSVC编译器。


QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" #以管理员运行

QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" #VS2013 在XP运行

运行文件附带调试输出窗口 CONFIG += console pro


绘制平铺背景QPainter::drawTiledPixmap,绘制圆角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();


移除旧的样式


//移除原有样式

style()->unpolish(ui->btn);

//重新设置新的该控件的样式。

style()->polish(ui->btn);

获取类的属性

const QMetaObject *metaobject = object->metaObject();

int count = metaobject->propertyCount();

for (int i = 0; i < count; ++i) {

    QMetaProperty metaproperty = metaobject->property(i);

    const char *name = metaproperty.name();

    QVariant value = object->property(name);

    qDebug() << name << value;

}

Qt内置图标封装在QStyle中,大概七十多个图标,可以直接拿来用。


SP_TitleBarMenuButton,

SP_TitleBarMinButton,

SP_TitleBarMaxButton,

SP_TitleBarCloseButton,

SP_MessageBoxInformation,

SP_MessageBoxWarning,

SP_MessageBoxCritical,

SP_MessageBoxQuestion,

...

根据操作系统位数判断加载


win32 {

    contains(DEFINES, WIN64) { DESTDIR = $${PWD}/../../bin64

    } else { DESTDIR = $${PWD}/../../bin32 }

}

Qt5增强了很多安全性验证,如果出现setGeometry: Unable to set geometry,请将该控件的可见移到加入布局之后。


可以将控件A添加到布局,然后控件B设置该布局,这种灵活性大大提高了控件的组合度,比如可以在文本框左侧右侧增加一个搜索按钮,按钮设置图标即可。


QPushButton *btn = new QPushButton;

btn->resize(30, ui->lineEdit->height());

QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);

layout->setMargin(0);

layout->addStretch();

layout->addWidget(btn);

对QLCDNumber控件设置样式,需要将QLCDNumber的segmentstyle设置为flat。


巧妙的使用findChildren可以查找该控件下的所有子控件。findChild为查找单个。


//查找指定类名objectName的控件

QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");

//查找所有QPushButton

QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();

//查找一级子控件,不然会一直遍历所有子控件

QList<QPushButton *> childButtons = parentWidget.findChildren<QPushButton *>(QString(), Qt::FindDirectChildrenOnly);

巧妙的使用inherits判断是否属于某种类。


QTimer *timer = new QTimer;         // QTimer inherits QObject

timer->inherits("QTimer");          // returns true

timer->inherits("QObject");         // returns true

timer->inherits("QAbstractButton"); // returns false

使用弱属性机制,可以存储临时的值用于传递判断。可以通过widget->dynamicPropertyNames()列出所有弱属性名称,然后通过widget->property("name")取出对应的弱属性的值。


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


如果出现Z-order assignment: is not a valid widget.错误提示,用记事本打开对应的ui文件,找到为空的地方,删除即可。


善于利用QComboBox的addItem的第二个参数设置用户数据,可以实现很多效果,使用itemData取出来。


如果用了webengine模块,发布程序的时候带上QtWebEngineProcess.exe+translations文件夹+resources文件夹。


默认Qt是一个窗体一个句柄,如果要让每个控件都拥有独立的句柄,设置下 a.setAttribute(Qt::AA_NativeWindows);


Qt+Android防止程序被关闭。


#if defined(Q_OS_ANDROID)

QAndroidService a(argc, argv);

return a.exec()

#else

QApplication a(argc, argv);

return a.exec();

#endif

可以对整体的指示器设置样式,例如 ::down-arrow,::menu-indicator{}::up-arrow:disabled,::up-arrow:off{}。


可以执行位置设置背景图片。


QMainWindow > .QWidget {

    background-color: gainsboro;

    background-image: url(:/images/pagefold.png);

    background-position: top right;

    background-repeat: no-repeat

}

嵌入式linux运行Qt程序 Qt4写法:./HelloQt -qws & Qt5写法:./HelloQt --platform xcb


Qtcreator软件的配置文件存放在:C:\Users\Administrator\AppData\Roaming\QtProject,有时候如果发现出问题了,将这个文件夹删除后打开creator自动重新生成即可。


QMediaPlayer是个壳,依赖本地解码器,视频这块默认基本上就播放个MP4,如果要支持其他格式需要下载k-lite或者LAV Filters安装即可(WIN上,其他系统上自行搜索)。如果需要做功能强劲的播放器,初学者建议用vlc、mpv,终极大法用ffmpeg。


判断编译器类型、编译器版本、操作系统。


//GCC编译器

#ifdef __GNUC__

#if __GNUC__ >= 3 // GCC3.0以上

 

 

//MSVC编译器

#ifdef _MSC_VER

#if _MSC_VER >=1000 // VC++4.0以上

#if _MSC_VER >=1100 // VC++5.0以上

#if _MSC_VER >=1200 // VC++6.0以上

#if _MSC_VER >=1300 // VC2003以上

#if _MSC_VER >=1400 // VC2005以上

#if _MSC_VER >=1500 // VC2008以上

#if _MSC_VER >=1600 // VC2010以上

#if _MSC_VER >=1700 // VC2012以上

#if _MSC_VER >=1800 // VC2013以上

#if _MSC_VER >=1900 // VC2015以上

 

 

//Borland C++

#ifdef __BORLANDC__

 

 

//Cygwin

#ifdef __CYGWIN__

#ifdef __CYGWIN32__

 

 

//mingw

#ifdef __MINGW32__

 

 

//windows

#ifdef _WIN32    //32bit

#ifdef _WIN64    //64bit

#ifdef _WINDOWS     //图形界面程序

#ifdef _CONSOLE     //控制台程序

//Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定义了

#if (WINVER >= 0x030a)     // Windows 3.1以上

#if (WINVER >= 0x0400)     // Windows 95/NT4.0以上

#if (WINVER >= 0x0410)     // Windows 98以上

#if (WINVER >= 0x0500)     // Windows Me/2000以上

#if (WINVER >= 0x0501)     // Windows XP以上

#if (WINVER >= 0x0600)     // Windows Vista以上

//_WIN32_WINNT 内核版本

#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上

#if (_WIN32_WINNT >= 0x0501) // Windows XP以上

#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上

在pro中判断Qt版本及构建套件位数


#打印版本信息

message(qt version: $$QT_VERSION)

#判断当前qt版本号

QT_VERSION = $$[QT_VERSION]

QT_VERSION = $$split(QT_VERSION, ".")

QT_VER_MAJ = $$member(QT_VERSION, 0)

QT_VER_MIN = $$member(QT_VERSION, 1)

#下面是表示 Qt5.5

greaterThan(QT_VER_MAJ, 4) {

greaterThan(QT_VER_MIN, 4) {

#自己根据需要做一些处理

}

}

 

 

#QT_ARCH是Qt5新增的,在Qt4上没效果

#打印当前Qt构建套件的信息

message($$QT_ARCH)

#表示arm平台构建套件

contains(QT_ARCH, arm) {}

#表示32位的构建套件

contains(QT_ARCH, i386) {}

#表示64位的构建套件

contains(QT_ARCH, x86_64) {}

Qt最小化后恢复界面假死冻结,加上代码


void showEvent(QShowEvent *e)

{

    setAttribute(Qt::WA_Mapped);

    QWidget::showEvent(e);

}

获取标题栏高度:style()->pixelMetric(QStyle::PM_TitleBarHeight); PM_TitleBarHeight点进去你会发现新大陆。


设置高分屏属性以便支持2K4K等高分辨率,尤其是手机app。必须写在main函数的QApplication a(argc, argv);的前面。


#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))

    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

#endif

如果运行程序出现 Fault tolerant heap shim applied to current process. This is usually due to previous crashes. 错误。办法:打开注册表,找到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers\,选中Layers键值,从右侧列表中删除自己的那个程序路径即可。


Qt内置了QFormLayout表单布局用于自动生成标签+输入框的组合的表单界面。


qml播放视频在linux需要安装 sudo apt-get install libpulse-dev。


可以直接继承QSqlQueryModel实现自定义的QueryModel,比如某一列字体颜色,占位符,其他样式等,重写QVariant CustomSqlModel::data(const QModelIndex &index, int role) const。


Qt5以后提供了类QScroller直接将控件滚动。


//禁用横向滚动条

ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

//禁用纵向滚动条

ui->listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

//设置横向按照像素值为单位滚动

ui->listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel);

//设置纵向按照像素值为单位滚动

ui->listWidget->setVerticalScrollMode(QListWidget::ScrollPerPixel);

//设置滚动对象以及滚动方式为鼠标左键拉动滚动

QScroller::grabGesture(ui->listWidget, QScroller::LeftMouseButtonGesture);

//还有个QScrollerProperties可以设置滚动的一些参数

如果使用sqlite数据库不想产生数据库文件,可以创建内存数据库。


QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

db.setDatabaseName(":memory:");

清空数据表并重置自增ID,sql = truncate table table_name。


Qtchart模块从Qt5.7开始自带,最低编译要求Qt5.4。在安装的时候记得勾选,默认不勾选。使用该模块需要引入命名空间。


#include <QChartView>

QT_CHARTS_USE_NAMESPACE

class CustomChart : public QChartView

QPushButton左对齐文字,需要设置样式表QPushButton{text-align:left;}


QLabel有三种设置文本的方法,掌握好Qt的属性系统,举一反三,可以做出很多效果。


ui->label->setStyleSheet("qproperty-text:hello;");

ui->label->setProperty("text", "hello");

ui->label->setText("hello");

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


QEventLoop loop;

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

loop.exec();

多种预定义变量 #if (defined webkit) || (defined webengine),去掉生成空的debug和release目录 CONFIG -= debug_and_release。


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


QSqlTableModel的rowCount方法,默认最大返回256,如果超过256,可以将表格拉到底部,会自动加载剩余的,每次最大加载256条数据,如果需要打印或者导出数据,记得最好采用sql语句去查询,而不是使用QSqlTableModel的rowCount方法。不然永远最大只会导出256条数据。如果数据量很小,也可以采用如下方法:


//主动加载所有数据,不然获取到的行数<=256

while(model->canFetchMore()) {

    model->fetchMore();

}

如果需要指定无边框窗体,但是又需要保留操作系统的边框特性,可以自由拉伸边框,可以使用 setWindowFlags(Qt::CustomizeWindowHint);


在某些http post数据的时候,如果采用的是&字符串连接的数据发送,中文解析乱码的话,需要将中文进行URL转码。


QString content = "测试中文";

QString note = content.toUtf8().toPercentEncoding();

Qt默认不支持大资源文件,比如添加了字体文件,需要pro文件开启。CONFIG += resources_big


Qt中继承QWidget之后,样式表不起作用,解决办法有三个。强烈推荐方法一。


方法一:设置属性 this->setAttribute(Qt::WA_StyledBackground, true);


方法二:改成继承QFrame,因为QFrame自带paintEvent函数已做了实现,在使用样式表时会进行解析和绘制。


方法三:重新实现QWidget的paintEvent函数时,使用QStylePainter绘制。


void Widget::paintEvent(QPaintEvent *)

{

    QStyleOption option;

    option.initFrom(this);

    QPainter painter(this);

    style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);

}

有时候在界面上加了弹簧,需要动态改变弹簧对应的拉伸策略,对应方法为changeSize,很多人会选择使用set开头去找,找不到的。


在使用QFile的过程中,不建议频繁的打开文件写入然后再关闭文件,比如间隔5ms输出日志,IO性能瓶颈很大,这种情况建议先打开文件不要关闭,等待合适的时机比如析构函数中或者日期变了需要重新变换日志文件的时候关闭文件。不然短时间内大量的打开关闭文件会很卡,文件越大越卡。


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


int fd = tcpSocket->socketDescriptor();

int keepAlive = 1;      //开启keepalive属性,缺省值:0(关闭)

int keepIdle = 5;       //如果在5秒内没有任何数据交互,则进行探测,缺省值:7200(s)

int keepInterval = 2;   //探测时发探测包的时间间隔为2秒,缺省值:75(s)

int keepCount = 2;      //探测重试的次数,全部超时则认定连接失效,缺省值:9(次)

setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));

setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));

setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

如果程序打包好以后弹出提示 This application failed to start because it could not find or load the Qt platform plugin 一般都是因为platforms插件目录未打包或者打包错了的原因导致的。


非常不建议tr中包含中文,尽管现在的新版Qt支持中文到其他语言的翻译,但是很不规范,也不知道TMD是谁教的,tr的本意是包含英文,然后翻译到其他语言比如中文,现在大量的初学者滥用tr,如果没有翻译的需求,禁用tr,tr需要开销的,Qt默认会认为他需要翻译,会额外进行特殊处理。


很多人Qt和Qt Creator傻傻分不清楚,经常问Qt什么版本结果发一个Qt Creator的版本过来,Qt Creator是使用Qt编写的集成开发环境IDE,和宇宙第一的Visual Studio一样,他可以是msvc编译器的(WIN对应的Qt集成安装环境中自带的Qt Cerator是msvc的),也可以是mingw编译的,还可以是gcc的。如果是自定义控件插件,需要集成到Qt Creator中,必须保证该插件的动态库文件(dll或者so等文件)对应的编译器和Qt版本以及位数和Qt Creator的版本完全一致才行,否则基本不大可能集成进去。特别注意的是Qt集成环境安装包中的Qt版本和Qt Creator版本未必完全一致,必须擦亮眼睛看清楚,有些是完全一致的。


超过两处相同处理的代码,建议单独写成函数。代码尽量规范精简,比如 if(a == 123) 要写成 if (123 == a),值在前面,再比如 if (ok == true) 要写成 if (ok),if (ok == false) 要写成 if (!ok)等。


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


对于大段的注释代码,建议用 #if 0 #endif 将代码块包含起来,而不是将该段代码选中然后全部 // ,下次要打开这段代码的话,又需要重新选中一次取消,如果采用的是 #if 0则只要把0改成1即可,效率大大提升。


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


Qt中的动画,底层用的是QElapsedTimer定时器来完成处理,比如产生一些指定规则算法的数据,然后对属性进行处理。


在绘制无背景颜色只有边框颜色的圆形时候,可以用绘制360度的圆弧替代,效果完全一致。


QRect rect(-radius, -radius, radius * 2, radius * 2);

//以下两种方法二选一,其实绘制360度的圆弧=绘制无背景的圆形

painter->drawArc(rect, 0, 360 * 16);

painter->drawEllipse(rect);

不要把d指针看的很玄乎,其实就是在类的实现文件定义了一个私有类,用来存放局部变量,个人建议在做一些小项目时,没有太大必要引入这种机制,会降低代码可读性,增加复杂性,新手接受项目后会看的很懵逼。


很多人在绘制的时候,设置画笔以为就只可以设置个单调的颜色,其实QPen还可以设置brush,这样灵活性就提高不知道多少倍,比如设置QPen的brush以后,可以使用各种渐变,比如绘制渐变颜色的进度条和文字等,而不再是单调的一种颜色。


很多控件都带有viewport,比如QTextEdit/QTableWidget/QScrollArea,有时候对这些控件直接处理的时候发现不起作用,需要对其viewport()设置才行,比如设置滚动条区域背景透明,需要使用scrollArea->viewport()->setStyleSheet("background-color:transparent;");而不是scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}");


有时候设置了鼠标跟踪setMouseTracking为真,如果该窗体上面还有其他控件,当鼠标移到其他控件上面的时候,父类的鼠标移动事件MouseMove识别不到了,此时需要用到HoverMove事件,需要先设置 setAttribute(Qt::WA_Hover, true);


Qt封装的QDateTime日期时间类非常强大,可以字符串和日期时间相互转换,也可以毫秒数和日期时间相互转换,还可以1970经过的秒数和日期时间相互转换等。


QDateTime dateTime;

QString dateTime_str = dateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss");

//从字符串转换为毫秒(需完整的年月日时分秒)

datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toMSecsSinceEpoch();

//从字符串转换为秒(需完整的年月日时分秒)

datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toTime_t();

//从毫秒转换到年月日时分秒

datetime.fromMSecsSinceEpoch(1315193829218).toString("yyyy-MM-dd hh:mm:ss:zzz");

//从秒转换到年月日时分秒(若有zzz,则为000)

datetime.fromTime_t(1315193829).toString("yyyy-MM-dd hh:mm:ss[:zzz]");

在我们使用QList、QStringList、QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍《C++ GUI Qt 4编程(第二版)》的书中有特别的强调说明,此教材的原作者据说是Qt开发的核心人员编写的,所以还是比较权威,至于使用 at() 与使用 [] 操作符速度效率的比较,网上也有网友做过此类对比。原文在书的212页,这样描述的:Qt对所有的容器和许多其他类都使用隐含共享,隐含共享是Qt对不希望修改的数据决不进行复制的保证,为了使隐含共享的作用发挥得最好,可以采用两个新的编程习惯。第一种习惯是对于一个(非常量的)向量或者列表进行只读存取时,使用 at() 函数而不用 [] 操作符,因为Qt的容器类不能辨别 [] 操作符是否将出现在一个赋值的左边还是右边,他假设最坏的情况出现并且强制执行深层赋值,而 at() 函数则不被允许出现在一个赋值的左边。


如果是dialog窗体,需要在exec以后还能让其他代码继续执行,请在dialog窗体exec前增加一行代码,否则会阻塞窗体消息。


QDialog dialog;

dialog.setWindowModality(Qt::WindowModal);

dialog.exec();

安全的删除Qt的对象类,强烈建议使用deleteLater而不是delete,因为deleteLater会选择在合适的时机进行释放,而delete会立即释放,很可能会出错崩溃。如果要批量删除对象集合,可以用qDeleteAll,比如 qDeleteAll(btns);


在QTableView控件中,如果需要自定义的列按钮、复选框、下拉框等其他模式显示,可以采用自定义委托QItemDelegate来实现,如果需要禁用某列,则在自定义委托的重载createEditor函数返回0即可。自定义委托对应的控件在进入编辑状态的时候出现,如果想一直出现,则需要重载paint函数用drawPrimitive或者drawControl来绘制。


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


心中有坐标,万物皆painter,强烈建议在学习自定义控件绘制的时候,将qpainter.h头文件中的函数全部看一遍、试一遍、理解一遍,这里边包含了所有Qt内置的绘制的接口,对应的参数都试一遍,你会发现很多新大陆,会大大激发你的绘制的兴趣,犹如神笔马良一般,策马崩腾遨游代码绘制的世界。


在使用setItemWidget或者setCellWidget的过程中,有时候会发现设置的控件没有居中显示而是默认的左对齐,而且不会自动拉伸填充,对于追求完美的程序员来说,这个可不大好看,有个终极通用办法就是,将这个控件放到一个widget的布局中,然后将widget添加到item中,这样就完美解决了,而且这样可以组合多个控件产生复杂的控件。


//实例化进度条控件

QProgressBar *progress = new QProgressBar;

//增加widget+布局巧妙实现居中

QWidget *widget = new QWidget;

QHBoxLayout *layout = new QHBoxLayout;

layout->setSpacing(0);

layout->setMargin(0);

layout->addWidget(progress);

widget->setLayout(layout);

ui->tableWidget->setCellWidget(0, 0, widget);

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


//根据背景色自动计算合适的前景色

double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;

QColor textColor = gray > 0.5 ? Qt::black : Qt::white;

对QTableView或者QTableWidget禁用列拖动。


#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))

    ui->tableView->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed);

#else

    ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);

#endif

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


Qt中的QColor对颜色封装的很完美,支持各种转换,比如rgb、hsb、cmy、hsl,对应的是toRgb、toHsv、toCmyk、toHsl,还支持透明度设置,颜色值还能转成16进制格式显示。


QColor color(255, 0, 0, 100);

qDebug() << color.name() << color.name(QColor::HexArgb);

//输出 #ff0000 #64ff0000

QVariant类型异常的强大,可以说是万能的类型,在进行配置文件的存储的时候,经常会用到QVariant的转换,QVariant默认自带了toString、toFloat等各种转换,但是还是不够,比如有时候需要从QVariant转到QColor,而却没有提供toColor的函数,这个时候就要用到万能办法。


if (variant.typeName() == "QColor") {

    QColor color = variant.value<QColor>();

    QFont font = variant.value<QFont>();

    QString nodeValue = color.name(QColor::HexArgb);

}

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


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


QTextEdit右键菜单默认英文的,如果想要中文显示,加载widgets.qm文件即可,一个Qt程序中可以安装多个翻译文件,不冲突。


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


在Qt5.10以后,表格控件QTableWidget或者QTableView的默认最小列宽改成了15,以前的版本是0,所以在新版的qt中,如果设置表格的列宽过小,不会应用,取的是最小的列宽。所以如果要设置更小的列宽需要重新设置ui->tableView->horizontalHeader()->setMinimumSectionSize(0);


Qt源码中内置了一些未公开的不能直接使用的黑科技,都藏在对应模块的private中,比如gui-private widgets-private等,比如zip文件解压类QZipReader、压缩类QZipWriter就在gui-private模块中,需要在pro中引入QT += gui-private才能使用。


#include "QtGui/private/qzipreader_p.h"

#include "QtGui/private/qzipwriter_p.h"

 

 

QZipReader reader(dirPath);

QString path("");

//解压文件夹到当前目录

reader.extractAll(path);

//文件夹名称

QZipReader::FileInfo fileInfo = reader.entryInfoAt(0);

//解压文件

QFile file(filePath);

file.open(QIODevice::WriteOnly);

file.write(reader.fileData(QString::fromLocal8Bit("%1").arg(filePath)));

file.close();

reader.close();

 

 

QZipWriter *writer = new QZipWriter(dirPath);

//添加文件夹

writer->addDirectory(unCompress);

//添加文件

QFile file(filePath);

file.open(QIODevice::ReadOnly);

writer->addFile(data, file.readAll());

file.close();

writer->close();

理论上串口和网络收发数据都是默认异步的,操作系统自动调度,完全不会卡住界面,网上那些说收发数据卡住界面主线程的都是扯几把蛋,真正的耗时是在运算以及运算后的处理,而不是收发数据,在一些小数据量运算处理的项目中,一般不建议动用线程去处理,线程需要调度开销的,不要什么东西都往线程里边扔,线程不是万能的。只有当真正需要将一些很耗时的操作比如编码解码等,才需要移到线程处理。


在构造函数中获取控件的宽高很可能是不正确的,需要在控件首次显示以后再获取才是正确的,控件是在首次显示以后才会设置好正确的宽高值,记住是在首次显示以后,而不是构造函数或者程序启动好以后,如果程序启动好以后有些容器控件比如QTabWidget中的没有显示的页面的控件,你去获取宽高很可能也是不正确的,万无一失的办法就是首次显示以后去获取。


数据库处理一般建议在主线程,如果非要在其他线程,务必记得打开数据库也要在那个线程,即在那个线程使用数据库就在那个线程打开,不能打开数据库在主线程,执行sql在子线程,很可能出问题。


新版的QTcpServer类在64位版本的Qt下很可能不会进入incomingConnection函数,那是因为Qt5对应的incomingConnection函数参数变了,由之前的int改成了qintptr,改成qintptr有个好处,在32位上自动是quint32而在64位上自动是quint64,如果在Qt5中继续写的参数是int则在32位上没有问题在64位上才有问题,所以为了兼容Qt4和Qt5,必须按照不一样的参数写。


#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))

    void incomingConnection(qintptr handle);

#else

    void incomingConnection(int handle);

#endif

Qt支持所有的界面控件比如QPushButton、QLineEdit自动关联 on_控件名_信号(参数) 信号槽,比如按钮的单击信号 on_pushButton_clicked(),然后直接实现槽函数即可。


QWebEngineView控件由于使用了opengl,在某些电脑上可能由于opengl的驱动过低会导致花屏或者各种奇奇怪怪的问题,比如showfullscreen的情况下鼠标右键失效,需要在main函数启用软件opengl渲染。


#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))

    //下面两种方法都可以,Qt默认采用的是AA_UseDesktopOpenGL

    QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);

    //QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);

#endif

    QApplication a(argc, argv);

另外一个方法解决 全屏+QWebEngineView控件一起会产生右键菜单无法弹出的bug,需要上移一个像素


QRect rect = qApp->desktop()->geometry();

rect.setY(-1);

rect.setHeight(rect.height());

this->setGeometry(rect);

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


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

用QFile读写文件的时候,推荐用QTextStream文件流的方式来读写文件,速度快很多,基本上会有30%的提升,文件越大性能区别越大。


//从文件加载英文属性与中文属性对照表

QFile file(":/propertyname.txt");

if (file.open(QFile::ReadOnly)) {

    //QTextStream方法读取速度至少快30%

#if 0

    while(!file.atEnd()) {

        QString line = file.readLine();

        appendName(line);

    }

#else

    QTextStream in(&file);

    while (!in.atEnd()) {

        QString line = in.readLine();

        appendName(line);

    }

#endif

    file.close();

}

用QFile.readAll()读取QSS文件默认是ANSI格式,不支持UTF8,如果在QtCreator中打开qss文件来编辑保存,这样很可能导致qss加载以后没有效果。


void frmMain::initStyle()

{

    //加载样式表

    QString qss;

    //QFile file(":/qss/psblack.css");

    //QFile file(":/qss/flatwhite.css");

    QFile file(":/qss/lightblue.css");

    if (file.open(QFile::ReadOnly)) {

#if 1

        //用QTextStream读取样式文件不用区分文件编码 带bom也行

        QStringList list;

        QTextStream in(&file);

        //in.setCodec("utf-8");

        while (!in.atEnd()) {

            QString line;

            in >> line;

            list << line;

        }

 

 

        qss = list.join("\n");

#else

        //用readAll读取默认支持的是ANSI格式,如果不小心用creator打开编辑过了很可能打不开

        qss = QLatin1String(file.readAll());

#endif

        QString paletteColor = qss.mid(20, 7);

        qApp->setPalette(QPalette(QColor(paletteColor)));

        qApp->setStyleSheet(qss);

        file.close();

    }

}

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


QString s1, s2;

s1 = "666.5567124";

s2.setNum(888.5632123, 'f', 7);

qDebug() << qSetRealNumberPrecision(10) << s1.toDouble() << s2.toDouble();

用QScriptValueIterator解析数据的时候,会发现总是会多一个节点内容,并且内容为空,如果需要跳过则增加一行代码。


while (it.hasNext()) {

    it.next();    

    if (it.flags() & QScriptValue::SkipInEnumeration)      

       continue;     

    qDebug() << it.name();

}

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


如果需要在尺寸改变的时候不重绘窗体,则设置属性即可 this->setAttribute(Qt::WA_StaticContents, true); 这样可以避免可以避免对已经显示区域的重新绘制。


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


Qt表格控件一些常用的设置封装,QTableWidget继承自QTableView,所以下面这个函数支持传入QTableWidget。


void QUIHelper::initTableView(QTableView *tableView, int rowHeight, bool headVisible, bool edit)

{

    //奇数偶数行颜色交替

    tableView->setAlternatingRowColors(false);

    //垂直表头是否可见

    tableView->verticalHeader()->setVisible(headVisible);

    //选中一行表头是否加粗

    tableView->horizontalHeader()->setHighlightSections(false);

    //最后一行拉伸填充

    tableView->horizontalHeader()->setStretchLastSection(true);

    //行标题最小宽度尺寸

    tableView->horizontalHeader()->setMinimumSectionSize(0);

    //行标题最大高度

    tableView->horizontalHeader()->setMaximumHeight(rowHeight);

    //默认行高

    tableView->verticalHeader()->setDefaultSectionSize(rowHeight);

    //选中时一行整体选中

    tableView->setSelectionBehavior(QAbstractItemView::SelectRows);

    //只允许选择单个

    tableView->setSelectionMode(QAbstractItemView::SingleSelection);

 

 

    //表头不可单击

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))

    tableView->horizontalHeader()->setSectionsClickable(false);

#else

    tableView->horizontalHeader()->setClickable(false);

#endif

 

 

    //鼠标按下即进入编辑模式

    if (edit) {

        tableView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked);

    } else {

        tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);

    }

}

在一些大的项目中,可能嵌套了很多子项目,有时候会遇到子项目依赖其他子项目的时候,比如一部分子项目用来生成动态库,一部分子项目依赖这个动态库进行编译,此时就需要子项目按照顺序编译。


TEMPLATE = subdirs

#设置ordered参数以后会依次编译 demo designer examples

CONFIG  += ordered

SUBDIRS += demo

SUBDIRS += designer

SUBDIRS += examples

MSVC编译器的选择说明


如果是32位的Qt则编译器选择x86开头的


如果是64位的Qt则编译器选择amd64开头的


具体是看安装的Qt构建套件版本以及目标运行平台的系统位数和架构


一般现在的电脑默认以64位的居多,选择amd64即可


如果用户需要兼容32位的系统则建议选择32位的Qt,这样即可在32位也可以在64位系统运行


诸葛大佬补充:x86/x64都是编译环境和运行环境相同,没有或。带下划线的就是交叉编译,前面是编译环境,后面是运行环境。


名称


说明




x86


32/64位系统上编译在32/64位系统上运行


x86_amd64


32/64位系统上编译在64位系统上运行


x86_arm


32/64位系统上编译在arm系统上运行


amd64


64位系统上编译在64位系统上运行


amd64_x86


64位系统上编译在32/64位系统上运行


amd64_arm


64位系统上编译在arm系统上运行


很多时候用QDialog的时候会发现阻塞了消息,而有的时候我们希望是后台的一些消息继续运行不要终止,此时需要做个设置。


QDialog dialog;

dialog.setWindowModality(Qt::WindowModal);

很多初学者甚至几年工作经验的人,对多线程有很深的误解和滥用,尤其是在串口和网络通信这块,什么都往多线程里面丢,一旦遇到界面卡,就把数据收发啥的都搞到多线程里面去,殊不知绝大部分时候那根本没啥用,因为没找到出问题的根源。


如果你没有使用wait***函数的话,大部分的界面卡都出在数据处理和展示中,比如传过来的是一张图片的数据,你需要将这些数据转成图片,这个肯定是耗时的;


还有就是就收到的数据曲线绘制出来,如果过于频繁或者间隔过短,肯定会给UI造成很大的压力的,最好的办法是解决如何不要频繁绘制UI比如合并数据一起绘制等;


如果是因为绘制UI造成的卡,那多线程也是没啥用的,因为UI只能在主线程;


串口和网络的数据收发默认都是异步的,由操作系统调度的,如果数据处理复杂而且数据量大,你要做的是将数据处理放到多线程中;


如果没有严格的数据同步需求,根本不需要调用wait***之类的函数来立即发送和接收数据,实际需求中大部分的应用场景其实异步收发数据就足够了;


有严格数据同步需求的场景还是放到多线程会好一些,不然你wait***就卡在那边了;


多线程是需要占用系统资源的,理论上来说,如果线程数量超过了CPU的核心数量,其实多线程调度可能花费的时间更多,各位在使用过程中要权衡利弊;


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


//这种方式设置的无边框窗体在嵌入式设备上无法产生焦点

setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);

 

 

//需要在show以后主动激活窗体

w->show();

w->activateWindow();

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


QGraphicsEffect类的相关效果很炫,可以实现很多效果比如透明、渐变、阴影等,但是该类很耗CPU,如果不是特别需要一般不建议用,就算用也是要用在该部件后期不会发生频繁绘制的场景,不然会让你哭晕在厕所。


在不同的平台上文件路径的斜杠也是不一样的,比如linux系统一般都是 / 斜杠,而在windows上都是 \ 两个反斜杠,Qt本身程序内部无论在win还是linux都支持 / 斜杠的路径,但是一些第三方库的话可能需要转换成对应系统的路径,这就需要用到斜杠转换,Qt当然内置类方法。


QString path = "C:/temp/test.txt";

path = QDir::toNativeSeparators(path);

//输出 C:\\temp\\test.txt

 

 

QString path = "C:\\temp\\test.txt";

path = QDir::toNativeSeparators(path);

//输出 C:/temp/test.txt

巧用QMetaObject::invokeMethod方法可以实现很多效果,包括同步和异步执行,比如有个应用场景是在回调中,需要异步调用一个public函数,如果直接调用的话会发现不成功,此时需要使用 QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection); 这种方式来就可以。invokeMethod函数有很多重载参数,可以传入返回值和执行方法的参数等。


Qt5中的信号是public的,可以在需要的地方直接emit即可,而在Qt4中信号是protected的,不能直接使用,需要定义一个public函数来emit。


Qt5.15版本开始官方不再提供安装包,只提供源码,可以自行编译或者在线安装,估计每次编译各种版本太麻烦,更多的是为了统计收集用户使用信息比如通过在线安装,后期可能会逐步加大商业化力度。


有时候我们需要判断当前Qt版本有没有某个模块可以使用qtHaveModule(Qt5新引入的判断)来判断,如果要判断自己的项目中有没有 QT += 的方式添加的模块,可以用 contains来判断。


qtHaveModule(webenginewidgets) {

message("当前Qt库有找到 webenginewidgets 模块")

}

 

 

!qtHaveModule(webkit) {

message("当前Qt库没有找到 webkit 模块")

}

 

 

contains(QT, network) {

message("当前项目已经引入 network 模块")

}

 

 

!contains(QT, widgets) {

message("当前项目没有引入 widgets 模块")

}c++11新引入了原始字符串格式,用户避免在字符串中加入转义字符\,可以用于表示json字符串等场景。QString s1 = R"(test\001.jpg)";

s1.replace("\\", "#");

qDebug()<< s1;

//结果 test#001.jpg

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


Qt的默认定时器精度不够高(比如应用场景是1分钟保存一条记录或者文件,当你用默认的定时器的时候你会发现有些时候是60秒而有些是59秒随机的,如果客户有要求这就需要设置精度了。当然我们所做的绝大部分项目也不需要精度非常高的定时器,毕竟精度越高,占用的系统资源可能越大),如果需要设置更高的精度可以设置 setTimerType(Qt::PreciseTimer)。Qt有两种定时器处理,一种是QTimer类,还有一种是QObject类就内置的timeevent事件,如果是QObject类的定时器要设置的话调用 startTimer(interval, Qt::PreciseTimer);


Qt::PreciseTimer 精确的定时器,尽量保持毫秒精度。


Qt::CoarseTimer 粗略的定时器,尽量保持精度在所需的时间间隔5%范围内。


Qt::VeryCoarseTimer 很粗略的定时器,只保留完整的第二精度。


精度再高,也依赖对应的操作系统中断,假设中断需要 5ms,则定时器精度不可能高于5毫秒。


QGraphicsEffect相关类很耗CPU,甚至在绘制的时候和某些地方有冲突干扰,基本上不建议使用,情非得已只建议少量使用和非频繁触发绘制的地方使用。


用QSettings设置注册表,如果不是管理员身份运行会打印 QSettings: failed to set subkey "xxx" (拒绝访问。),你需要手动鼠标右键管理员身份运行就可以。


QLineEdit除了单纯的文本框以外,还可以做很多特殊的处理用途。


限制输入只能输入IP地址。


限制输入范围,强烈推荐使用 QRegExpValidator 正则表达式来处理。


//正在表达式限制输入

QString str = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b";

ui->lineEdit->setValidator(new QRegExpValidator(QRegExp(str)));

//用于占位

ui->lineEdit->setInputMask("000.000.000.000");

 

 

#if 0

//下面代码设置浮点数范围限制失败

ui->lineEdit->setValidator(new QDoubleValidator(20, 50, 1));

#else

//下面代码设置浮点数范围限制成功

QDoubleValidator *validator = new QDoubleValidator(20, 50, 1);

validator->setNotation(QDoubleValidator::StandardNotation);

ui->lineEdit->setValidator(validator);

#endif

//下面代码设置整数范围限制成功

ui->lineEdit->setValidator(new QIntValidator(10, 120));

 

 

//其实上面的代码缺陷很多,只能限制只输入小数,无法设定数值范围,很操蛋

//需要来个万能的牛逼的 QRegExpValidator

 

 

//限制浮点数输入范围为[-180,180]

QRegExp regexp("^-?(180|1?[0-7]?\\d(\\.\\d+)?)$");

//限制浮点数输入范围为[-90,90]并限定为小数位后4位

QRegExp regexp("^-?(90|[1-8]?\\d(\\.\\d{1,4})?)$");

QRegExpValidator *validator = new QRegExpValidator(regexp, this);

ui->lineEdit->setValidator(validator);

在继承自QAbstractItemView的控件中,比如QTableView、QTableWidget,如果文本超过队列item的宽度,则会自动省略号显示,想要快速显示完整的文本,可以在该列和下一列分割线中间双击即可,会自动自适应显示最大宽度,如果是Qt5.14或者更高版本,你会发现显示省略号的计算规则变了,如果是rtsp、http之类的开头的英文字符串,同样的列宽下,会提前就显示省略号,比如字符串 rtmp://58.200.131.2:1935/livetv/cctv1,会显示成 rtmp://... ,而在旧版本的Qt中会显示成 rtmp://58.200.131... ,很多时候我们并不想看到烦人的省略号,可以设置取消。


//取消自动换行tableView->setWordWrap(false);//超出文本不显示省略号tableView->setTextElideMode(Qt::ElideNone);

四、其他经验

Qt界的中文乱码问题,版本众多导致的如何选择安装包问题,如何打包发布程序的问题,堪称Qt界的三座大山!


在Qt的学习过程中,学会查看对应类的头文件是一个好习惯,如果在该类的头文件没有找到对应的函数,可以去他的父类中找找,实在不行还有爷爷类,肯定能找到的。通过头文件你会发现很多函数接口其实Qt已经帮我们封装好了,有空还可以阅读下他的实现代码。


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


Qt在开发阶段不支持中文目录,切记,这是无数人可能犯的错误,在安装Qt集成开发环境以及编译器的时候,务必记得目录必须英文,否则很可能不正常,建议尽量用默认的安装位置。


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


Qt一共有几百个版本,关于如何选择Qt版本的问题,我一般保留四个版本,为了兼容Qt4用4.8.7,最后的支持XP的版本5.7.0,最新的长期支持版本比如5.12,最高的新版本比如5.14.2。强烈不建议使用4.7以前和5.0到5.3之间的版本,太多bug和坑,稳定性和兼容性相比于之后的版本相当差,能换就换,不能换睡服领导也要换。


Qt和msvc编译器常见搭配是Qt5.7+VS2013、Qt5.9+VS2015、Qt5.12+VS2017,按照这些搭配来,基本上常用的模块都会有,比如webengine模块,如果选用的Qt5.12+msvc2015,则很可能官方没有编译这个模块,只是编译了Qt5.12+msvc2017的。


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


新版本Qt安装包安装的时候需要填写注册信息,如果不想填写,先禁用网卡,在运行安装包,可以直接跳过这一步进行安装。


最后一条:珍爱生命,远离编程。祝大家头发浓密,睡眠良好,情绪稳定,财富自由!


五、推荐的Qt论坛+个人博客+网站+群

名称


网址


QtWidget开源demo集合


https://gitee.com/feiyangqingyun/QWidgetDemo


QtQuick/Qml开源demo集合


https://gitee.com/jaredtao/TaoQuick


qtcn


http://www.qtcn.org


豆子的空间


https://www.devbean.net


yafeilinux


http://www.qter.org


一去二三里


http://blog.csdn.net/liang19890820


乌托邦2号


http://blog.csdn.net/taiyang1987912


foruok


http://blog.csdn.net/foruok


jason


http://blog.csdn.net/wsj18808050


朝十晚八


http://www.cnblogs.com/swarmbees


BIG_C_GOD


http://blog.csdn.net/big_c_god


公孙二狗


https://qtdebug.com/qtbook


雨田哥


https://blog.csdn.net/ly305750665


郑天佐


https://blog.csdn.net/zhengtianzuo06


寒山-居士


https://blog.csdn.net/esonpo


feiyangqingyun


https://blog.csdn.net/feiyangqingyun


前行中小猪


http://blog.csdn.net/goforwardtostep


涛哥的知乎专栏


https://zhuanlan.zhihu.com/TaoQt


Qt君


https://blog.csdn.net/nicai_xiaoqinxi


Qt老外视频教程


http://space.bilibili.com/2592237/#!/index


Qt维基补充文档


https://wiki.qt.io/Main


Qt源码查看网站


https://code.woboq.org/qt5


Qt官方下载地址


https://download.qt.io


Qt官方下载新地址


https://download.qt.io/new_archive/qt/


Qt国内镜像下载地址


https://mirrors.cloud.tencent.com/qt


Qt安装包下载地址


http://qthub.com/download/ (超过1000多个,由Qt君整理)


精美图表控件QWT


http://qwt.sourceforge.net/


精美图表控件QCustomPlot


https://www.qcustomplot.com/


免费图标下载


http://www.easyicon.net/


图形字体下载


https://www.iconfont.cn/


漂亮界面网站


https://www.ui.cn/


六、其他

C++入门书籍推荐《C++ primer plus》,进阶书籍推荐《C++ primer》。


Qt入门书籍推荐霍亚飞的《Qt Creator快速入门》,Qt进阶书籍推荐官方的《C++ GUI Qt4编程》,qml书籍推荐《Qt5编程入门》。


强烈推荐程序员自我修养和规划系列书《大话程序员》《程序员的成长课》《解忧程序员》,受益匪浅,受益终生!



QT读写NFC

QT       +=nfc core gui


MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QMainWindow>

#include <QNearFieldManager>

#include <QtNfc/qnearfieldtarget.h>

QT_FORWARD_DECLARE_CLASS(QNearFieldManager)

namespace Ui {

class MainWindow;

}


class MainWindow : public QMainWindow

{

    Q_OBJECT


public:

    explicit MainWindow(QWidget *parent = nullptr);

    ~MainWindow();

    QByteArray HexStringToByteArray(QString HexString);

    QString ByteArrayToHexString(QByteArray data);


private slots:

    void targetDetected(QNearFieldTarget *target);

    void targetLost(QNearFieldTarget *target);

private:

    QNearFieldManager *m_manager;

    Ui::MainWindow *ui;

};


#endif // MAINWINDOW_H



MainWindow.cpp


MainWindow::MainWindow(QWidget *parent) :

    QMainWindow(parent),

    ui(new Ui::MainWindow)

{

    ui->setupUi(this);

    m_manager = new QNearFieldManager(this);

    if (!m_manager->isAvailable()) {

              QMessageBox::information(0, "title", "NFC not available", QMessageBox::Yes, QMessageBox::Yes);

               qDebug() << "NFC not available";

              return;

          }

    m_manager->startTargetDetection();

          connect(m_manager, &QNearFieldManager::targetDetected,

                  this, &MainWindow::targetDetected);

          connect(m_manager, &QNearFieldManager::targetLost,

                  this, &MainWindow::targetLost);

}


MainWindow::~MainWindow()

{

    delete ui;

}

void MainWindow::targetDetected(QNearFieldTarget *target)

  {

    QByteArray m_IDArray=target->uid();

    QString sID=ByteArrayToHexString(m_IDArray);

    QMessageBox::information(0, "读到卡", sID, QMessageBox::Yes, QMessageBox::Yes);


 }



  void MainWindow::targetLost(QNearFieldTarget *target)

  {

      target->deleteLater();

  }


  QByteArray MainWindow::HexStringToByteArray(QString HexString)

  {

      bool ok;

      QByteArray ret;

      HexString = HexString.trimmed();

      HexString = HexString.simplified();

      QStringList sl = HexString.split(" ");


      foreach (QString s, sl) {

          if(!s.isEmpty())

          {

              char c = s.toInt(&ok,16)&0xFF;

              if(ok){

                  ret.append(c);

              }else{

                  //qDebug()<<"非法的16进制字符:"<<s;

                  QMessageBox::warning(0,tr("错误:"),QString("非法的16进制字符: \"%1\"").arg(s));

              }

          }

      }

      //qDebug()<<ret;

      return ret;

  }


  QString MainWindow::ByteArrayToHexString(QByteArray data){

      QString ret(data.toHex().toUpper());

      int len = ret.length()/2;

      //qDebug()<<len;

      for(int i=1;i<len;i++)

      {

          //qDebug()<<i;

          ret.insert(2*i+i-1," ");

      }


      return ret;

  }



Qt中低功耗蓝牙模块使用

QT += bluetooth


bluedevice.h

#ifndef BLUEDEVICE_H

#define BLUEDEVICE_H

#include <QObject>

#include<QBluetoothDeviceDiscoveryAgent>

#include<QBluetoothDeviceInfo>

#include<QBluetoothUuid>

#include<QBluetoothServiceInfo>

#include<QLowEnergyController>

#include<QLowEnergyService>

#include<QLowEnergyDescriptor>

class BlueDevice: public QObject{

    Q_OBJECT

public:

    BlueDevice();

    QString getAddress(QBluetoothDeviceInfo device) const;

private:

        QList<QBluetoothDeviceInfo> device_list;  //存放搜索到到蓝牙设备列表

        QBluetoothDeviceDiscoveryAgent *m_deviceDiscoveryAgent;  //设备搜索对象

        QLowEnergyController *m_controler;   //单个蓝牙设备控制器

        QLowEnergyService *m_service; //服务对象实例

};

#endif // BLUEDEVICE_H


bluedevice.cpp


#include "bluedevice.h"

#include <QDebug>

#include<QTimer>

BlueDevice::BlueDevice() {

    qDebug() << "enter bludedevice constructor....";

    m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);

    m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(5000);

    //每次发现新设备时触发

    connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this ,[this]() {

        qDebug() << "find a new bluebooth device";

    });

    

    //蓝牙设备搜索完成后,筛选出目标设备进行连接,并进行相关信号与槽函数的绑定

    connect(m_deviceDiscoveryAgent,&QBluetoothDeviceDiscoveryAgent::finished, this, [this]() {

        device_list = this->m_deviceDiscoveryAgent->discoveredDevices();

        //遍历显示设备详情

        QList<QBluetoothDeviceInfo>::iterator it;

        for(it=device_list.begin(); it != device_list.end(); it++) {

            

            // 外围蓝牙设备对象

            QBluetoothDeviceInfo tmp_device = *it;  

            QString device_name = tmp_device.name();

            //qDebug() <<"device name:::" << device_name;

            //qDebug() << "device address:::" << this->getAddress(tmp_device);

            

           //打印搜索出来的全部低功耗蓝牙

           if (tmp_device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {

                qDebug() << " low Energy device ....";

                qDebug() <<"22222device name:::" << device_name;

              }

            

            //正则匹配目标设备,

            QString pattern_str = "^Eric.*";  //qt中正则匹配任意个字符,需要使用.*而不是*

            QRegExp rx(pattern_str);

            if(!rx.exactMatch(device_name)) {

                continue;

            }

            qDebug() <<"device name:::" << device_name;

            qDebug() << "device address:::" << this->getAddress(tmp_device);

            // 创建蓝牙设备控制器对象 

            m_controler = new QLowEnergyController(tmp_device, this);

            

            // 监听目标设备连接成功消息,连接成功后,搜索目标设备等服务列表

            connect(m_controler, &QLowEnergyController::connected, this, [this](){

                qDebug() << "m_controler connected ......";

                //必须要在连接建立后 执行开始寻找service的函数 

                //之前调试,就是因为没有在设备连接后主动请求获取服务列表信息,后续监听便没有触发

                m_controler->discoverServices();

            });

            

            // 监听发现服务消息,如果服务的uuid 为约定好的要使用服务类型,则进行后续处理

            connect(m_controler,&QLowEnergyController::serviceDiscovered, this, [this](QBluetoothUuid serviceUuid) {

                if(serviceUuid == QBluetoothUuid( quint16(0xffd0))) {  //我们用的服务类型是0xffd0对应的uuid

                    

                    //发现匹配的服务后,使用控制器对象创建服务对象

                    m_service = m_controler->createServiceObject(serviceUuid,this);

                    if(m_service) {

                        // 服务对象创建成功后,坚挺服务状态变化,如果状态变成已发现,则进行后续服务下特征对象获取

                        connect(m_service,&QLowEnergyService::stateChanged, this, [this]() {

                            qDebug() << "service state change" << m_service->state() << ",||||||";

                            //发现服务, 建立characteristic对象实例

                            if(m_service->state() == QLowEnergyService::ServiceDiscovered) {

                                QLowEnergyCharacteristic hrChar = m_service->characteristic(QBluetoothUuid(quint16(0xfff6)));

                                if(!hrChar.isValid()) {

                                    qDebug() << "characteristic fff6 error:::";

                                }

                                // 设置特征对象可用

                                //enable the chracteristic notification by write 0x01 to client characteristic configuration

                                QLowEnergyDescriptor m_notificationDesc = hrChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);

                                if (m_notificationDesc.isValid()) {

                                    if(hrChar.properties() & QLowEnergyCharacteristic::Notify) {

                                        qDebug() << "";

                                    }

                                    m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));

                                }

                            }

                        });

                        // 通过监听特征对象的变化,不断获得鞋垫压力数据。 

                        connect(m_service,&QLowEnergyService::characteristicChanged, this,

                                [this](QLowEnergyCharacteristic c,QByteArray value) {

                                    qDebug() << "characteristicChanged state change::" <<c.uuid()<< ",||||||";

                                    qDebug() << "value length::" << value.length();

                                    qDebug() << "value length::" << value;

                                    QByteArray sub_1 = value.mid(0,2);

                                    QByteArray sub_2 = value.mid(2,2);

                                    QByteArray sub_3 = value.mid(4,2);

                                    bool ok;

                                    // num 1,2,3  分别对应三个压力感应点的压力值

                                    int num_1 =  QString(sub_1.toHex()).toInt(&ok,16);

                                    qDebug() << "num_1:::" << num_1;

                                    int num_2 =  QString(sub_2.toHex()).toInt(&ok,16);

                                    qDebug() << "num_1:::" << num_2;

                                    int num_3 =  QString(sub_3.toHex()).toInt(&ok,16);

                                    qDebug() << "num_1:::" << num_3;

                                }

                        );

                        

                        // 触发服务详情发现函数 ,不要忘记调用

                        m_service->discoverDetails();

                    }

                }

            });

            connect(m_controler,&QLowEnergyController::discoveryFinished, this, [this]() {

                qDebug() << "finish service discovery";

            });

            //连接外围设备

            m_controler->connectToDevice();

            //QTimer *timer = new QTimer(this);

            //connect(timer, &QTimer::timeout, this, [this](){ qDebug() <<"state:::" <<  this->m_controler->state();});

            //timer->start(1000);

        }

    });

   

    // 开始外围设备搜索

    m_deviceDiscoveryAgent->start();

}

// mac和其他系统上address获取有少许差异,参见官方文档

QString BlueDevice::getAddress(QBluetoothDeviceInfo device) const

{

#ifdef Q_OS_MAC

    // On OS X and iOS we do not have addresses,

    // only unique UUIDs generated by Core Bluetooth.

    return device.deviceUuid().toString();

#else

    return device.address().toString();

#endif

}

//void BlueDevice::getNewService(QBluetoothServiceInfo info) {

//    qDebug() << "ppppp uuuuuuuuuu";

//}


Qt 串口通信(QSerialPort)

QT += serialport


widget.h

#ifndef WIDGET_H

#define WIDGET_H


#include <QWidget>

#include <QSerialPort>

#include <QSerialPortInfo>

#include <QComboBox>

#include <QPushButton>

namespace Ui {

class Widget;

}


class Widget : public QWidget

{

    Q_OBJECT


public:

    explicit Widget(QWidget *parent = 0);

    ~Widget();


    void initUI();


    QStringList getPortNameList();//获取所有可用的串口列表


    void openPort();//打开串口

public slots:

    void receiveInfo();

private:

    Ui::Widget *ui;

    QSerialPort* m_serialPort; //串口类

    QStringList m_portNameList;


    QComboBox* m_PortNameComboBox;

    QPushButton* m_OpenPortButton;

};


#endif // WIDGET_H


widget.cpp

#include "widget.h"

#include "ui_widget.h"

#include <QLayout>


#include <QDebug>


Widget::Widget(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::Widget)

{

    ui->setupUi(this);

    m_serialPort = new QSerialPort();


    initUI();


    m_portNameList = getPortNameList();


    m_PortNameComboBox->addItems(m_portNameList);


    connect(m_OpenPortButton,&QPushButton::clicked,this,&Widget::openPort);


}


Widget::~Widget()

{

    if (m_serialPort->isOpen())

    {

        m_serialPort->close();

    }

    delete m_serialPort;


    delete ui;

}


void Widget::initUI()

{

    this->setWindowTitle("码农小明 test QSerialPort");



    m_OpenPortButton = new QPushButton();

    m_OpenPortButton->setText("打开串口");


    m_PortNameComboBox  = new QComboBox();


    QHBoxLayout *m_layout = new QHBoxLayout();


    m_layout->addWidget(m_PortNameComboBox);

    m_layout->addWidget(m_OpenPortButton);


    this->setLayout(m_layout);

}


QStringList Widget::getPortNameList()

{

    QStringList m_serialPortName;

    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())

    {

        m_serialPortName << info.portName();

        qDebug()<<"serialPortName:"<<info.portName();

    }

    return m_serialPortName;

}


void Widget::openPort()

{

    if(m_serialPort->isOpen())//如果串口已经打开了 先给他关闭了

    {

        m_serialPort->clear();

        m_serialPort->close();

    }



    m_serialPort->setPortName(m_PortNameComboBox->currentText());//当前选择的串口名字


    if(!m_serialPort->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口

    {

        qDebug()<<"打开失败!";

        return;

    }

    qDebug()<<"串口打开成功!";


    m_serialPort->setBaudRate(QSerialPort::Baud115200,QSerialPort::AllDirections);//设置波特率和读写方向

    m_serialPort->setDataBits(QSerialPort::Data8);      //数据位为8位

    m_serialPort->setFlowControl(QSerialPort::NoFlowControl);//无流控制

    m_serialPort->setParity(QSerialPort::NoParity); //无校验位

    m_serialPort->setStopBits(QSerialPort::OneStop); //一位停止位


    connect(m_serialPort,SIGNAL(readyRead()),this,SLOT(receiveInfo()));

}


//接收到单片机发送的数据进行解析

void Widget::receiveInfo()

{

    QByteArray info = m_serialPort->readAll();


    qDebug()<<"receive info:"<<info;


}


m_serialPort->write("ccc");

m_serialPort->flush();





Qt编译一般的EXE

1、将项目改为构建Release。项目文件中会自动生成Release项目文件夹。

2、打开Release项目文件夹中含有.exe可执行程序,单独复制到一个新建文件夹中。

3、打开QT自带的命令行工具,然后cd到新建文件夹,使用命令(C:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin):windeployqt xxx.exe 进行打包,会将所需动态库拷贝到本目录中,打包成功便可直接运行了。


Qt编译qml应用程序

1.png

windeployqt.exe 为打包工具

–qmldir 是 指定所有.qml文件所在的文件夹指令

该工程qml文件所在的目录

qml.exe就是我们打包的目标文件


例子:

windeployqt  --qmldir  "C:\Qt\Qt5.14.2\Examples\Qt-5.14.2\quickcontrols2\gallery\pages"  "gallery.exe"

windeployqt  --qmldir  "C:\Qt\Qt5.14.2\5.14.2\mingw73_64\qml"  "gallery.exe"

发现有问题时候采用编译器下的qml目录



Qt程序打包发布方法(使用官方提供的windeployqt工具)

Qt程序打包发布方法(使用官方提供的windeployqt工具)


Qt 官方开发环境使用的动态链接库方式,在发布生成的exe程序时,需要复制一大堆 dll,如果自己去复制dll,很可能丢三落四,导致exe在别的电脑里无法正常运行。因此 Qt 官方开发环境里自带了一个工具:windeployqt.exe。

以官方 Qt 5.4.0+MinGW 开发环境为例,windeployqt工具在 %QTDIR%\Qt5.4.0\5.4\mingw491_32\bin 目录下,其中 QTDIR 是 Qt 的安装目录,是环境变量。


在集成开发环境 QtCreator 中可选择 “Qt Widgets Application” 或 “Qt Quick Application” 两种方式生成图形界面应用程序。


下面分别介绍这两种方式创建应用的发布方式。


1. Qt Widgets Application

首先用 QtCreator 新建一个 Qt Widgets Application 项目,直接用默认的 QMainWindow 程序就可以了,项目名字假定是 hellomw。

然后以 Release 方式编译生成 exe 程序:

1.png


生成的程序运行正常之后,找到项目的生成目录,比如 项目源码路径:C:\QtPros\hellomw\ 。

它的项目生成目录是 C:\QtPros\build-hellomw-Desktop_Qt_5_4_0_MinGW_32bit-Release\ 。

进入这个文件夹,在进入它的子文件夹 release 里面,找到 hellomw.exe,将这个exe 复制到一个新的单独的文件夹里用于发布,比如存到 D:\hellomw\ 文件夹里面。


然后从开始菜单打开 Qt 命令行,输入命令:cd /d D:\hellomw

然后使用 Qt安装目录(如D:\Qt\5.15.0\msvc2019\bin)的windeployqt 工具命令:D:\Qt\5.15.0\msvc2019\bin\windeployqt hellomw.exe

1.png



然后可以在 D:\hellomw 文件夹里看到 windeployqt 工具自动复制的插件文件夹和 dll文件、qm文件。这时候得到的就完整的 exe 程序发布集合,依赖关系都解决好了。

把 D:\hellomw 文件夹 打包就可以发布了,不用自己一个个找 dll 文件了。D:\hellomw 文件夹里的qm文件是多国语言翻译文件,不需要可以删了,其他的都保留。


Qt Quick Application

首先用 QtCreator 新建一个 Qt Quick Application 项目,直接用默认的项目模版,点击下一步生成项目,项目名字假定是 helloqml。

然后以 Release 方式编译生成 exe 程序:

1.png



然后找到项目的构建目录,比如项目源码目录 C:\QtPros\helloqml 。

它的构建目录是:C:\QtPros\build-helloqml-Desktop_Qt_5_4_0_MinGW_32bit-Release\ 。

进入这个目录,再进入 release 子文件夹,找到 helloqml.exe ,复制到一个新的单独的文件夹里面,比如 D:\helloqml\ 文件夹里面。


然后从开始菜单打开 Qt 命令行,进入D:\helloqml\文件夹:cd /d D:\helloqml

然后使用 windeployqt 工具命令:windeployqt helloqml.exe --qmldir C:\Qt\Qt5.4.0\5.4\mingw491_32\qml

1.png

注意不要跟完全一样照抄上条命令!–qmldir 是指出 Qt 库里面的 qml 文件夹位置,上面命令里 C:\Qt\Qt5.4.0 是 Qt 官方开发环境安装的文件夹,C:\Qt\Qt5.4.0\5.4\mingw491_32 是Qt类库的目录(QTDIR),因此使用的 --qmldir 后面写的是 C:\Qt\Qt5.4.0\5.4\mingw491_32\qml ,读者Qt环境安装路径不一样,要根据实际情况修改!


然后可以看到 D:\helloqml 文件夹里有一大堆文件,就是 QtQuick程序需要的依赖文件。将整个 D:\helloqml 文件夹 打包就可以发布出去,在别的电脑上使用。

这个 D:\helloqml 文件夹里的东西很多,看不懂就不要删,老老实实打包发布就行了。


上面是最简单的程序发布,实际复杂程序可能还带一些图片文件、数据库文件、配置文件之类的,可以按自己需要添加这些文件到发布文件夹里面。



全网最靠谱的QML程序发布

为了发布QML程序,不得不研究了我一下午,网上全是一些牛魔蛇神,不知道的都在那里乱说,还是得找官方的英文资料才行,国内的很多博客真的是误人子弟啊。


照着他们的方法不是程序报错,就是程序双击后没有一点反应,真的是让我蛋疼,很多次的尝试真的是让人绝望,废话不多说,赶紧分享最成功的方法。


第一步,找到自己生成的exe文件目录,尽量是release版本的,说的是尽量,不是也没关系,毕竟是发布版本,调试版本还是算了吧

1.png



将exe单独拷贝出来,存放在一个空目录中


2.png


第二步,在开始菜单中找到Qt自带的命令符,有的同学可能安装了很多版本,这里要留意一下,我选择的是Qt5.15.2(MSVC 2019 64-bit)

3.png

 


 第三步,进入刚才新建的文件夹目录中,下面的自己看仔细。


 




第四步,最最重要的就是下图中的1位置啊,兄弟们,一定要看清楚了,这是你项目代码的位置,不是网上传说的qml的安装位置,真的是大坑,害人不少。 2就是你要打包的exe文件名字,按tab键就能自动将2补齐,因为目录中此时就只有这一个文件。

1.png



 注意上图命令行1中的位置是你代码的位置,如下图所示,我贴出来了。

2.png



最后,执行完上面的第4步后,在你拷贝出来的exe目录中就会多出了很多文件,都是exe需要的文件,在运行时需要的依赖文件。

3.png



 最后一击,直接运行,应用发布成功。

4.png

 NND,真心不容易,花了我好长好长时间,所以我决定英语继续学习下去,学无止境。



Qt6构建于打包发布

打包发布

release 单文件打包

参考文献:点击查看


首先准备我们欲打包发布的项目


默认情况下运行时发布的是 debug 类型的(包含冗余调试信息,文件大),我们需要切换到 release 类型!!!


切换完毕后点击绿色运行按钮,此时即编译完成 release 文件


按照下图步骤 1,切换至 release

构建文件生成位置根据下图 234 步即可查看


1.png


打开构建完毕的文件夹,找到构建完毕的 exe 文件

之后任意新建一个 model 文件夹,把该 exe 文件拷贝进去



之后在开始菜单里面搜索,找到 Qt 命令行,注意我这里使用的编译器是 MinGW,如果你用的是 MSVC 就要切换到对应的命令行!


1.png


使用 cd 指令,进入到 model 文件夹下,使用下方指令对其进行打包


windeployqt xxx.exe

打包完毕后双击对应的 exe 文件,发现可以正常运行,那么我们就进入下一步



这里我们需要使用到一个打包软件,叫做Enigma Virtual Box,它是免费的

点击这里前往官网下载:https://enigmaprotector.com/en/downloads.html


打开 Enigma Virtual Box

待封包的主程序,选择 model 文件夹下的 exe 文件

封包程序另存为,自己找一个顺眼的文件夹保存打包好的单文件


1.png


之后点击“文件选项”,勾选“压缩文件”,然后点击确定


1.png


点击“添加”,务必选择“递归添加文件夹”,弹出窗口选择我们的 model 文件夹即可


1.png


最后点击右下角的 执行封包 稍稍等待一分钟,就可以生成我们打包好的单文件应用了


该应用封装了所有 dll,移植到任何一台 windows 电脑都可以正常使用!



anaconda 冲突错误解决

有些情况下,我们使用 windeployqt 打包时,会出现unable find xxx


这是由于我们之前安装了 anaconda 环境,并配置了环境变量,命令行错误的寻址到了 anaconda 下并查找模块 windeployqt,自然是找不到的,所以必定报错


目前没有很好的解决办法,只能修改环境变量


打开环境变量,打开 path,找到我们设置的 anaconda 变量的位置,在该变量之前加上一个 0(目的就是为了使该变量失效,从而使命令行不要寻址到此位置!)

然后连点三个确定,才可正式应用变动


1.png


重新打开 qt 命令行,cd 到 model 文件夹,此时再执行 windeployqt 就不会有问题了!



C++中的关键字explicit

关键字 explicit 可以禁止“单参数构造函数”被用于自动类型转换。光看这一句似乎不太容易明白,下面,举个简单地例子。


          //main.cpp

            #include <iostream>


            using namespace std;


            class Test

            {

            public:

                 Test(int a)

                 {

                      m_data = a;

                 }


                 void show()

                 {

                      cout << "m_data = " << m_data << endl;

                 }


            private:

                 int m_data;

            };


            void main(void)

            {

                 Test t = 2;   // 将一个常量赋给了一个对象

                  t.show();

            }


      编译能够通过,执行结果:m_data = 2。


      为什么会这样呢?原来C++通过隐式转换,构造了一个临时对象Test(2),将它赋给了t(这里调用了默认的构造函数,而不是重载的“=”,因为这是在对象创建的时候)。那么,如果给构造函数加上关键字 explicit ,构造函数变成了 explicit Test(int a),再次编译,编译器就会报错。这时,就只能显式地使用构造函数了Test t = Test(2) 。


QVariant使用

1.构造函数

QVariant(bool b);

QVariant(double d);

QVariant(const QString &string);

QVariant(const QList<QVariant> &list);

QVariant(const QMap<QString,QVariant> &map);

QVariant(const QHash<QString,QVariant> &hash);

QVariant(const QVariant &other);

2.通过setValue()方法设置数据

QVariant v;

v.setValue(5);

3.静态方法设置数据-fromValue

QVariant v = QVariant::fromValue(xxx);

4.取值

qreal toReal(bool *ok = Q_NULLPTR) const;

QByteArray toByteArray() const;

5.使用基本类型数据

//保存数据

QVariant var=12;

//获取数据

int data=var.toInt();

6.使用自定义类型数据

MyClass myClass;

//保存数据

QVariant data=QVariant::fromValue(myClass);

//获取数据

MyClass myClass=data.value<MyClass>();

int id=myClass.id;

QString name=myClass.name;

7.使用指针

//保存数据

QVariant var=QVariant::fromValue((void*)event); 

//获取数据

QPaintEvent* e=(QPaintEvent*)var.value<void*>();



QString的数据转换

QString转为其他类型

toInt toLong toShort toUInt toULong toDouble  toFloat 

数字转串

str=QString::asprintf("%.2f",total);

str=str.sprintf(".2f",total);

格式换串

QString test="womeng";

QString v;

v=QString("%1").arg(test);



QT MDI

创建一个MDI窗口如

class QFormDoc:public QWidget

{

   Q_OBJECT

   private :

    QString mCurrFile;

    bool mFileOpened=false;

   public:

   explicit QFormDoc(QWidget *parent=0);

   ~QFormDoc();

  ....

  private :

Ui::QFormDoc *ui;

}


QFormDoc::QFormDoc(QWidget *parent):QWidget(parent),ui(new Ui::QFormDoc)

{

 ui->setupUi(this);

this->setWindowTitle("....");

this->setAttribute(Qt::WA_DeleteOnClose);//关闭自动删除


}


QFormDoc::~QFormDoc()

{

delete ui;

}


主窗口里加入


ui->setupUi(this);

this->setCentralWidget(ui->mdiArea);

this->setwindowState(Qt::WindowMaximized);





QFormDoc *formDoc=new QFormDoc(this);

ui->mdiArea->addSubWindow(formDoc);

formDoc->show();



获得所有打开的

ui->mdiArea->subWindowList().Count();

获得活动的

formDoc=(QFormDoc *)ui->mdiArea->activeSubWindow()->widget();


QMdiArea常营功能函数

ui->mdiArea->cascadSubWindows();// 窗口级联展开

ui->mdiArea->titleSubWindows();// 平铺展开

ui->mdiArea->closeAllSubWindows();// 关闭全部子窗口

Tab多页显示

ui->mdiArea->setViewMode(QMdiArea::TabbedView);//tab多页显示模式

ui->mdiArea->setTabsCloseable(true);//页面可关闭

ui->actCasecade->setEnabled(false);//菜单状态设置

ui->actTitle->setEnabled(false);//菜单状态设置

 子窗口模式

ui->mdiArea->setViewMode(QMdiArea::SubWindowView);//子窗口显示模式

ui->actCasecade->setEnabled(true);//菜单状态设置

ui->actTitle->setEnabled(true);//菜单状态设置


MDIArea信号

subWindowActivated(QMdiSubWindow *arg1)



Qt 中文显示

1. 设置QObject的成员函数tr()的编码。

具体的转换代码看下面:

#include <QApplication>

#include <QTextCodec>

#include <QLabel>



int main(int argc,char *argv[])

{undefined

QApplication app(argc,argv);

QTextCodec::setCodecForTr(QTextCodec::codecForName("GBK"));

QLabel hello(QObject::tr("你好世界"));

hello.setWindowTitle(QObject::tr("Qt中文显示"));

hello.show();

return app.exec();

}


注意:

setCodecForTr一定要在QApplication后面。不然没有效果。而且这种方法只会转换经过tr函数的字符串,并不转换不经过tr函数的字符串。


技巧:

可以用codecForLocale函数来返回现在系统的默认编码,这样更容易做多编码的程序而不用自己手动来更改具体的编码。



2. 使用QString的fromLocal8Bit()函数


这个方法是最快的,系统直接自动将char *的参数转换成为系统默认的编码,然后返回一个QString。


#include <QApplication>

#include <QTextCodec>

#include <QLabel>



int main(int argc,char *argv[])

{undefined


   QApplication app(argc,argv);


   QString str;

str = str.fromLocal8Bit("Qt中文显示");

hello.setWindowTitle(str);

hello.show();

return app.exec();

}


3. 用QTextCodec的toUnicode方法来显示中文


#include <QApplication>

#include <QTextCodec>

#include <QLabel>



int main(int argc,char *argv[])

{undefined


   QApplication app(argc,argv);

QLabel hello(QObject::tr("你好世界").toLocal8Bit());

QTextCodec *codec = QTextCodec::codecForLocale();

QString a = codec->toUnicode("Qt中文显示");

hello.setWindowTitle(a);

hello.show();

return app.exec();

}


 


PS:关于中文显示乱码的问题我纠结了好久,在网上查的一些方法似乎都不是太管用,所用我自己又实验了很多次,终于解决了这个问题。我其他两种方法我没有试过,我只说第一种方法:


    刚开始的时候我设置QTextCodec::setCodecForTr(QTextCodec::codecForName("GBK"));或者将"GBK"换成"GB2312","GB18030"都没有成功,依然是乱码。不过也并不是一定不行,后来发现有些时候这样设置也是可以的,我认为可能与源代码的编码方式有关。我后来又找到了一种解决办法就是设置成QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));或者设置成QTextCodec::setCodecForTr(QTextCodec::codecForLocale());我在Ubuntu下,这两种设置都可行;在Windows下,QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));和QTextCodec::setCodecForTr(QTextCodec::codecForLocale());中应该有一种可以,希望我的这些研究能够帮到你。


QT定位组件

QLabel *label=new Qlabel(this);

labe->setGeometry(100,200,300,300); //设置位置宽高



Qt 动态加载动态库

#include <stdio.h>

#include <QLibrary>

int main(int argc, char *argv[])

{

    QLibrary *hello_lib = NULL;

    //写清楚库的路径,如果放在当前工程的目录下,路径为./libhello.so

    hello_lib = new QLibrary("/home/libhello.so");

    

    //加载动态库

    hello_lib->load();

    if (!hello_lib->isLoaded()) 

    {

        printf("load libhello.so failed!\n");

        return 0; 

    }

    

    //定义函数指针

    typedef void (*Fun)();

    

    //resolve得到库中函数地址

    Fun hello = (Fun)hello_lib->resolve("hello");

    if (hello)

    {

        hello();

    }

    

    //卸载库

    hello_lib->unload();

    return 0;

}


Qt 之 pro 配置详解

简述
使用Qt的时候,我们经常会对pro进行一系列繁琐的配置,为方便大家理解、查找,现将常用的配置进行整理。
版权声明:一去、二三里,未经博主允许不得转载。
配置
注释
以“#”开始,到这一行结束。
快捷键:Ctrl + /
CONFIG
指定编译器选项和项目配置,值由qmake内部识别并具有特殊意义。
以下配置值控制编译标志:
选项
说明
release
项目以release模式构建。如果也指定了debug,那么最后一个生效。
debug
项目以debug模式构建。
debug_and_release
项目准备以debug和release两种模式构建。
debug_and_release_target
此选项默认设置。如果也指定了debug_and_release,最终的debug和release构建在不同的目录。
build_all
如果指定了debug_and_release,默认情况下,该项目会构建为debug和release模式。
autogen_precompile_source
自动生成一个.cpp文件,包含在.pro中指定的预编译头文件。
ordered
使用subdirs模板时,此选项指定应该按照目录列表的顺序处理它们。
precompile_header
可以在项目中使用预编译头文件的支持。
warn_on
编译器应该输出尽可能多的警告。如果也指定了warn_off,最后一个生效。
warn_off
编译器应该输出尽可能少的警告。
exceptions
启用异常支持。默认设置。
exceptions_off
禁用异常支持。
rtti
启用RTTI支持。默认情况下,使用编译器默认。
rtti_off
禁用RTTI支持。默认情况下,使用编译器默认。
stl
启用STL支持。默认情况下,使用编译器默认。
stl_off
禁用STL支持。默认情况下,使用编译器默认。
thread
启用线程支持。当CONFIG包括qt时启用,这是缺省设置。
c++11
启用c++11支持。如果编译器不支持c++11这个选项,没有影响。默认情况下,支持是禁用的。
c++14
启用c++14支持。如果编译器不支持c++14这个选项,没有影响。默认情况下,支持是禁用的。
当使用debug和release选项时(Windows下默认的),该项目将被处理三次:一次生成一个”meta”Makefile,另外两次生成Makefile.Debug和Makefile.Release。
在后面的次数,build_pass和相应的debug或release添加到CONFIG选项。这使得它可以执行构建特定任务。
例如:
build_pass:CONFIG(debug, debug|release) {    unix: TARGET = $$join(TARGET,,,_debug)    else: TARGET = $$join(TARGET,,,d) }

DEFINES
qmake添加这个变量的值作为编译器C预处理器宏(-D选项)。
例如:
DEFINES += USE_MY_STUFF

然后就可以在代码中使用:
#ifdef USE_MY_STUFF    // TODO#else    // TODO#endif

往往可以指定项目的特殊版本(比如:正式版、试用版)、对一些特殊功能模块(比如:加密狗)进行限制等。
DEPENDPATH
指定查看解决依赖关系的目录列表,当包含文件时使用。
例如:
DEPENDPATH += . forms include qrc sources

DESTDIR
指定在何处放置目标文件。
例如:
DESTDIR = ../../lib

FORMS
指定UI文件(参考: Qt Designer Manual)在编译前被uic处理。所有的构建这些UI文件所需的依赖、头文件和源文件都会自动被添加到项目中。
例如:
FORMS = mydialog.ui \        mywidget.ui \        myconfig.ui

HEADERS
指定项目中所有的头文件。
qmake会自动检测是头文件的类中是否需要moc,并增加适当的依赖关系和文件到项目中,来生成和链接moc文件。
例如:
HEADERS = myclass.h \          login.h \          mainwindow.h

INCLUDEPATH
指定编译项目时应该被搜索的#include目录。
例如:
INCLUDEPATH = c:/msdev/include d:/stl/include

如果路径包含空格,需要使用引号包含。
win32:INCLUDEPATH += "C:/mylibs/extra headers"unix:INCLUDEPATH += "/home/user/extra headers"

LIBS
指定链接到项目中的库列表。如果使用Unix -l (library) 和 -L (library path) 标志,在Windows上qmake正确处理库(也就是说,将库的完整路径传递给链接器),库必须存在,qmake会寻找-l指定的库所在的目录。
例如:
win32:LIBS += c:/mylibs/math.libunix:LIBS += -L/usr/local/lib -lmath

如果路径包含空格,需要使用引号包含路径。
win32:LIBS += "C:/mylibs/extra libs/extra.lib"unix:LIBS += "-L/home/user/extra libs" -lextra

MOC_DIR
指定来自moc的所有中间文件放置的目录(含Q_OBJECT宏的头文件转换成标准.h文件的存放目录)。
例如:
unix:MOC_DIR = ../myproject/tmp win32:MOC_DIR = c:/myproject/tmp

OBJECTS_DIR
指定所有中间文件.o(.obj)放置的目录。
例如:
unix:OBJECTS_DIR = ../myproject/tmp win32:OBJECTS_DIR = c:/myproject/tmp

QT
指定项目中使用Qt的模块。默认情况下,QT包含core和gui,以确保标准的GUI应用程序无需进一步的配置就可以构建。
如果想建立一个不包含Qt GUI模块的项目,可以使用“ -=”操作符。
下面一行将构建一个很小的Qt项目:
QT -= gui # 仅仅使用core模块

如果要创建一个界面,里面用到XML及网络相关的类,那么需要包含如下模块:
QT += core gui widgets xml network

如果你的项目是一个Qt Designer插件,使用值uiplugin指定项目构建成库,但特定的Qt Designer插件支持,请参考:Building and Installing the Plugin。
RCC_DIR
指定Qt资源编译器输出文件的目录(.qrc文件转换成qrc_*.h文件的存放目录)。
例如:
unix:RCC_DIR = ../myproject/resources win32:RCC_DIR = c:/myproject/resources

RESOURCES
指定资源文件 (qrc) 的名称,参考:Qt之资源系统
例如:
RESOURCES += Resource/resource.qrc

RC_FILE
指定应用程序资源文件的名称。这个变量的值通常是由qmake或qmake.conf处理,很少需要进行修改。
例如:
RC_FILE += myapp.rc
RC_ICONS
仅适用于Windows,指定的图标应该包含在一个生成的.rc文件里。如果RC_FILE 和RES_FILE变量都没有设置这才可利用。
例如:
RC_ICONS = myapp.ico  

SOURCES
指定项目中所有源文件。
例如:
SOURCES = myclass.cpp \          login.cpp \          mainwindow.cpp

TARGET
指定目标文件的名称。默认情况下包含的项目文件的基本名称。
例如:
TEMPLATE = app TARGET = myapp SOURCES = main.cpp

上面项目会生成一个可执行文件,Windows下为myapp.exe,Unix下为myapp。
TEMPLATE
模板变量告诉qmake为这个应用程序生成哪种makefile。
可供使用的选项:
选项
说明
app
创建一个用于构建应用程序的Makefile(默认)。
lib
创建一个用于构建库的Makefile。
subdirs
创建一个用于构建目标子目录的Makefile,子目录使用SUBDIRS变量指定。
aux
创建一个不建任何东西的Makefile。如果没有编译器需要被调用来创建目标,比如你的项目使用解释型语言写的,使用此功能。注:此模板类型只能用于Makefile-based生成器。特别是,它不会工作在vcxproj和Xcode生成器。
vcapp
仅适用于Windows。创建一个Visual Studio应用程序项目。
vclib
仅适用于Windows。创建一个Visual Studio库项目。
例如:
TEMPLATE = lib SOURCES = main.cpp TARGET = mylib

TRANSLATIONS
指定包含用户界面翻译文本的翻译(.ts)文件列表。
例如:
TRANSLATIONS += Resource/myapp_zh.ts \                Resource/myapp_en.ts

UI_DIR
指定来自uic的所有中间文件放置的目录(.ui文件转化成ui_*.h文件的存放目录)。
例如:
unix:UI_DIR = ../myproject/ui win32:UI_DIR = c:/myproject/ui

更多参考





Qt自学经验

widget查找指定控件

parentWidget->findChild<QListWidget *>();

parentWidget->findChild<QPushbutton *>(“button1”);

智能指针

QPointer<QLabel>

QSharedPointer<QLabel>

QWeakPointer<QLabel>

QScopedPointer<QLabel>


QT中文乱码问题处理

QMessageBox::information(NULL, QString::fromLocal8Bit("中文"), QString::fromLocal8Bit("中华人民共和国"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

ui.webEngineView->page()->runJavaScript(QString::fromLocal8Bit("alert('中文');"));



QT配置加载头文件及库

描述

还是由于前一阵的项目,关于QT的一个小代码我仍然需要长期维护。因此在这里记录一下,如何配置一个新的外部库


包括:


如何添加头文件

如何链接静态库

我的当前电脑是Mac,但在Ubuntu系统下代码是通用的,只需要替换相应路径即可


代码

QT的写法还是有自己特点的,切勿自己去随意更改


QT配置

我的项目是这么写的,这一节需要结合自己情况


QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = CalibrationLidarofFar

TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

TEMPLATE = app

CONFIG -= app_bundle


加载系统头文件

没什么说的,路径填在这里就行


INCLUDEPATH += /usr/local/Cellar/opencv/4.1.2/include/opencv4/opencv2/

INCLUDEPATH += /usr/local/Cellar/opencv/4.1.2/include/

INCLUDEPATH += /usr/local/Cellar/opencv/4.1.2/include/opencv4/

INCLUDEPATH += /usr/local/include/


加载系统头文件

LIBS += -L<需要的lib路径> -l<在前面那个路径下你需要的.lib的文件名>


主要写法如上,具体写法如下


LIBS += -L/usr/local/Cellar/opencv/4.1.2/lib/ -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs

LIBS += -L/usr/local/lib/ -lfastrtps -lfastcdr


注意: -L紧跟路径,-l后紧跟该路径下的文件名(不加.lib的文件名)


源代码

使用 \ 来隔开


SOURCES += \

        main.cpp \

        mainwindow.cpp\

        imageprocess.cpp \

    operation.cpp \

    common.cpp \

    include/Time.cxx


源头文件

HEADERS += \

        mainwindow.h \

        imageprocess.h \

    operation.h \

    common.h \

    include/ \

    include/rapidjson


最后是QT的UI

FORMS += \

        mainwindow.ui




VS开发QT

在QT Design里连接事件,双击UI文件打开设计

1.png

手动先添加slots

class VSQT : public QMainWindow

{

    Q_OBJECT


public:

    VSQT(QWidget *parent = Q_NULLPTR);


private:

    Ui::VSQTClass ui;

private slots:

void OpenWeb();


};

实现文件:

VSQT::VSQT(QWidget *parent)

: QMainWindow(parent)

{

ui.setupUi(this);

ui.webEngineView->load(QUrl("http://html5test.com"));

}

void VSQT::OpenWeb()

{

ui.webEngineView->load(QUrl("http://www.1xn1.com"));

}

可以手动加事件连接函数

QObject::connect(pushButton, SIGNAL(pressed()), VSQTClass, SLOT(OpenWeb()));

QObject::connect(pushButton_2, SIGNAL(pressed()), VSQTClass, SLOT(OpenWeb2()));

pushButton,pushButton_2 发送信号的按钮对象

SIGNAL(pressed())  信号名

VSQTClass  接收信号的槽函数对象

SLOT(OpenWeb()) 连接槽函数


典型例子

QLabel *label = new QLabel;

QScrollBar *scrollBar = new QScrollBar;

QObject::connect(scrollBar, SIGNAL(valueChanged(int)),label,  SLOT(setNum(int)));

在设计界面连接函数

1.png

连接函数

然后左击控件打开页面,往下拖一段距离,再松开手,就会弹出控件关联槽函数的对话框。

1.png

1.png

添加OpenWeb

也可以直接添加

1.png



QT根据类名动态创建类

QT根据类名动态创建类,可能是由于内容开始是网络地址(http://...)完整的英文词截取后不能换行造成 为避免代码造成手机端排版的混乱,可适当增加文字描述,将代码往后推移

基类

class Parser {
public:
 virtual void parse() = 0;
 virtual ~Parser() {};
};


两个子类

class Parser1 : public Parser {
public:
    Parser1() {
        qDebug() <<"Parser1::Parser1()";
    }    
    void parse() {
        qDebug() << "Parser1::parse()";
    }
    ~Parser1() {
       qDebug() <<"Parser1::~Parser1()";
    }
};
Q_DECLARE_METATYPE(Parser1)

   

class Parser2 : public Parser {
public:
    Parser2() {
        qDebug() <<"Parser2::Parser2()";
    }
    void parse() {
        qDebug() << "Parser2::parse()";
    }
    ~Parser2() {
       qDebug() <<"Parser2::~Parser2()";
    }
};
Q_DECLARE_METATYPE(Parser2)

类工厂

class Parser1 : public Parser {
public:
    Parser1() {
        qDebug() <<"Parser1::Parser1()";
    }    
    void parse() {
        qDebug() << "Parser1::parse()";
    }
    ~Parser1() {
       qDebug() <<"Parser1::~Parser1()";
    }
};
Q_DECLARE_METATYPE(Parser1)

   

class Parser2 : public Parser {
public:
    Parser2() {
        qDebug() <<"Parser2::Parser2()";
    }
    void parse() {
        qDebug() << "Parser2::parse()";
    }
    ~Parser2() {
       qDebug() <<"Parser2::~Parser2()";
    }
};
Q_DECLARE_METATYPE(Parser2)

使用

int main ( int argc, char* argv[] )
{
    qRegisterMetaType<Parser1>("Parser1");
    qRegisterMetaType<Parser2>("Parser2");

     

    qDebug() << "###### Trying create Parser1";
    factory("Parser1");

     

    qDebug() << "###### Trying create Parser2";
    factory("Parser2");
}


qt代码中判断windows/linux/arm等系

qt代码中判断windows/linux/arm等系统

可使用宏判断,例如:


#include <QtGlobal>

...

#ifdef Q_OS_MAC

// mac

#endif

 

#ifdef Q_OS_LINUX

// linux

#endif

 

#ifdef Q_OS_WIN32

// win

#endif

 

#ifdef __arm__

// arm

#endif

pro文件中用于平台区分的写法

unix {

    TARGET = appname

}

macx {

    TARGET = appname2

}

win32 {

    TARGET = appname3

}


QT类属性介绍

class Device: public QObject

{

    Q_OBJECT

    Q_PROPERTY(QString update READ getUpdate WRITE setUpdate NOTIFY updateChanged)

public:

    Device();

    ~Device();   

    QString getUpdate();


Q_SIGNALS:

   

    void updateChanged();

private:

    void setUpdate(const QString &message);

     QString m_message;



实现部分:


QString Device::getUpdate()

{

    return m_message;

}

void Device::setUpdate(const QString &message)

{

    m_message = message;

    emit updateChanged();

}


Qt线程与进程


Qt-QProcess-启动子进程-控制台进程隐藏-获取子进程标准输出和返回码

网上已经有很多教程了,这里再说一下:

//注意,mProcess为共享指针,要特别注意,信号和槽的连接要使用原始指针(也就是mProcess.get()函数)

std::shared_ptr<QProcess> mProcess;    

mProcess = std::make_shared<QProcess>();

 

mProcess->setProgram(cmd);

mProcess->setArguments(splitProcessCommand(arguments));

mProcess->setProcessChannelMode(QProcess::MergedChannels);   //设置读取标准输出模式 

mProcess->setProcessEnvironment(env);                        //设置环境变量

mProcess->setWorkingDirectory(workingDir);                   //这种工作目录

 

 

ok = connect(mProcess.get(), &QProcess::readyReadStandardError,[this](){ 

        this->log(QString::fromLocal8Bit( mProcess->readAllStandardError()));

    });

    

ok = connect(mProcess.get(), &QProcess::readyReadStandardOutput,[this](){

        //这样就实现把结果exe的信息给显示在控制台了

        this->log(QString::fromLocal8Bit( mProcess->readAllStandardOutput()));    

    });

 

mProcess->start();

mProcess->waitForStarted(5000);

 

//这里可以给它传入一些数据

mProcess->write(readFileToByteArray(mInputFile));

mProcess->closeWriteChannel();

 

 

//如果啥时候想终止这个进程,还可以用下面的代码

mProcess->closeReadChannel(QProcess::StandardOutput);

mProcess->closeReadChannel(QProcess::StandardError);

mProcess->closeWriteChannel();

mProcess->terminate();

mProcess->kill();

 

 

 

//这里面就能获得启动的exe的标准输出信息了

void DebugTarget::log(const QString &msg)

{

    emit debugTargetOutput(msg);

}

特别注意:


如果需要修改process的控制台窗口是否显示,则用下面的函数


mProcess->setCreateProcessArgumentsModifier([this](QProcess::CreateProcessArguments * args)

{   

        //下面这两行让后台exe弹出一个窗口         

        args->flags |=  CREATE_NEW_CONSOLE;            

        args->flags &= ~CREATE_NO_WINDOW;

}

切记:在这个函数里,下面这一行,千万不要用,否则qprocess关闭了标准输出,我们就获取不到输出了(我踩了这个巨坑,一天才找出来)

               args->startupInfo -> dwFlags &= ~STARTF_USESTDHANDLES;


当然,如果不通过信号与槽函数机制,自己主动去读取mProcee的标准输出也可以的,那就用


mProcee->readAll()

mProcee->write()


2.QProcess

QProcess是Qt提供的强大进程交互工具。简单的进程调用,采用上述模式即可解决,而父子进程之间更复杂的交互,QProcess提供了更好的解决方案。


2.1.基础用法-start和startDetached

start是一体式的:外部程序启动后,将随主程序的退出而退出;

startDetached是分离式的:外部程序启动后,不会随主程序的退出而退出。

重要区别:如果是start则回调都可以正常接收到信息;如果是startDetached则回调无法正常接收到信息。

如果是简单调用,建议采用startDetached,需要获取进程执行的各种状态,建议采用start。QProcess启动的控制台程序,都是隐藏窗口。范例代码如下:


#include <QProcess>

static void TestStartDetached(){

    //startDetached是个静态函数,可以直接调用,一共有四种形式

    QProcess::startDetached(QString::fromLocal8Bit("./TestConsoleApplication1.exe"));

    QProcess::startDetached(QString::fromLocal8Bit("./TestConsoleApplication1.exe"), 

    QStringList() << "para1" << "para2");

}


static void TestStart(){

    QProcess* tQProcess = new QProcess;

    //子进程会随着tQProcess的销毁而关闭,如果不想随着函数的调用结束而关闭,需要在堆上申请内存。

    tQProcess->start(QString::fromLocal8Bit("./TestConsoleApplication1.exe"),

    QStringList()<<"para1"<<"para2" );

    //tQProcess->deleteLater();

}


static void TestStart2(){

    //显示窗口-极少使用

    QProcess *tQProcess = new QProcess();

    tQProcess->start("cmd.exe");

    tQProcess->write("cd /d E:/work/CurrentProject/微博/QtConnect/x64/Release && start TestConsoleApplication1.exe\n");

}


2.2.获取子进程的标准输出

进程具有两个预定义的输出通道:标准输出通道(stdout)提供常规控制台输出。标准错误通道(stderr)通常提供由进程打印的错误。

这些通道代表两个单独的数据流,可以通过调⽤setReadChannel()函数在它们之间切换。当前读取通道上有可⽤数据时,QProcess发出readyRead()信号。当有新的标准输出数据可⽤时,它也会发出readyReadStandardOutput()信号,⽽当有新的标准错误数据可⽤时,它会发出readyReadStandardError()信号。⽆需调⽤read()函数,readLine()函数或getChar()函数,⽽是可以通过调⽤readAllStandardOutput()函数或readAllStandardError()函数显式地从两个通道之⼀读取所有数据。


进程同步API

waitForStarted()函数——阻塞直到进程开始。

waitForReadyRead()函数——阻塞直到有新数据可在当前读取通道上读取为⽌。

waitForBytesWritten()函数——阻塞直到将⼀个有效载荷数据写⼊该进程为⽌。

waitForFinished()函数——阻塞直到过程完成。


3.代码范例

3.1.等待进程执行完毕,获取所有的输出

代码如下所示:


#include <qDebug>

#include <QProcess>

static void TestGetOutData(){

    QProcess process;

    process.start("ping www.baidu.com");

    process.waitForFinished();

    QByteArray allOutData = process.readAll();

    if (allOutData.isEmpty())

    {

    allOutData = process.readAllStandardOutput();

    if (allOutData.isEmpty())

    {

    allOutData = process.readAllStandardError();

    }

    }

    //中文乱码问题

    qDebug() << QString::fromStdString(allOutData.data());

}


3.2.子进程返回信号

进程结束,返回进程结束参数。


void QtConnect::finished(int exitCode, QProcess::ExitStatus exitStatus)

{

    qDebug() << "finished";

    qDebug() << exitCode;// 被调用程序的main返回的int

    qDebug() << exitStatus;// QProcess::ExitStatus(NormalExit)

}


void QtConnect::Test() {

    QProcess* tQProcess = new QProcess;

    tQProcess->start(QString::fromLocal8Bit("E:/work/CurrentProject/微博/QtConnect/x64/Debug/TestConsoleApplication1.exe"));

    connect(tQProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finished(int, QProcess::ExitStatus)));

}


3.3.进程是否启动

void QtConnect::Test() {

    QProcess* tQProcess = new QProcess;

    tQProcess->start(QString::fromLocal8Bit("E:/work/CurrentProject/微博/QtConnect/x64/Debug/TestConsoleApplication1.exe"));

    if (!tQProcess->waitForStarted())

    {

    qDebug() << "成功!";

    }

    else

    {

    qDebug() << "失败!";

    }

}


3.4.执行命令行

void QtConnect::Test() {

    QStringList arguments;

    arguments << "/c" << "dir";//

    QProcess tProcess(this);

    tProcess.setProcessChannelMode(QProcess::MergedChannels);

    tProcess.start("cmd.exe", arguments);

    tProcess.waitForStarted();

    tProcess.waitForFinished();

    QString strResult = QString::fromLocal8Bit(tProcess.readAll());

    qDebug() << strResult;

}


3.5.与子进程交互

未来有需要再写,本质就是利用通道来进行数据的交互。


    QProcess gzip;

    gzip.start("gzip", QStringList() << "-c");

    if (!gzip.waitForStarted())

        return false;


    gzip.write("Qt rocks!");

    gzip.closeWriteChannel();


    if (!gzip.waitForFinished())

        return false;


    QByteArray result = gzip.readAll();



Qt 之进程间通信(IPC)

简述
进程间通信,就是在不同进程之间传播或交换信息。那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。
进程间通信(IPC-Interprocess communication)是一组编程接口,能够让程序员协调不同的进程,使之能在一个操作系统里同时运行。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有自己的优点和局限性,因此,对于单个程序而言使用所有的IPC方法并不常见。
版权声明:一去、二三里,未经博主允许不得转载。
通信目的
  • 数据传输:
一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
  • 共享数据:
多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
  • 通知事件:
一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:
多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
  • 进程控制:
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信(IPC)机制,信号和管道是其中的两种。
通信方式
  • 管道(pipe):
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  • 有名管道(named pipe):
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 信号量(semophore):
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 消息队列(message queue):
消息队列就是消息的一个链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号(signal):
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  • 共享内存(shared memory):
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
  • 套接字(socket):
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
Qt进程通信
在Qt应用程序中,Qt提供了几个方法来实现进程间通信(IPC)。可以在助手中查找关键字”Inter-Process”进行查找。
TCP/IP
跨平台的Qt Network模块提供的类可以让网络编程更加便携和方便。它提供了高级类(例如:QNetworkAccessManager、QFtp)通信,使用特定的应用程序级协议,和较底层的类(例如:QTcpSocket、QTcpServer、QSslSocket)用于实现协议。
Shared Memory
跨平台的QSharedMemory-共享内存类,提供对操作系统的共享内存的实现。它允许多个线程和进程安全访问共享内存段。此外,QSystemSemaphore可以用来控制访问由系统共享的资源,以及进程之间的通信。
D-Bus
Qt的D-Bus模块是一种可用于使用D-Bus协议实现IPC的唯一Unix库。它将Qt的信号和槽机制延伸到IPC级别,允许由一个进程发出的信号被连接到另一个进程的槽。Qt的D-Bus文档已经详细说明如何使用Qt中的D-Bus模块。
QProcess
跨平台类QProcess可以用于启动外部程序作为子进程,并与它们进行通信。它提供了用于监测和控制该子进程状态的API。另外,QProcess为从QIODevice继承的子进程提供了输入/输出通道。
Session Management
在Linux/X11平台上,Qt提供了会话管理的支持。会话允许事件传播到进程,例如,当检测到关机时。进程和应用程序可以执行任何必要的操作,例如:保存打开的文档。
更多参考



Qt 之进程间通信(TCP/IP)

简述
可以通过Qt提供的IPC使用TCP/IP,使用QtNetwork模块即可实现,TCP/IP在实现应用程序和进程内部通信或与远程进程间的通信方面非常有用。
QtNetwork模块提供的类能够创建基于TCP/IP的客户端与服务端应用程序。为实现底层的网络访问,可以使用QTcpSocket、QTcpServer和QUdpSocket,并提供底层网络类。还提供了使用常规协议实现网络操作的QNetworkRequest、QNetworkReply、QNetworkAccessManager。
版权声明:一去、二三里,未经博主允许不得转载。
QtNetwork
作为使用IPC的方法,TCP/IP可以使用多种类进行进程内部和外部的通信。
QtNetwork模块提供的类:
说明
QLocalServer
基于服务器的本地套接字的类
QLocalSocket
支持本地套接字的类
QNetworkAccessManager
处理从网络首发收据响应的类
QSocketNotifier
监控从网络通知消息的类
QSsl
在所有网络通信上用于SSL认证的类
QSslSocket
支持通过客户端和服务器端加密的套接字的类
QTcpServer
基于TCP的服务器端类
QTcpSocket
TCP套接字
QUdpSocket
UDP套接字
除表中所示,Qt提供的QtNetwork模块还支持多种协议。如果需要实现内部进程间的通信,建议使用QLocalSocket类。
下面我们来看一个示例,可以在Creator自带的示例中查找QLocalSocket或Local Fortune。
Server
首先,启动Server,这是必然的,服务端不开启,客户端怎么连接得上呢?
server = new QLocalServer(this);// 告诉服务器监听传入连接的名字。如果服务器当前正在监听,那么将返回false。监听成功返回true,否则为falseif (!server->listen("fortune")) {    QMessageBox::critical(this, tr("Fortune Server"),                        tr("Unable to start the server: %1.")                        .arg(server->errorString()));    close();    return; } fortunes << tr("You've been leading a dog's life. Stay off the furniture.")         << tr("You've got to think about tomorrow.")         << tr("You will be surprised by a loud noise.")         << tr("You will feel hungry again in another hour.")         << tr("You might have mail.")         << tr("You cannot kill time without injuring eternity.")         << tr("Computers are not intelligent. They only think they are.");// 有新客户端进行连接时,发送数据connect(server, SIGNAL(newConnection()), this, SLOT(sendFortune()));// 发送数据void Server::sendFortune() {    // 从fortunes中随机取出一段字符串然后进行写入。    QByteArray block;    QDataStream out(&block, QIODevice::WriteOnly);    out.setVersion(QDataStream::Qt_4_0);    out << (quint16)0;    out << fortunes.at(qrand() % fortunes.size());    out.device()->seek(0);    out << (quint16)(block.size() - sizeof(quint16));    // nextPendingConnection()可以返回下一个挂起的连接作为一个连接的QLocalSocket对象。    QLocalSocket *clientConnection = server->nextPendingConnection();    connect(clientConnection, SIGNAL(disconnected()),            clientConnection, SLOT(deleteLater()));    clientConnection->write(block);    clientConnection->flush();    clientConnection->disconnectFromServer(); }

socket被当做server的孩子创建,这意味着,当QLocalServer对象被销毁时它也会被自动删除。这明显是一个删除对象的好主意,使用完成以后,避免了内存的浪费。
Client
启动客户端,连接到对应的服务器,如果连接不上,则进行错误处理。
socket = new QLocalSocket(this); connect(getFortuneButton, SIGNAL(clicked()),        this, SLOT(requestNewFortune())); connect(socket, SIGNAL(readyRead()), this, SLOT(readFortune())); connect(socket, SIGNAL(error(QLocalSocket::LocalSocketError)),        this, SLOT(displayError(QLocalSocket::LocalSocketError)));// 连接到服务器,abort()断开当前连接,重置socket。void Client::requestNewFortune() {    getFortuneButton->setEnabled(false);    blockSize = 0;    socket->abort();    socket->connectToServer(hostLineEdit->text()); }// 读取服务器端发送的数据void Client::readFortune() {    // 读取接收到的数据    QDataStream in(socket);    in.setVersion(QDataStream::Qt_4_0);    if (blockSize == 0) {        if (socket->bytesAvailable() < (int)sizeof(quint16))            return;        in >> blockSize;    }    if (in.atEnd())        return;    QString nextFortune;    in >> nextFortune;    // 如果当前的数据和收到的数据相同,则重新请求一次,因为是随机的字符串,所以肯定不会每次都相同。    if (nextFortune == currentFortune) {        QTimer::singleShot(0, this, SLOT(requestNewFortune()));        return;    }    currentFortune = nextFortune;    statusLabel->setText(currentFortune);    getFortuneButton->setEnabled(true); }// 发生错误时,进行错误处理void Client::displayError(QLocalSocket::LocalSocketError socketError) {    switch (socketError) {    case QLocalSocket::ServerNotFoundError:        QMessageBox::information(this, tr("Fortune Client"),                                 tr("The host was not found. Please check the "                                    "host name and port settings."));        break;    case QLocalSocket::ConnectionRefusedError:        QMessageBox::information(this, tr("Fortune Client"),                                 tr("The connection was refused by the peer. "                                    "Make sure the fortune server is running, "                                    "and check that the host name and port "                                    "settings are correct."));        break;    case QLocalSocket::PeerClosedError:        break;    default:        QMessageBox::information(this, tr("Fortune Client"),                                 tr("The following error occurred: %1.")                                 .arg(socket->errorString()));    }    getFortuneButton->setEnabled(true); }

更多参考



Qt 之进程间通信(Windows 消息)

简述
通过上一节的了解,我们可以看出进程通信的方式很多,今天分享下如何利用Windows消息机制来进行不同进程间的通信。
版权声明:一去、二三里,未经博主允许不得转载。

效果

1.png


发送消息
自定义类型与接收窗体
包含所需库,定义发送的自定义类型、接收消息的窗体标题。自定义类型可以处理消息过多情况下,对消息的区分,如果不需要也可以去掉。
#ifdef Q_OS_WIN#pragma comment(lib, "user32.lib")#include <qt_windows.h>#endifconst ULONG_PTR CUSTOM_TYPE = 10000;const QString c_strTitle = "ReceiveMessage";

发送数据
点击按钮,进行消息发送。里面的do{…}while用来忽略本窗口,当然自身也可以接受自身的消息。
void onSendMessage() {    HWND hwnd = NULL;    //do    //{       LPWSTR path = (LPWSTR)c_strTitle.utf16();  //path = L"SendMessage"       hwnd = ::FindWindowW(NULL, path);    //} while (hwnd == (HWND)effectiveWinId()); // 忽略自己    if (::IsWindow(hwnd))    {        QString filename = QStringLiteral("进程通信-Windows消息");        QByteArray data = filename.toUtf8();        COPYDATASTRUCT copydata;        copydata.dwData = CUSTOM_TYPE;  // 用户定义数据        copydata.lpData = data.data();  //数据大小        copydata.cbData = data.size();  // 指向数据的指针        HWND sender = (HWND)effectiveWinId();        ::SendMessage(hwnd, WM_COPYDATA, reinterpret_cast<WPARAM>(sender), reinterpret_cast<LPARAM>(&copydata));    } }

接收消息
设置标题
这一步很重要,必须与上一步的c_strTitle保持一致,否则会找不到窗体。自定义类型CUSTOM_TYPE也必须保持一致,进行过滤。
setWindowTitle("ReceiveMessage");
  • 1
重写nativeEvent
bool nativeEvent(const QByteArray &eventType, void *message, long *result) {    MSG *param = static_cast<MSG *>(message);    switch (param->message)    {    case WM_COPYDATA:    {        COPYDATASTRUCT *cds = reinterpret_cast<COPYDATASTRUCT*>(param->lParam);        if (cds->dwData == CUSTOM_TYPE)        {            QString strMessage = QString::fromUtf8(reinterpret_cast<char*>(cds->lpData), cds->cbData);            QMessageBox::information(this, QStringLiteral("提示"), strMessage);            *result = 1;            return true;        }    }    }    return QWidget::nativeEvent(eventType, message, result); }

更多参考



Qt 之进程间通信(共享内存)

简述
上一节中,我们分享下如何利用Windows消息机制来进行不同进程间的通信。但是有很多局限性,比如:不能跨平台,而且必须两个进程同时存在才可以,要么进程A发了消息谁接收呢?
下面我们来分享另外一种跨平台的进行间通信的方式-Shared Memory(共享内存)。
Qt提供的基于共享内存的IPC有QSharedMemory类和QSystemSemaphore类,QSharedMemory可以访问共享内存区域,以及多线程和进程的共享内存区域。而QSystemSemaphore类用于访问系统共享资源,以实现独立进程间的通信。
版权声明:一去、二三里,未经博主允许不得转载。
QSharedMemory
QSharedMemory读写内存时,可以使用lock()实现同步。因此,如果同步完成,必须使用unlock()为共享内存区域解锁。
QSharedMemory可以使用attach()访问共享内存。可以通过指定参数来设置共享内存的访问模式。如果使用的是QSharedMemory::ReadOnly模式,则只能通过只读模式访问共享内存。反之,使用QSharedMemory::ReadWrite模式则可以通过读写模式访问共享内存。
QSharedMemory拥有进程并提供可以返回共享内存区域指针的成员函数。在共享内存区域,成员函数constData()可以通过void类型返回进程正在使用的内存区域指针。创建共享时,QSharedMemory可以以字节为单位分配共享内存区域,还可以通过第二个参数设置函数attach()提供的模式。
QSharedMemory可以设置特定共享内存的固定键。函数setNativeKey()可以设置共享内存对象的键,该函数使用从属平台的共享内存的键进行相关设置。相反,使用函数setKey()可以设置与独立与平台的键。函数setKey()创建与平台本地键(Native Key)映射的键。
QSystemSemaphore
QSystemSemaphore可以提供普通系统的信号量。信号量使用互斥体,而互斥体只可以使用1次锁定(Block)。因此,QSemaphore类不能对有效资源使用多线程,而QSystemSemaphore类可以再多进程或多线程中实现。
QSystemSemaphore与QSemaphore类不同,可以访问多进程。这表示QSystemSemaphore是一个重量级的类。因此,使用单一线程或进程时,建议使用QSemaphore。获得资源前,成员函数acquire()始终阻塞。函数release()用于释放资源,且该函数可以设置参数。该函数的参数>1时,释放资源。
注意事项
初始化QSharedMemory时,必须指定一个唯一的标识Key,进程的Key必须保持一致。可以使用setKey来设置。
加载进内存
说明
进程A-写
分为下面几步:
  1. 检测该进程是否连接到共享内存段,如果连接,则将该进程与共享内存段分离。
  2. 从系统足够大的内存中得到一个新的共享内存段。
  3. 锁定该共享内存段,以阻止第二个对话框进程访问,将缓冲区中的图片复制进共享内存段。
  4. 将共享内存段解锁,然后第二个对话框进程就可以访问了。
实现
void Dialog::loadFromFile() {    if (sharedMemory.isAttached())    {        // 将该进程与共享内存段分离        if (!sharedMemory.detach())            qDebug() << "Unable to detach from shared memory.";    }    QString fileName = QFileDialog::getOpenFileName(0, QString(), QString(),                                        tr("Images (*.png *.xpm *.jpg)"));    QImage image;    if (!image.load(fileName))    {        qDebug() << "Selected file is not an image, please select another.";        return;    }    // 将数据加载到共享内存中    QBuffer buffer;    buffer.open(QBuffer::ReadWrite);    QDataStream out(&buffer);    out << image;    int size = buffer.size();    // 创建共享内存段    if (!sharedMemory.create(size))    {        qDebug() << sharedMemory.errorString() << "\n Unable to create shared memory segment.";        return;    }    sharedMemory.lock();    char *to = (char*)sharedMemory.data();    const char *from = buffer.data().data();    memcpy(to, from, qMin(sharedMemory.size(), size));    sharedMemory.unlock(); }
从内存中读取
说明
进程B-读
分为下面几步:
  1. 将该进程与进程A创建的共享内存段绑定
  2. 锁定共享内存段,复制数据到缓冲区,然后写入到QImage中。
  3. 将共享内存段解锁,然后将该进程与共享内存段分离。
实现
void MainWindow::loadFromMemory() {    // 将共享内存与该进程绑定    if (!sharedMemory.attach())    {        qDebug() << "Unable to attach to shared memory segment.";        return;    }    // 从共享内存中读取数据    QBuffer buffer;    QDataStream in(&buffer);    QImage image;    sharedMemory.lock();    buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());    buffer.open(QBuffer::ReadOnly);    in >> image;    sharedMemory.unlock();    sharedMemory.detach();    m_pLabel->setPixmap(QPixmap::fromImage(image)); }
更多参考



Qt 之进程间通信(QProcess)

QProcess可以在应用程序内部与其它进程通信,或启动其它应用程序。与在终端机之类的命令输入窗口上使用名称和参数是一样的,可以使用QProcess提供的函数start()启动进程。可以注册QStringList处理进程后的参数。
版权声明:一去、二三里,未经博主允许不得转载。
命令行参数启动
说明
进程A-带参启动进程B
  1. 一般编写程序时,严格来说,启动外部程序,需要判断版本是debug还是release。否则,有可能会造成错误。
  2. 判断将要启动的进程是否存在,如果不存在,则启动;否则,不启动。
  3. 传参:这里我列举的是json格式。
实现
void onSendMessage() {    QString strExe("");    if (m_pProcess == NULL)        m_pProcess = new QProcess(this);#if defined(QT_DEBUG)    strExe = "ReceiveMessaged.exe";#   else    strExe = "ReceiveMessage.exe";#  endif    // 判断进程是否存在    QProcess tasklist;    tasklist.start("tasklist",                   QStringList() << "/NH"                   << "/FO" << "CSV"                   << "/FI" << QString("IMAGENAME eq %1").arg(strExe));    tasklist.waitForFinished();    QString strOutput = tasklist.readAllStandardOutput();    if (!strOutput.startsWith(QString("\"%1").arg(strExe)))    {        QJsonObject json;        json.insert("UserName", QStringLiteral("╰☆一去、二三里`"));        json.insert("Password", "123456");        QJsonDocument document;        document.setObject(json);        QByteArray byteArray = document.toJson(QJsonDocument::Compact);        QStringList arguments;        arguments << byteArray;        m_pProcess->startDetached(strExe, arguments);    } }

命令行读取
说明
进程B-命令行读取
  1. 在main函数中初始化QApplication以后,获取命令行参数。
  2. 命令行参数中包含当前程序的名称、接收的参数等信息。
实现
QStringList cmdLineArgs = QCoreApplication::arguments(); QMessageBox::information(NULL, QStringLiteral("ReceiveMessage"), cmdLineArgs.join(" "));
  • 1



Qt线程同步的几种方法

一、QMutex类

       QMutex类就像一把锁,在互斥量之前上锁(QMutex::lock()),然后在使用完互斥量之后解锁(QMutex::unlock())。比如下面的代码:

void someMethod()

{

    mutex.lock();

    qDebug()<<"Hello";

    qDebug()<<"World";

    mutex.unlock();

}

 

class Thread1 : public QThread  

{

protected:

    virtual void run()

    {

        someMethod();

    }

};

 

class Thread2 : public QThread  

{

protected:

    virtual void run()

    {

        someMethod();

    }

};

如上面的代码,在函数someMethod里面有两条语句,如果有两个线程启动之后,这两个线程都将调用这个函数(run函数即为线程启动后自动执行的函数),则可能会出现的结果是Hello Hello World World。但显然,这并不是我们想要的,我们希望的是每个线程可以一次性执行完someMethod函数里面的代码。这个时候我们便可以在函数里面给函数体加上锁,然后在结束的时候解锁。

这里需要注意的是,如果一个线程试图向一个已经被其它线程上锁了互斥量上锁的话,这个线程将被阻塞(挂起),直到这个互斥量被解锁。如果一个线程希望自己在试图对一个上锁了的互斥量进行访问的时候能够不被阻塞(而是立即返回),可以将lock()函数替换为tryLock()函数,这个函数的效果是:如果线程正在试图访问的互斥量已经被上锁了,那么可以立即返回而不被阻塞。

二、QMutexLocker便利类

使用 QMutex 对互斥量进行加锁解锁比较繁琐,在一些复杂的函数或者抛出C++异常的函数中都非常容易发生错误。可以使用一个方便的 QMutexLocker 类来简化对互斥量的处理。首先,QMutexLocker类的构造函数接收一个QMutex对象作为参数并且上锁,然后在析构函数中自动对其进行解锁。如下代码:

 

QMutex mutex;

 

void someMethod()

{

    QMutexLocker locker(&mutex);

    qDebug()<<"Hello";

    qDebug()<<"World";

}

这里创建一个QMutexLocker类实例,在这个实例的构造函数中将对mutex对象进行加锁。然后在析构函数中自动对mutex进行解锁。解锁的工作不需要显示地调用unlock函数,而是根据QMutexLocker对象的作用域绑定在一起了。

三、QReadWriteLock类

      前两种保护互斥量的方法比较绝对,其达到的效果是:不管我要对互斥量做些是什么,我都要一个人霸占着,即使我只是看看它,也不能让别人看。这会使得这个互斥量资源的使用率大大下降,造成资源等待等问题。

      于是,我们可以对线程对互斥量的操作进行分类:读和写。有几种情况:

1、如果我只是看看的话,你也可以看,大家看到的都是正确的结果;

2、如果我要看这个数据,你是不能改的,不然我看到的结果就不知道是什么了;

3、我在改的时候,你不能看,否则我可能会让你看到不正确的结果;

4、我在改的时候,你当然不能改了。

因此,我们可以对QMutex锁进行升级,将其升级为QReadWriteLock,QMutex加锁的方法是lock(),而QReadWriteLock锁有两种锁法:设置为读锁(lockForRead())和写锁(lockForWrite())。代码如下:

QReadWriteLock lock;

void someMethod()

{

lock.lockForRead(); //为读而锁

//lock.lockForWrite(); //为写而锁

qDebug()<<"Hello";

qDebug()<<"World";

 

lock.unlock();  //解锁

}

于是可能有以下四种情况:

1、一个线程试图对一个加了读锁的互斥量进行上读锁,允许;

2、一个线程试图对一个加了读锁的互斥量进行上写锁,阻塞;

3、一个线程试图对一个加了写锁的互斥量进行上读锁,阻塞;

4、一个线程试图对一个加了写锁的互斥量进行上写锁,阻塞。

所以可以看出,读写锁比较适用的情况是:需要多次对共享的数据进行读操作的阅读线程。

四、QReadLocker便利类和QWriteLocker便利类对QReadWriteLock进行加解锁

和QMutex与QMutexLocker类的关系类似,关于读写锁也有两个便利类,读锁和写锁,QReadLocker和QWriteLocker。它们的构造函数都是一个QReadWriteLock对象,不同的是,在QReadLocker的构造函数里面是对读写锁进行lockForRead()加锁操作,而在QWriteLocker的构造函数里面是对读写锁进行lockForWrite()加锁操作。然后解锁操作unlock()都是在析构函数中完成的。

void write()

{

QReadLocker locker(&lock);

..........

}

 

void read()

{

QWriteLocker locker(&lock);

..............

}

QReadLocker和QWriteLocker的成员函数都一模一样,退出函数的时局部变量locker会自动销毁,讲lock自动解锁。也可以调用locker.unlock()给lock解锁,然后再调用locker.relock()再锁住lock。也可以调用locker.readWriteLock()获取建立locker时引入的那个lock的指针。

五、信号量QSemaphore

前面的几种锁都是用来保护只有一个变量的互斥量的。但是还有些互斥量(资源)的数量并不止一个,比如一个电脑安装了2个打印机,我已经申请了一个,但是我不能霸占这两个,你来访问的时候如果发现还有空闲的仍然可以申请到的。于是这个互斥量可以分为两部分,已使用和未使用。一个线程在申请的时候,会对未使用到的部分进行加锁操作,如果加锁失败则阻塞,如果加锁成功,即又有一个资源被使用了,于是则将已使用到的部分解锁一个。

以著名的生产者消费者问题为例,分析问题:生产者需要的是空闲位置存放产品,结果是可取的产品多了一个。于是,我们可以定义两个信号量:QSemaphore freeSpace和QSemaphore usedSpace,前者是给生产者使用的,后者是给消费者使用的。

 

六、条件触发QWaitCondition

QWaitCondition最大的好处,我觉得,是能在一个线程中唤醒一个或多个其它线程,当然前提是,其它线程在等待某个QWaitCondition,否则不起作用,你唤醒也没用。QWaitCondition必须与QMutex或者QReadwriteLock一起用。


Qt6


Qt6 C++ 实现按住窗口拖拽移动

按住窗口拖拽

实现鼠标左键按住窗口后,移动鼠标拖拽窗口四处移动


首先需要在头文件内定义两个之后要重写的事件

mousePressEvent 和 mouseMoveEvent


代码清单:MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QMainWindow>

#include <QContextMenuEvent>

#include <QMouseEvent>

#include <QMenu>



QT_BEGIN_NAMESPACE

namespace Ui { class MainWindow; }

QT_END_NAMESPACE


class MainWindow : public QMainWindow

{

    Q_OBJECT


public:

    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();


private:

    Ui::MainWindow *ui;


protected:

    // 重写鼠标按下事件

    // 重写鼠标移动事件

    void mousePressEvent(QMouseEvent *evt);

    void mouseMoveEvent(QMouseEvent *evt);


private:

    // 在这里配置偏移值,以确保移动窗口时位置正确

    QPoint mOffset;

};

#endif // MAINWINDOW_H



之后就仅需我们在 cpp 里实现这两个事件函数即可


由于版本较新的缘故,目前官方推荐使用 globalPosition 获取当前鼠标全局位置,之后才使用 toPoint 转换为对于坐标点


代码清单:MainWindow.cpp


// 鼠标按下的一刻,获取偏移值offset

// 代码计算出鼠标按下时窗口左上角的坐标与鼠标按下时鼠标指针的位置之间的偏移量,并将其存储在mOffset变量中

void MainWindow::mousePressEvent(QMouseEvent *evt)

{

    // globalPosition()返回当前鼠标指针的全局位置

    mOffset = evt->globalPosition().toPoint() - this->pos();

}


// 代码计算出当前鼠标指针的全局位置与偏移量之间的差值,并将窗口的位置设置为该值。这会导致窗口跟随鼠标移动

void MainWindow::mouseMoveEvent(QMouseEvent *evt)

{

    this->move(evt->globalPosition().toPoint()-mOffset);

}


Qt6 C++ 右键弹出菜单栏

右键弹出菜单

接下来将实现这个功能:右键点击主窗口 MainWindow 后,弹出一个关闭软件的菜单


首先我们需要在头文件内定义对应的事件以及弹出的目录和鼠标动作这三样关键要素


代码清单:MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QLabel>

#include <QMainWindow>


QT_BEGIN_NAMESPACE

namespace Ui {

class MainWindow;

}

QT_END_NAMESPACE


class MainWindow : public QMainWindow {

    Q_OBJECT


   public:

    MainWindow(QWidget* parent = nullptr);

    ~MainWindow();


   protected:

   // 重写QContextMenuEvent鼠标右键点击事件,实现右键弹出菜单栏

    void contextMenuEvent(QContextMenuEvent* event);


   private:

    Ui::MainWindow* ui;


    QMenu* mExitMenu;   // 右键退出的菜单

    QAction* mExitAct;  // 退出的行为

};

#endif  // MAINWINDOW_H



#include "mainwindow.h"


#include <QContextMenuEvent>

#include <QDebug>

#include <QMenu>


#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget* parent)

    : QMainWindow(parent), ui(new Ui::MainWindow) {

    ui->setupUi(this);


    //设置窗口属性

    setWindowFlag(Qt::FramelessWindowHint);  // 无边框

    setFixedSize(width(), height());         // 固定窗口大小


    // 右键菜单:退出程序

    mExitMenu = new QMenu(this);

    mExitAct = new QAction();

    mExitAct->setText(tr("退出"));

    mExitAct->setIcon(QIcon(":/res/close.png"));

    mExitMenu->addAction(mExitAct);


    // 链接对应信号与槽

    // mExitAct 表示由鼠标点击事件触发

    // &QAction::triggered 表示时间触发的方式

    // this 处理触发的对象我们就选择当前mainwindow

    // 最后的一个lambda函数,使用qApp直接退出该软件

    connect(mExitAct, &QAction::triggered, this, [=]() { qApp->exit(0); });

}


MainWindow::~MainWindow() { delete ui; }


// 重写的contextMenuEvent方法

void MainWindow::contextMenuEvent(QContextMenuEvent* event) {

    // 在鼠标当前位置启动菜单栏

    mExitMenu->exec(QCursor::pos());

    // 允许鼠标右键点击事件

    event->accept();

}


Qt6 C++基础入门4 事件处理与eventFilter检测

事件处理

覆写指定事件实现检测

QObject 为我们提供了丰富的关于鼠标、键盘等事件的检测,我们仅需要对其覆写即可实现检测效果


代码清单:widget.h


protected:

    // 覆写按键点击事件

    void keyPressEvent(QKeyEvent *event);


之后来到主文件 widget.cpp,直接覆写对应的时间即可


// 覆写按键点击事件

void Widget::keyPressEvent(QKeyEvent *event)

{

    // 检测按下alt和x键

    if(event->modifiers()==Qt::AltModifier && event->key()==Qt::Key_X){

        qDebug() << "press key alt m";

    }else{

        // 检测不到,就一直检测

        QWidget::keyPressEvent(event);

    }

}



eventFilter 按键检测

eventFilter 可以实现让一个对象监听另一个对象的所有事件,我们只需要在对应的函数中对截取到的事件进行过滤后处理对应事件即可


我们接下来实现这样的效果:在 widget 主窗口点击 ctrl+m,然后调试输出一段文本


首先在头文件 Widget.h 定义一下虚函数 eventFilter


...


class Widget : public QWidget

{

    Q_OBJECT


protected:

    // 添加虚函数,注意尾部的override对其进行覆写,否则无法使用!

    bool eventFilter(QObject *watched, QEvent *event) override;


...

};

#endif // WIDGET_H


紧接着进入主文件 Widget.cpp


由于我们需要监听整个 widget,所以在主构造函数里面使用 installEventFilter 装载监听器,监听对象就是自己 this


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    // 装载监听器,监听对象就是自己

    installEventFilter(this);

}


之后就是对应的事件过滤函数,我们需要对其进行覆写


bool Widget::eventFilter(QObject *obj, QEvent *event)

{

    // 如果事件类型为键盘点击事件

    if (event->type() == QEvent::KeyPress) {

        // 强制转换事件类型为键盘点击事件

        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

        // 如果同时按下ctrl与m键

        if (keyEvent->modifiers()==Qt::ControlModifier && keyEvent->key() == Qt::Key_M) {

            // 输出一段调试文本

            qDebug() << "M key pressed";

        }

    }

    // 最后返回事件作为结尾

    return QWidget::eventFilter(obj, event);

}


完整代码 Widget.cpp


#include "Widget.h"

#include "ui_Widget.h"

#include "QKeyEvent"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    installEventFilter(this);

}


Widget::~Widget()

{

    delete ui;

}


bool Widget::eventFilter(QObject *obj, QEvent *event)

{

    if (event->type() == QEvent::KeyPress) {

        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

        if (keyEvent->modifiers()==Qt::ControlModifier && keyEvent->key() == Qt::Key_M) {

           qDebug() << "M key pressed";

        }

    }

    return QWidget::eventFilter(obj, event);

}



Qt6 C++基础入门3 对话框与MainWindow

对话框

目前的对话框主要有以下几大类


文件对话框( QFile Dialog)

消息对话框( QMessageBox)

输入对话框( QInputDialog)

颜色对话框( QColorDialog)

字体对话框( QFontDialog)


这是七大对话框及其基本用法的实例参考,所有代码都写在槽函数里面了


#include "Widget.h"

#include "ui_Widget.h"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    te = ui->textEdit;

}


Widget::~Widget()

{

    delete ui;

}



// 打开文件对话框

void Widget::on_fileBtn_clicked()

{

    // 打开多个文件并获取文件完整路径

    // 如果你想打开单个文件,请使用getOpenFileName方法

    QStringList filenames = QFileDialog::getOpenFileNames(

                this,

                "打开图片", ".", "Images (*.png *.jpg)");

            for(int i=0; i<filenames.length(); i++)

                te->append(filenames[i]);

}



// 颜色选择对话框

void Widget::on_colorBtn_clicked()

{

    QColor color = QColorDialog::getColor();

            te->setTextColor(color);

}



// 字体选择对话框

void Widget::on_fontBtn_clicked()

{

    bool ok;

            QFont font = QFontDialog::getFont(&ok);


            if(ok)  //用户选择了字体

                te->setCurrentFont(font);

}



void Widget::on_inputBtn_clicked()

{

    QString str = QInputDialog::getText(this, "xxxx", "yyyy");

            te->setText(str);

}



void Widget::on_errorBtn_clicked()

{

    QErrorMessage *x;

            x->showMessage("error message show");

}



void Widget::on_messageBtn_clicked()

{

    QMessageBox::warning(this, "xxxx", "yyyyyyy", QMessageBox::Open, QMessageBox::Apply);

}



void Widget::on_processBtn_clicked()

{

    QProgressDialog x;

            x.setValue(88);

            x.exec();

}


MainWindow


菜单

使用代码的方式添加(不推荐,不方便管理)


#include "MainWindow.h"

#include "ui_MainWindow.h"


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    // 创建菜单项,名称为“打开文件”

    QAction *a1 = new QAction("打开文件");

    a1->setIcon(QIcon("xxx.png")); // 设置菜单项图标

    a1->setShortcut(QKeySequence("Ctrl+O")); // 设置菜单项右侧简介


    // 创建主菜单,名称为“文件”

    QMenu *menu = menuBar()->addMenu("文件");

    menu->addAction(a1); // 把菜单项插入到主菜单里面

}


MainWindow::~MainWindow()

{

    delete ui;

}


使用 Designer 直接设计


进入 UI 编辑界面,双击顶部即可创建新的主菜单;

进入主菜单后,继续双击即可创建菜单项;


下图创建一个使用快捷键 ctrl+f 触发的菜单项,我们在创建时应该输出: new (&F)


其中 &F 表示以&后面的第一个字母作为 ctrl 的辅助键,例如 &X 就表示 ctrl+x


1.png



如何触发菜单项点击事件?


新建一个主菜单以及对应菜单项后,我们会在设计页面的右侧预览中找到对应的组件


如下图


menuBar 表示默认的菜单组件

menu 表示新添加的主菜单“文件”

actionFile 表示先添加的菜单项“new”

1.png


之后直接回到 MainWindow.cpp,添加对应 connect 即可!


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    connect(ui->actionNew,&QAction::triggered,this,&MainWindow::newFile);

}


工具栏

在 designer 界面,右键点击 mainwindow,弹出窗口选择添加工具栏即可!


由于无法直接在 designer 里面添加工具栏项,我们只能借用菜单栏内的菜单项(即 action)

下方代码我们先使用变量获取 toolbar 以及对应的 actionnew,然后使用 addAction 把对应的菜单项添加到工具栏即可!


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    toolBar = ui->toolBar;      // 获取工具栏实例

    actionNew = ui->actionNew;  // 获取欲添加的菜单项实例


    // 先绑定菜单项action对应的信号和槽

    connect(actionNew,&QAction::triggered,this,&MainWindow::newFile);


    // 然后把菜单项插入到工具栏

    toolBar->addAction(actionNew);


}


Qt6 C++基础入门2 文件结构与信号和槽

标准文件结构


widget.h

widget 对象的头文件


一般会直接在头文件导入所有后续在 cpp 文件内用到的类,所以 include 基本都会写在这里


// 头文件标志起始

#ifndef WIDGET_H

#define WIDGET_H


// 头文件导入

#include <QWidget>


// 这一块不要动,你也动不了现在

QT_BEGIN_NAMESPACE

namespace Ui { class Widget; }

QT_END_NAMESPACE


class Widget : public QWidget

{

    Q_OBJECT


// 初始化定义区域,定义非信号和槽方法

public:

    Widget(QWidget *parent = nullptr);

    ~Widget();


// 定义信号的区域

signals:


// 定义槽的区域

private slots:

    void on_pushButton_clicked();


// 定义全局私有变量

private:

    Ui::Widget *ui;

};

#endif // WIDGET_H



widget.cpp

// 头文件导入区

#include "Widget.h"

#include "ui_Widget.h"


// 主构造函数,可以自定义构造函数的参数以及继承规则

Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    // 定义Widget被实例化后立刻执行的代码

    // 比如TCP链接或者调试输出啥的

}


// 析构函数,用于Widget被销毁前需执行的代码

Widget::~Widget()

{

    delete ui;

}


// 在这里定义信号以及槽的具体实现方法

// ...



main.cpp

主入口文件


#include "Widget.h"


#include <QApplication>


// 主入口,代码从这里执行

int main(int argc, char *argv[])

{

    QApplication a(argc, argv);


    // 实例化widget后使用show显示他

    Widget w;

    w.show();


    // 程序结束,使用exec

    return a.exec();

}


pro 文件

该文件比较复杂,具体使用方式请查看帮助文档,这里没办法告诉你具体的使用方式


最常用的就是当你使用 TCP 链接或者任意网络请求时,必须要在第一行的末尾添加一个 network,就在下方代码第一行末尾注释区那边


QT       += core gui #network


greaterThan(QT_MAJOR_VERSION, 4): QT += widgets


CONFIG += c++11


# You can make your code fail to compile if it uses deprecated APIs.

# In order to do so, uncomment the following line.

#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \

    main.cpp \

    Widget.cpp


HEADERS += \

    Widget.h


FORMS += \

    Widget.ui


# Default rules for deployment.

qnx: target.path = /tmp/$${TARGET}/bin

else: unix:!android: target.path = /opt/$${TARGET}/bin

!isEmpty(target.path): INSTALLS += target



信号与槽


自定义信号

实现功能:点击按钮发射一个信号,widget 获取信号后执行对应槽函数输出一段信息(此过程含有信息的传递)


新建一个 Widget 文件,UI 设计图添加一个 pushbutton,重命名为 firstBtn,并且为其添加一个空的 clicked() 槽


此时的 Widget.h 文件应该是这样的


#ifndef WIDGET_H

#define WIDGET_H


#include <QWidget>

#include <QDebug>


QT_BEGIN_NAMESPACE

namespace Ui { class Widget; }

QT_END_NAMESPACE


class Widget : public QWidget

{

    Q_OBJECT


public:

    Widget(QWidget *parent = nullptr);

    ~Widget();


// 自定义一个新的信号,其接收一个字符串参数

signals:

    void firstSignal(QString msg);


// firstEmit为自定义槽函数,用于响应自定义信号firstSignal

// on_firstBtn_clicked为按钮点击相应槽函数

private slots:

    void firstEmit(QString msg);

    void on_firstBtn_clicked();


private:

    Ui::Widget *ui;

};

#endif // WIDGET_H


代码清单 Widget.cpp


注意,如果信号定义 N 个形参,那么对应接收的槽也必须有等于或少于 N 个的形参(绝对不能大于 N)!因为发射的信号的所有参数值都会一一传递给槽函数,所有参数都是对应关系!


#include "Widget.h"

#include "ui_Widget.h"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    // 第一步,connect链接信号和槽

    // 参数一:信号发出者,这里选择当前widget

    // 参数二:欲发出的信号

    // 参数三:信号接收者,这里也是当前widget

    // 参数四:欲处理对应信号的槽函数

    connect(this,&Widget::firstSignal,this,&Widget::firstEmit);

}


Widget::~Widget()

{

    delete ui;

}


// 第二步:定义处理信号的槽函数

// 函数有一个形参,用于接收信号传递过来的参数

void Widget::firstEmit(QString msg)

{

    // 调试输出信号发射过来的参数msg

    qDebug() << msg;

}


// 第三步:定义发射信号的按钮响应槽函数

void Widget::on_firstBtn_clicked()

{

    // 使用emit发射对应名称的信号,注意我们这里传入了一个字符串作为参数

    emit firstSignal("shit");

}


此时保存文件,编译运行,可见点击按钮后就会在 console 里面看见我们输出的调试信息了!



connect 的两种方式

特别注意,只要选择了一种方式,那么信号和槽都必须使用同种方式添加,不能说我信号使用 SIGNAL 然后槽使用引用,这是不对的!


链接信号和槽时可以使用引用的方式,或者实例化对象的方式:


实例化时,信号和槽末尾要添加小括号

引用时,不能保留小括号

#include "MainWindow.h"

#include "ui_MainWindow.h"


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    // 方法一:使用SIGNAL以及SLOT传入信号和槽

    connect(this,SIGNAL(customSignal()),this,SLOT(on_pushButton_clicked()));


    // 方法二:直接使用引用传入信号和槽

    connect(this,&MainWindow::customSignal,this,&MainWindow::on_pushButton_clicked);


    emit customSignal();

}


MainWindow::~MainWindow()

{

    delete ui;

}


void MainWindow::on_pushButton_clicked()

{

    qDebug() << "this is a sentence";

}


Qt6 C++基础入门1 定时器与QTimer

定时器


定时器图片流水灯案例

实现效果:构建一个界面,点击开始按钮轮流播放文件夹下图片,点击停止按钮停止播放


1.png


构建页面,上部是一个没有内容的 label

下面是开始和暂停按钮,各自的名称分别为 startBtn 和 stopBtn



先保持在 UI 设计界面别走

右键点击开始和停止按钮,选择添加槽,各自添加一个 clicked 槽!


此时不需要为槽添加响应内容,我们打开 Widget.h


下图注释部分即为我们需要添加的部分


#ifndef WIDGET_H

#define WIDGET_H


#include <QWidget>


// 第一步:定义轮播间隔时间

#define TIMEOUT 1*1000


QT_BEGIN_NAMESPACE

namespace Ui { class Widget; }

QT_END_NAMESPACE


class Widget : public QWidget

{

    Q_OBJECT


public:

    Widget(QWidget *parent = nullptr);


    // 第二步:定义虚函数timerEvent,表示定时器事件,他接受一个QTimerEvent类型的参数

    virtual void timerEvent(QTimerEvent *evt);

    ~Widget();


private slots:


    // 第三步:我们为两个按钮添加槽后,这边自动生成的代码

    void on_startBtn_clicked();

    void on_stopBtn_clicked();


private:

    Ui::Widget *ui;


    // 第四步:定义定时器ID(保证定时器的唯一性),以及当前图片轮播ID

    int myTimerId;

    int pixId;

};

#endif // WIDGET_H



头文件已经处理完毕,打开 Widget.cpp


首先在 widget 初始化时,我们就需要为 label 指定显示一张图片


#include "Widget.h"

#include "ui_Widget.h"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    // 下一张图片ID

    pixId = 2;

    // 默认显示的图片,即第一张图片1.png

    QPixmap pix("E:\\singlechip\\Linux\\qt\\qt_demo2\\image\\1.png");

    // 将图片绑定到界面内部的label里去

    ui->label->setPixmap(pix);

}



为两个信号槽定义代码


// 开始按钮

void Widget::on_startBtn_clicked()

{

    // 开启定时器,并且返回定时器编号

    myTimerId = this->startTimer(TIMEOUT);

}


// 停止按钮

void Widget::on_stopBtn_clicked()

{

    // 杀死指定ID的定时器

    this->killTimer(myTimerId);

}



还记得我们之前在头文件中注册的虚函数 timerEvent 吗?

我们需要在这边实现它,定义事件处理函数


void Widget::timerEvent(QTimerEvent *evt)

{

    // 如果取得的定时器ID不对应,那么不执行后续代码

    if(evt->timerId()!=myTimerId) return;


    // 字符串拼接获得图片路径

    QString path("E:\\singlechip\\Linux\\qt\\qt_demo2\\image\\");

    path+=QString::number(pixId);

    path+=".png";


    // 将图片路径绑定到label上,以便显示图片

    QPixmap pix(path);

    ui->label->setPixmap(pix);


    // 每次显示完毕后ID都自增一次,直到递增超过文件夹下最大图片数量,就重置为1,从头开始显示

    pixId++;

    if(pixId==3) pixId=1;

}



这是完整的 Widget.cpp 代码:


#include "Widget.h"

#include "ui_Widget.h"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    pixId = 2;

    QPixmap pix("E:\\singlechip\\Linux\\qt\\qt_demo2\\image\\1.png");

    ui->label->setPixmap(pix);

}


Widget::~Widget()

{

    delete ui;

}



void Widget::on_startBtn_clicked()

{

    // 开启定时器,并且返回定时器编号

    myTimerId = this->startTimer(TIMEOUT);

}


void Widget::timerEvent(QTimerEvent *evt)

{

    if(evt->timerId()!=myTimerId) return;


    QString path("E:\\singlechip\\Linux\\qt\\qt_demo2\\image\\");

    path+=QString::number(pixId);

    path+=".png";


    QPixmap pix(path);

    ui->label->setPixmap(pix);


    pixId++;

    if(pixId==3) pixId=1;

}



void Widget::on_stopBtn_clicked()

{

    this->killTimer(myTimerId);

}


大功告成,你现在可以点击左下角的绿色按钮执行测试了



QTimer

QTimer 的使用方式和上面那个差不多,只是稍微流程有些变动


打开 widget.h


#ifndef WIDGET_H

#define WIDGET_H


#include <QWidget>


// 导入QTimer

#include <QTimer>

// 设置好间隔时间

#define TIMEOUT 1*1000


QT_BEGIN_NAMESPACE

namespace Ui { class Widget; }

QT_END_NAMESPACE


class Widget : public QWidget

{

    Q_OBJECT


public:

    Widget(QWidget *parent = nullptr);

    ~Widget();


private slots:

    // 需要手动在此注册超时函数的槽

    void timeout_slot();


    // 定义开始和停止按钮的点击槽

    void on_startBtn_clicked();

    void on_stopBtn_clicked();


private:

    Ui::Widget *ui;


    // 定义QTimer全局变量

    QTimer *timer;

    // 定义计数器,指示当前数字

    int currentNumber;

};

#endif // WIDGET_H


这是对应的 widget.cpp 代码清单


#include "Widget.h"

#include "ui_Widget.h"


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    // 实例化计时器

    timer = new QTimer;


    // 初始值为0,显示在label内部,注意数值和文本之间的类型转换

    currentNumber=0;

    ui->label->setText(QString::number(currentNumber));


    // 使用connect链接信号和槽!

    connect(timer,&QTimer::timeout,this,&Widget::timeout_slot);

}


Widget::~Widget()

{

    delete ui;

}



void Widget::on_startBtn_clicked()

{

    // 开启计时器

    timer->start(TIMEOUT);

}


// 超时函数,每经过一次计时周期(TIMEOUT的长度),就会执行一次如下槽内的代码

void Widget::timeout_slot()

{

    currentNumber++;

    ui->label->setText(QString::number(currentNumber));

}



void Widget::on_stopBtn_clicked()

{

    // 停止计时器

    timer->stop();

}



Qt6 C++ 网络请求实例

实用概念

QNetworkAccessManager是 Qt 网络模块提供的一个用于发送网络请求和接收网络响应的类。它是一个高层次的网络 API,提供了一种方便的方式来发送和接收 HTTP、FTP 和其他网络协议的数据。QNetworkAccessManager的主要功能包括:


发送网络请求:QNetworkAccessManager提供了多种发送网络请求的方法,包括get()、put()、post()、deleteResource()等。


接收网络响应:QNetworkAccessManager可以异步接收网络响应,并在响应到达时发出信号。


处理网络错误:QNetworkAccessManager可以处理网络错误,例如连接超时、网络不可用等。


处理网络代理:QNetworkAccessManager可以使用网络代理来发送和接收网络请求。


使用QNetworkAccessManager时,通常需要创建一个QNetworkRequest对象来描述网络请求的 URL、HTTP 头、请求方法等信息,并将其传递给QNetworkAccessManager的发送请求方法中。


使用QNetworkAccessManager时,可以通过连接QNetworkAccessManager的信号,例如finished()和sslErrors(),来处理网络响应和错误。在接收到网络响应时,可以使用QNetworkReply类来访问响应的 HTTP 头、数据和状态码等信息。



QNetworkReply是 Qt 网络模块提供的一个用于处理网络响应的类。它提供了访问响应 HTTP 头、数据和状态码等信息的方法,并通过信号和槽机制提供了响应处理的异步方式。


QNetworkReply的主要功能包括:


接收网络响应:QNetworkReply可以异步接收网络响应,并在响应到达时发出信号。


访问响应数据:QNetworkReply提供了多种方法来访问响应数据,例如read()、readAll()、bytesAvailable()等。


访问响应 HTTP 头:QNetworkReply提供了多种方法来访问响应的 HTTP 头信息,例如header()、hasRawHeader()、rawHeaderList()等。


访问响应状态码:QNetworkReply提供了方法来访问响应的状态码。


使用QNetworkReply时,通常需要连接QNetworkReply的信号,例如readyRead()和finished(),来处理响应数据和完成响应处理。在处理响应数据时,可以使用QIODevice的读取函数来读取数据。在响应处理完成后,需要调用QNetworkReply的deleteLater()方法来释放资源。


需要注意的是,由于网络请求和响应的处理是异步的,所以在处理响应时需要注意线程安全和对象生命周期等问题。



接口请求完整步骤

pro 注册

再开始之前,我们需要在项目的 pro 文件内添加网络库


即 QT += core gui network



配置头文件

此处我们需要请求一个天气接口,该接口免费且无需 token 直接使用


天气接口 API: http://t.weather.itboy.net/api/weather/city/101010100

(接口 URL 最末尾的一串数字为城市代码,大家可以上网搜,我这给出的代码是北京的代码)



需要在头文件内定义一些关键性的内容


代码清单 MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QMainWindow>

#include <QMessageBox>


// 导入必备的两个网络操作库

#include <QNetworkAccessManager>

#include <QNetworkReply>


QT_BEGIN_NAMESPACE

namespace Ui { class MainWindow; }

QT_END_NAMESPACE


class MainWindow : public QMainWindow

{

    Q_OBJECT


public:

    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();


private:

    Ui::MainWindow *ui;


protected:

    // 获取指定城市代码天气信息的函数

    void getWeatherInfo(QString cityCode);


private:

    // 当接收到接口传回的响应时,对应的处理函数

    void onReplied(QNetworkReply *reply);


private:

    // 网络操作对象,用于执行GET或者PUT等基本网络请求

    QNetworkAccessManager *networkManager;

};

#endif // MAINWINDOW_H



主体文件

接下来的代码均写在 mainwindow.cpp 里面去


首先应当在构造函数内执行网络管理对象的初始化


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    // 实例化网络管理对象

    networkManager = new QNetworkAccessManager(this);


    // 配置当接收响应时(QNetworkAccessManager::finished),触发的回调函数onReplied

    connect(networkManager,&QNetworkAccessManager::finished,this,&MainWindow::onReplied);


    // 设置我们需要获取的天气的城市代码

    getWeatherInfo("101010100");

}



看一下获取城市天气信息函数的实现


void MainWindow::getWeatherInfo(QString cityCode)

{

    // 配置接口API

    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);

    // 直接使用GET请求

    networkManager->get(QNetworkRequest(url));

}


对应的响应回调函数的实现


void MainWindow::onReplied(QNetworkReply *reply)

{

    qDebug() << "请求成功!!!"; // 输出请求成功的信息


    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // 获取响应状态码


    qDebug() << "状态码:" << statusCode; // 输出状态码

    qDebug() << "URL:" << reply->url(); // 输出请求的URL

    qDebug() << "操作:" << reply->operation(); // 输出操作类型

    qDebug() << "原始头信息:" << reply->rawHeaderList(); // 输出原始头信息


    if(reply->error()!=QNetworkReply::NoError||statusCode!=200){ // 如果响应出现错误或状态码不为200

        qDebug() << reply->errorString().toLatin1().data(); // 输出错误信息

        QMessageBox::critical(this,"错误","无法请求接口,请检查网络是否连接正确",QMessageBox::Ok); // 弹出错误提示框

    }else{ // 如果响应正常

        QByteArray buff = reply->readAll(); // 读取响应数据

        qDebug() << buff.data(); // 输出响应数据

    }


    reply->deleteLater(); // 释放reply对象

}



此时运行程序,就会发现我们成功的请求了接口,并且获取到了对应数据


下面展示的即为接口回馈的 JSON 信息


{

"message": "success感谢又拍云(upyun.com)提供CDN赞助",

"status": 200,

"date": "20230710",

"time": "2023-07-10 07:42:05",

"cityInfo": {

"city": "北京市",

"citykey": "101010100",

"parent": "北京",

"updateTime": "07:31"

},

"data": {

"shidu": "53%",

"pm25": 12.0,

"pm10": 37.0,

"quality": "优",

"wendu": "29",

"ganmao": "各类人群可自由活动",

"forecast": [

{

"date": "10",

"high": "高温 39℃",

"low": "低温 27℃",

"ymd": "2023-07-10",

"week": "星期一",

"sunrise": "04:55",

"sunset": "19:44",

"aqi": 91,

"fx": "东北风",

"fl": "2级",

"type": "多云",

"notice": "阴晴之间,谨防紫外线侵扰"

},

{

"date": "11",

"high": "高温 35℃",

"low": "低温 28℃",

"ymd": "2023-07-11",

"week": "星期二",

"sunrise": "04:55",

"sunset": "19:44",

"aqi": 97,

"fx": "东南风",

"fl": "2级",

"type": "多云",

"notice": "阴晴之间,谨防紫外线侵扰"

},

{

"date": "12",

"high": "高温 31℃",

"low": "低温 25℃",

"ymd": "2023-07-12",

"week": "星期三",

"sunrise": "04:56",

"sunset": "19:43",

"aqi": 78,

"fx": "东风",

"fl": "1级",

"type": "中雨",

"notice": "记得随身携带雨伞哦"

},

{

"date": "13",

"high": "高温 34℃",

"low": "低温 24℃",

"ymd": "2023-07-13",

"week": "星期四",

"sunrise": "04:57",

"sunset": "19:43",

"aqi": 91,

"fx": "西南风",

"fl": "2级",

"type": "小雨",

"notice": "雨虽小,注意保暖别感冒"

},

{

"date": "14",

"high": "高温 34℃",

"low": "低温 24℃",

"ymd": "2023-07-14",

"week": "星期五",

"sunrise": "04:57",

"sunset": "19:43",

"aqi": 98,

"fx": "西北风",

"fl": "3级",

"type": "晴",

"notice": "愿你拥有比阳光明媚的心情"

},

{

"date": "15",

"high": "高温 35℃",

"low": "低温 25℃",

"ymd": "2023-07-15",

"week": "星期六",

"sunrise": "04:58",

"sunset": "19:42",

"aqi": 69,

"fx": "西北风",

"fl": "3级",

"type": "晴",

"notice": "愿你拥有比阳光明媚的心情"

},

{

"date": "16",

"high": "高温 36℃",

"low": "低温 22℃",

"ymd": "2023-07-16",

"week": "星期日",

"sunrise": "04:59",

"sunset": "19:41",

"aqi": 60,

"fx": "东北风",

"fl": "2级",

"type": "晴",

"notice": "愿你拥有比阳光明媚的心情"

},

{

"date": "17",

"high": "高温 37℃",

"low": "低温 23℃",

"ymd": "2023-07-17",

"week": "星期一",

"sunrise": "05:00",

"sunset": "19:41",

"aqi": 59,

"fx": "东南风",

"fl": "2级",

"type": "晴",

"notice": "愿你拥有比阳光明媚的心情"

},

{

"date": "18",

"high": "高温 39℃",

"low": "低温 25℃",

"ymd": "2023-07-18",

"week": "星期二",

"sunrise": "05:01",

"sunset": "19:40",

"aqi": 85,

"fx": "东风",

"fl": "2级",

"type": "小雨",

"notice": "雨虽小,注意保暖别感冒"

},

{

"date": "19",

"high": "高温 38℃",

"low": "低温 24℃",

"ymd": "2023-07-19",

"week": "星期三",

"sunrise": "05:01",

"sunset": "19:40",

"aqi": 77,

"fx": "东风",

"fl": "2级",

"type": "晴",

"notice": "愿你拥有比阳光明媚的心情"

},

{

"date": "20",

"high": "高温 39℃",

"low": "低温 25℃",

"ymd": "2023-07-20",

"week": "星期四",

"sunrise": "05:02",

"sunset": "19:39",

"aqi": 54,

"fx": "东南风",

"fl": "2级",

"type": "小雨",

"notice": "雨虽小,注意保暖别感冒"

},

{

"date": "21",

"high": "高温 36℃",

"low": "低温 23℃",

"ymd": "2023-07-21",

"week": "星期五",

"sunrise": "05:03",

"sunset": "19:38",

"aqi": 65,

"fx": "东南风",

"fl": "2级",

"type": "多云",

"notice": "阴晴之间,谨防紫外线侵扰"

},

{

"date": "22",

"high": "高温 38℃",

"low": "低温 25℃",

"ymd": "2023-07-22",

"week": "星期六",

"sunrise": "05:04",

"sunset": "19:37",

"aqi": 47,

"fx": "东风",

"fl": "2级",

"type": "中雨",

"notice": "记得随身携带雨伞哦"

},

{

"date": "23",

"high": "高温 38℃",

"low": "低温 24℃",

"ymd": "2023-07-23",

"week": "星期日",

"sunrise": "05:05",

"sunset": "19:37",

"aqi": 49,

"fx": "北风",

"fl": "2级",

"type": "暴雨",

"notice": "关好门窗,外出避开低洼地"

},

{

"date": "24",

"high": "高温 30℃",

"low": "低温 21℃",

"ymd": "2023-07-24",

"week": "星期一",

"sunrise": "05:06",

"sunset": "19:36",

"aqi": 46,

"fx": "东北风",

"fl": "1级",

"type": "中雨",

"notice": "记得随身携带雨伞哦"

}

],

"yesterday": {

"date": "09",

"high": "高温 38℃",

"low": "低温 25℃",

"ymd": "2023-07-09",

"week": "星期日",

"sunrise": "04:54",

"sunset": "19:45",

"aqi": 101,

"fx": "西北风",

"fl": "2级",

"type": "多云",

"notice": "阴晴之间,谨防紫外线侵扰"

}

}

}


完整代码结构

头文件 MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QMainWindow>

#include <QMessageBox>


#include <QNetworkAccessManager>

#include <QNetworkReply>


QT_BEGIN_NAMESPACE

namespace Ui { class MainWindow; }

QT_END_NAMESPACE


class MainWindow : public QMainWindow

{

    Q_OBJECT


public:

    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();


private:

    Ui::MainWindow *ui;


protected:

    void getWeatherInfo(QString cityCode);


private:

    void onReplied(QNetworkReply *reply);


private:

    QNetworkAccessManager *networkManager;

};

#endif // MAINWINDOW_H




主文件 MainWindow.cpp


#include "MainWindow.h"

#include "ui_MainWindow.h"


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

    , ui(new Ui::MainWindow)

{

    ui->setupUi(this);


    networkManager = new QNetworkAccessManager(this);

    connect(networkManager,&QNetworkAccessManager::finished,this,&MainWindow::onReplied);


    getWeatherInfo("101010100");

}


MainWindow::~MainWindow()

{

    delete ui;

}


void MainWindow::getWeatherInfo(QString cityCode)

{

    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);

    networkManager->get(QNetworkRequest(url));

}


void MainWindow::onReplied(QNetworkReply *reply)

{

    qDebug() << "request successfully!!!";


    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();


    qDebug() << "status code:" << statusCode;

    qDebug() << "url:" << reply->url();

    qDebug() << "operation:" << reply->operation();

    qDebug() << "raw header:" << reply->rawHeaderList();


    if(reply->error()!=QNetworkReply::NoError||statusCode!=200){

        qDebug() << reply->errorString().toLatin1().data();

        QMessageBox::critical(this,"错误","无法请求接口,请检查网络是否连接正确",QMessageBox::Ok);

    }else{

        QByteArray buff = reply->readAll();

        qDebug() << buff.data();

    }


    reply->deleteLater();

}


Qt资源文件相关


QT之qss教程-qss文件使用方法

1、先创建一个style.qss文件,如:


QPushButton {

    /* 前景色 */

    color:red;


    /* 背景色 */

    background-color:rgb(30,75,10);

}


2、把qss文件加入到工程资源里,如:

1.png

3、在代码里调用qss文件,如:


    QFile file(":/qss/style.qss");

    file.open(QFile::ReadOnly);

    QString styleSheet = tr(file.readAll());

    this->setStyleSheet(styleSheet);

    file.close();


QT皮肤(QSS)编程

借用css 的灵感, Qt也支持Qt自己的css, 简称qss。同css 相似,qss的主要功能与最终目的都是能使界面的表现与界面的元素分离,即质与形的分离,就如同一个人可以在不同的时候穿上不同的衣服一样,css机制的引入,使得设计一种皮肤与界面控件分离的软件成为可能,应用程序也能像web界面那样随意地改变外观。


一、QSS语法


同css一样,他也有由一个selector与一个declaration组成,selector指定了是对哪一个控件产生效果,而declaration才是真正的产生作用的语句。如:

QPushButton { color: red }

QPushButton指定了是对所有的QPushButton或是其子类控件(如用户定义的MyPushButton)产生影响,而color:red表明所有的受影响控件的前景色都为red。

除了“类名”,“对象名”,“Qt属性名”这三样东西是大小写敏感的外其他的东西都是大小写不敏感的,如color与Color代表同一属性。

如果有几个selector指定了相同的declaration, 可以使用逗号(,)将各个选择器分开,如:

QPushButton, QLineEdit, QComboBox { color: red }

他相当于:

QPushButton { color: red }

QLineEdit { color: red }

QComboBox { color: red }

declaration部份是一系列的(属性:值)对,使用分号(;)将各个不同的属性值对分开,使用大括号({})将所有declaration包含在一起。


1、 一般选择器(selector)


Qt支持所有的CSS2定义的选择器,其祥细内容可以在w3c的网站上查找http://www.w3.org/TR/CSS2/selector.html , 其中比较常用的selector类型有:



通用类型选择器:* 会对所有控件有效果。

类型选择器:QPushButton匹配所有QPushButton的实例和其子类的实例。

属性选择器:QPushButton[flat=”false”]匹配所有QPushButton属性flat为false的实例,属性分为两种,静态的和动态的,静态属性可以通过Q_PROPERTY() 来指定,来动态属性可以使用setProperty来指定,如:

QLineEdit *nameEdit = new QLineEdit(this);

nameEdit->setProperty("mandatoryField", true);

如果在设置了qss后Qt属性改变了,需要重新设置qss来使其生效,可以使用先unset再set qss。

类选择器:.QPushButton匹配所有QPushButton的实例,但不包含其子类,这相当于:*[class~="QPushButton"]     ~=的意思是测试一个QStringList类型的属性是否包含给定的QString

ID选择器:QPushButton#okButton对应Qt里面的object name设置,使用这条CSS之前要先设置对应控件的object name为okButton,如:Ok->setObjectName(tr(“okButton”));

后裔选择器:QDialog QPushButton匹配所有为QDialog后裔(包含儿子,与儿子的儿子的递归)为QPushButton的实例

子选择器:QDialog > QPushButton匹配所有的QDialog直接子类QPushButton的实例,不包含儿子的儿子的递归。


2、子控件选择器


对于复杂的控件,可能会在其中包含其他子控件,如一个QComboxBox中有一个drop-down的按钮。那么现在如果要设置QComboxBox的下拉按钮的话,就可以这样访问:

QComboBox::drop-down { image: url(dropdown.png) }其标志是(::)

子控件选择器是用位置的引用来代表一个元素,这个元素可以是一个单一控件或是另一个包含子控件的复合控件。使用subcontrol-origin属性可以改变子控件的默认放置位置,如:

QComboBox {

       margin-right: 20px;

}

QComboBox::drop-down {

       subcontrol-origin: margin;

}


如上语句可以用来改变drop-down的margin。

相对位置属性可以用来改变子控件相对于最初位置的偏移量,如当一个QCombox的drop-down按钮被按下时,我们可以用一个内部的小偏移量来表示被按下的效果,如下:

QComboBox::down-arrow {

       image: url(down_arrow.png);

}

QComboBox::down-arrow:pressed {

       position: relative;

       top: 1px; left: 1px;

}

绝对位置属性允许子控件改变自己的位置和大小而不受引用元素的控件。一但位置被设定了,这些子控件就可以被当成一般的widget来对待,并且可以使用合模型。关于合模型可以参考下面。

如果你要查看Qt支持哪些子控件选择器,可以参考:http://pepper.troll.no/s60prereleases/doc/stylesheet-reference.html#list-of-sub-controls-syntax.html

3、伪选择器(pseudo-states)


伪选择器以冒号(:)表示,与css里的伪选择器相似,是基于控件的一些基本状态来限定程序的规则,如hover规则表示鼠标经过控件时的状态,而press表示按下按钮时的状态。如:

QPushButton:hover {

       Background-color:red;

}

表示鼠标经过时QPushButton背景变红。

Pseudo还支持否定符号(!),如:

QRadioButton:!hover { color: red }

表示没有鼠标移上QRadioButton时他显示的颜色是red。

Pseudo可以被串连在一起,比如:

QPushButton:hover:!pressed { color: blue; }

表示QPushButton在鼠标移上却没有点击时显示blue字,但如果点击的时候就不会显示blue颜色了。

同样可以和之前所讲的子控件选择器一起联合使用,如:

QSpinBox::down-button:hover { image: url(btn-combobox-press.bmp) }

与前面所讲的一样,伪选择器,子控件选择器等都是可以用逗号(,)分隔表示连续相同的设置的,如:

QPushButton:hover,QSpinBox::down-button, QCheckBox:checked { color: white ;image: url(btn-combobox-press.bmp);} 表示如下

更多请参考:http://pepper.troll.no/s60prereleases/doc/stylesheet-reference.html#list-of-pseudo-states


二、 解决冲突


使用object name

在程序里面要先设置控件的,如:

btnOne = new QPushButton(centralWidget);

btnOne->setObjectName(QString::fromUtf8("btnOneCh"));

这样,我们有了一个object name为btnOneCh的QPushButton,试验一下,使用指定object name的方式,如:

QPushButton#btnOneCh { color: red }

QPushButton { color: white }

可以看出,btnOncCh的color变成了red

使用伪选择器,如hover,press,enabled等,如:按扭“1”是disable了的,QPushButton:!enabled {color: white}

所有的类型选择器都有一个共同的特性,就是如果有两个属性冲突了的话就会以最后出现的一个为准,如:

QPushButton { color: red }

QAbstractButton { color: gray }

由于QPushButton为QAbstractButton的子类,如果只设置QAbstractButton的可以想像结果是所有的QPushButton都为gray, 如果只设置QPushButton的所有QPushButton都会为red,当两个都能设置起效的时候,以在文本上最后出现的为准,所以结果为Grey

当然其中如果有#指定了object name,他所设置的优先级是最大的,具体规则可以参考:http://www.w3.org/TR/CSS2/cascade.html#specificity,或是http://pepper.troll.no/s60prereleases/doc/stylesheet-syntax.html#conflict-resolution

QPushButton#btnOneCh { color: red }

QPushButton { color: blue }

QAbstractButton { color: gray }

虽然QAbstractButton在最后,但是之前有#btnOneCh指定了QPushButton“一”的color为red所以最后显示也是“一”为red。


三、级联效应


子类可以继承父类的StyleSheet,但是如果子类里面设置了StyleSheet与父类里在设置的有冲突,那么当然会优先考虑子类自己的,

同样,如果在qApp时面设置了,但是在某一个特定控件里面也设置,如果有冲突,也是优先控件自己的,例如,我在程序时面设置了:btnOneEn->setStyleSheet("QPushButton { color: red }");

而,当我再设置qApp时,如果,将QPushButton的color设置成grey的,那么结果是对于btnOneEn这个QPushButton来说他的颜色还是red。

这就是为什么这里设置为grey了btnOneEn却还是red的。

如果我们对一个控件设置StyleSheet为:

QPushButton* myPushButton;

myPushButton->setStyleSheet("* { color: blue }");

其实他和设置为:myPushButton->setStyleSheet("color: blue");

效果相同,只是后一种设置不会对QPushButton的子类产生作用,但第一种却会。


四、继承性


与CSS不同的一点,在CSS box模型中,如果一个元素在别一元素的里面,那么里面的元素会自动继承外面元素的属性,但QSS里面不会,如:

一个QPushButton如果放在一个QGroupBox里面,如果:qApp->setStyleSheet("QGroupBox { color: red; } ");

并不代表在QGroupBox里面的QPushButton也会有color:red的属性,如果要想有的话要显示写明,如:qApp->setStyleSheet("QGroupBox, QGroupBox * { color: red; }");

或者在应用程序里面也可以用QWidget::setFont等来设置到子控件的属性。


五、Namespace冲突


类型选择器能够使用到一个特定的类型,如:

class MyPushButton : public QPushButton {

      // ...

}

qApp->setStyleSheet("MyPushButton { background: yellow; }");


因为QSS使用QObject::className来判断要赋与style sheet的控件类型,如果一个用户定义控件类型在一个namespace里面的话,QObject::className会返回<namespace>::<classname> 的名字,这和子控件选择器的语法相冲突,为了解决此问题,使用“--”来代替“::”,比如:

namespace ns {

      class MyPushButton : public QPushButton {

          // ...

      }

}

qApp->setSytleSheet("ns--MyPushButton { background: yellow; }");


六、设置对像属性


如果在程序里面使用Q_PROPERTY设置的属性,可以在qss里面使用:qproperty-<property name>的形式来访问并设置值。如:

MyLabel { qproperty-pixmap: url(pixmap.png); }

MyGroupBox { qproperty-titleColor: rgb(100, 200, 100); }

QPushButton { qproperty-iconSize: 20px 20px; }

如果属性引用到的是一个由Q_ENUMS申明的enum 时,要引用其属性名字要用定义的名称而不是数字。



QT qss界面美化总结

什么是QSS

QSS称为Qt Style Sheets也就是Qt样式表,它是Qt提供的一种用来自定义控件外观的机制。QSS大量参考了CSS的内容,只不过QSS的功能比CSS要弱很多,体现在选择器要少,可以使用的QSS属性也要少很多,并且并不是所有的属性都可以用在Qt的所有控件上。


回到顶部

QSS在Qt程序中的使用办法

首先将QSS写在文件中,然后利用如下的代码设置QSS:


MainWidget::MainWidget(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::MainWidget)

{

  //应用样式 apply the qss style

    QFile file(":/qss/main.qss");

    file.open(QFile::ReadOnly);

    QTextStream filetext(&file);

    QString stylesheet = filetext.readAll();

    this->setStyleSheet(stylesheet);

   file.close();

}

该段代码写在ui界面的后台cpp文件的构造函数中,主要是this->setStyleSheet()函数的设置功能,要说明的是该函数除了可以对整个当前构造数所在的那个类所表示的ui进行整体应用样式以外,setStyleSheet()函数本身是QWidget的成员函数,几乎Qt中的大多数控件都可以直接使用该函数分别设置自己的样式。


回到顶部

QSS的语法规则

QSS的语法规则几乎与CSS相同。一条QSS的样式是由两部分组成的,一部分是选择器指定了哪些控件会受到影响,另一部分是指定了属性的值,表示这些控件的哪些属性会受到影响。例如:


QPushButton { color: red }

QPushButton表示选择器,指定了所有的QPushButton或者是QPushButton的子类会受到影响,注意凡是继承自QPushButton的子类也会受到影响,这是与CSS中不同的地方,因为CSS应用的都是一些标签,没有类的层次结构,更加没有子类的概念。而后面的{color:red}则是规则的定义,表明指定前景颜色是红色。整个意思就是设置QPushButton类以及其子类的所有实例的前景色是红色。


如果MyButton继承自QPushButton,那么上面的规则也会应用到所有MyButton控件上,但是如果规则是如下的:


MyButton{color:red} 

则只会对MyButton的实例应用红色的前景颜色,而对QPushButton的实例没有应用。


回到顶部

QSS的选择器类型

1.通配选择器:*  ; 匹配所有的控件

2.类型选择器:QPushButton ; 匹配所有QPushButton和其子类的实例

3.属性选择器:QPushButton[flat="false"]; 匹配所有flat属性是false的QPushButton实例,注意该属性可以是自定义的属性,不一定非要是类本身具有的属性

4.类选择器:  .QPushButton ;  匹配所有QPushButton的实例,但是并不匹配其子类。这是与CSS中的类选择器不一样的地方,注意前面有一个点号

5.ID选择器:  #myButton; 匹配所有id为myButton的控件实例,这里的id实际上就是objectName指定的值

6.后代选择器: QDialog QPushButton ; 所有QDialog容器中包含的QPushButton,不管是直接的还是间接的

7.子选择器:  QDialog > QPushButton; 所有QDialog容器下面的QPushButton,其中要求QPushButton的直接父容器是QDialog


另外上面所有的这些选择器可以联合使用,并且支持一次设置多个选择器类型,用逗号隔开,这点与CSS一样,例如#frameCut,#frameInterrupt,#frameJoin 表示所有这些id使用一个规则。#mytable  QPushButton 表示选择所有id为mytable的容器下面的QPushButton实例


回到顶部

QSS子控件

QSS的子控件实际上也是选择器的一种,因为这种选择器与CSS有一些不同,所以单独拿出来说,QSS的子控件选择器是应用在一些复合控件上的,典型的例如QComboBox,该控件的外观包含几个部分,一般情况下有一个矩形的外边框,右边有一个向下的箭头用于提示点击之后会有弹出下拉列表。例如:


QComboBox::drop-down { image: url(dropdown.png) }

上面的样式指定所有QComboBox的下拉箭头的图片为自定义的图片dropdown.png

::dropdown子控件选择器也可以与上面提到的选择器一起联合使用:


QComboBox#myQComboBox::drop-down { image: url(dropdown.png) }

指定id名为myQComboBox的QComboBox控件的下拉箭头的自定义图片,要注意的是子控件选择器实际上是选择复合控件中的一部分,也就是说对复合控件中的一部分应用样式,例如为QComboBox的下拉箭头指定图片,而不是为QComboBox本身指定图片。


QSS为很多复杂的复合控件提供了子控件的定义,以方便对这些复合控件的各个部分进行样式设置。限于篇幅,本文也不能将这些可用的子控件都列出来,在安装QtCreator之后自带的帮助中就有很详细的描述。


回到顶部

QSS伪状态

QSS的伪状态选择器实际上与CSS中的类似,是以冒号开头的一个选择表达式,例如:hover表示当鼠标经过时候的状态。他限制了当控件在某一种状态下的时候才能应用QSS规则,伪状态只能描述一个控件的状态,或者是一个复合控件中的子控件的状态,所以该伪状态选择器只能放在选择器的最后面,例如:


QComboBox:hover{background-color:red;}

该规则表示当鼠标经过QComboBox上面的时候,其背景颜色指定为红色,该伪状态 :hover描述的是QComboBox的状态。

伪状态除了描述选择器选择的控件以外,还可以描述子控件选择器所选择的复合控件中的子控件的状态,例如:


QComboBox::drop-down:hover{background-color:red;}

表示当鼠标经过QComboBox的下拉箭头的时候,该下拉箭头的背景颜色变成红色。

此外伪状态可以用一个感叹号表示否,例如:hover表示鼠标经过,而:!hover表示鼠标没有经过的状态。几个伪状态可以同时一起使用,例如:


QCheckBox:hover:checked { color: white }

指定一个当鼠标经过一个选中的QCheckBox的时候,设置其文字的前景颜色为白色。

QSS提供了很多的伪状态,一些伪状态只能用在特定的控件上,具体有哪些伪状态,在Qt的帮助里面有详细的列表,限于篇幅这里也不列出了。


回到顶部

QSS级联与冲突

QSS中的级联包含几个方面的概念,一个是当在同一个控件上应用两个不同的规则,那么应该应用哪一个规则的问题,也就是如何解决这种冲突。二个是在一个容器控件上设置的QSS规则会对容器里面的控件产生效果(这要取决于容器控件上设置的QSS规则是什么样的规则,如果容器控件上设置的QSS规则仅仅针对容器控件本身则该规则对子控件没有影响,如果该QSS规则里面有对子控件的设置,则自然会对子控件产生效果),级联问题是解决当一个控件被层层父容器包裹,并且在每一层的父容器上都有对该控件的样式设置的时候,该控件的最终效果是合并这些父容器上的QSS效果。


冲突问题:


QPushButton#okButton { color: gray }

QPushButton { color: red }

这两条规则都会应用到名为okButton的按钮上,但是他们为同一个属性设置了不同的颜色,这会有冲突,那么要解决这样的冲突就必须考虑到选择器的特异性(这个词怎么理解,我理解为这个特异性为更加特殊,实际上CSS上用权重表示这里的特异性),显然QPushButton#okButton仅仅针对对象名为okButton的控件有效果,而QPushButton却对所有的QPushButton的实例或者是其子类的实例有效果,显然QPushButton#okButton选择器更加特殊,也就是更具有特异性。所以最终okButton前景色被应用为灰色。如果两条规则的特异性一样,那么就选择放在后面的那一条。


另外如果一个选择器应用了伪状态,而另一个没有,那么应用了伪状态的选择器要更加特殊,例如:


QPushButton:hover { color: white }

QPushButton { color: red }

显然QPushButton:hover比单纯的QPushButton更加具有特异性。这两条规则表示当鼠标放在按钮上的时候文字是白色,其他情况下都是红色。

如下面两个规则的特异性是一样的,那么应该是如何应用呢:


QPushButton:hover { color: white }  //如果鼠标经过则前景白色

QPushButton:enabled { color: red }  //如果按钮是enabled状态则前景红色

所以默认情况下前景文字是红色的,当鼠标经过的时候并不会变成白色,因为他们的特异性是一样的,所以选择后面的,也就是红色。

那么换一下顺序会怎样呢:



QPushButton:enabled { color: red }  //如果按钮是enabled状态则前景红色

QPushButton:hover { color: white }  //如果鼠标经过则前景白色

当鼠标经过的时候,就变成白色的了,因为他们的特异性一样,所以选择后面的规则,也就是鼠标经过前景变成白色。

如果把其中的一条的特异性增加,例如:



QPushButton:hover:enabled { color: white }

QPushButton:enabled { color: red }

那么第一条的特异性比第二条大,所以应用第一条的规则。

另外一种特异性发生在类型选择器上:



QPushButton { color: red }   //应用在所有的QPushButton上

QAbstractButton { color: gray } //应用在所有的QAbstractButton上

而在类的继承结构上,QAbstractButton是QPushButton的父类,显然QPushButton更加具有特异性,所以QPushButton的前景颜色被应用为红色,而不是灰色。有没有一个办法来确定两条QSS规则的特异性大小呢,其实QSS使用的特异性的计算方法与CSS是一样的,详细可以参考CSS2的文档规范,这里还是简要的说明一下,特异性这个东西在CSS中一般被称为权重,权重越大的越优先使用,CSS的计算规则如下:


1.计算一条规则中id选择器的个数,假设存放在变量a中

2.计算一条规则中类选择器和属性选择器的个数,存放在变量b中

3.计算一条规则中的类型选择器的个数,存放在变量c中

4.忽略伪元素,对应QSS中的子控件


下面是具体的计算方法:



*             {}  /* a=0 b=0 c=0 -> specificity =   0 */

LI            {}  /* a=0 b=0 c=1 -> specificity =   1 */

UL LI         {}  /* a=0 b=0 c=2 -> specificity =   2 */

UL OL+LI      {}  /* a=0 b=0 c=3 -> specificity =   3 */

H1 + *[REL=up] {}  /* a=0 b=1 c=1 -> specificity =  11 */

UL OL LI.red   {}  /* a=0 b=1 c=3 -> specificity =  13 */

LI.red.level    {}  /* a=0 b=2 c=1 -> specificity =  21 */

#x34y        {}  /* a=1 b=0 c=0 -> specificity = 100 */

上面的计算规则是CSS的计算规则,同样可以应用的QSS上。


关于级联:



QSS可以设置在QApplication上,也可以设置在一个部件的容器部件上,也可以设置在子孙部件上,一个部件最终使用的样式效果是合并了他的所有父容器,祖父容器等上面设置的所有样式的结果,这些设置会进行叠加。如果在级联过程中发生了冲突,例如部件本身指定的前景颜色为绿色,而其父亲容器为其指定的前景颜色为红色,此时就选择部件本身设置的样式效果。例如:



qApplication->setStyleSheet("QPushButton { color: white }");

myPushButton->setStyleSheet("* { color: blue }");

第一条语句表示在QApplication上设置QPushButton的样式,而第二条语句表示在myPushButton对象上设置样式。最终结果会将myPushButton的前景颜色设置为蓝色。级联效果主要应用在当一个控件的样式在多个容器控件上都有设置的时候,该控件的最终效果是这些所有容器控件上效果的合并。级联规则在CSS中本身也是一个复杂的主题,如果大家感兴趣,还是需要自己参考CSS2的规范文档,本节仅仅是抛装引玉。



另外一个要提到的是,QSS中似乎为父容器控件本身设置的样式,并不会被子控件继承,例如如果QFrame中有一个QPushButton控件,那么如下的语句:



ui->frame->setStyleSheet("QFrame{ color: red; border:1px solid red }");

该语句仅仅为QFrame设置前景颜色以及边框的效果并不会应用到其里面的QPushButton上,如果是下面的语句则都可以:



ui->frame->setStyleSheet("QPushButton{ color: red; border:1px solid red }");

ui->frame->setStyleSheet("*{ color: red; border:1px solid red }");

回到顶部

QSS实际应用中要注意的地方

在使用QSS的时候遇到过一些坑,看似简单,但是如果不知道的话,还是很折磨人的:

<1>使用QSS设置边框无效,例如:



border:1px solid red; //Ok

border:solid 1px red; //Error

border:red 1px solid; //Error

border:red solid 1px; //Error

设置边框颜色和像素的时候,必须是第一种顺序,而CSS中是无所谓的,至于原因,我也不清楚,就是这么坑人。


<2> QSS设置宽高无效:

在QSS中设置宽高必须要使用 min-width和min-height,max-width,max-height来设置,用width和height设置是没有任何效果的。


<3>QComboBox的样式设置的问题:

QcomboBox是一个复杂的控件,QComboBox由3部分组成,一个是QComboBox的外框,里面有一个下拉按钮,这个按钮可以通过QComboBox::drop-down 来控制其位置,将其定义到QComboBox的左边而不一定是右边。另外在这个下拉按钮上面一般会有一个向下的箭头,这个箭头图像也是可以定制的,通过QComboBox::down-arrow来指定箭头的图像。


如果要控制QComboBox的弹出下拉列表的样式需要通过:



QComboBox QAbstractItemView {

  //设置当点击下拉按钮之后弹出的下拉列表的样式,要注意的是这里的样式

  //仅仅只能设置弹出的整个下拉列表范围的矩形的样式,不能设置下拉列表

  //中的每一个下拉项的样式,例如不能设置每一个下拉项高度

}

QcomboBox{

  //设置未弹出下拉列表的样式

}

 

QComboBox QAbstractItemView::item {

//设置弹出下拉列表中的每一个下拉项的样式,这里的样式要想生效,必须先

//对QcomboBox做下面的设置

//QStyledItemDelegate* itemDelegate = new QStyledItemDelegate();

//combox->setItemDelegate(itemDelegate);   

}

回到顶部

Qt开发的程序的案例

前面一段时间用Qt开发了一个基于公司平台的视频导出与编辑工具,当作一个案例贴出来吧,还是有很多可以美化的地方,窗体的外框没有做任何美化处理,用QSS做起来其实还是蛮简单的,里面的页面按钮,文本框,分页功能等都用QSS设置过,其实这个软件的界面算是中规中矩的,要做成酷狗,360那种风格用QSS是完全没有问题的:

1.jpg


登录界面,里面用到了Qt提供的正则验证功能,如果输入的ip不符合规则,则根本输入不了,例如如果输入一个非法的字符,则文本框中没有反应


登录进去之后的主界面,一共有7个选项卡,每一个选项卡代表一个功能:


视频导出:导出web平台上的视频数据,包括文件,片头片尾,视频的索引,可以导出电影模式,资源模式,支持断点续传。

视频剪切:可以将视频中不需要的内容剪切掉

视频截取:截取视频中需要的部分

后期导播:对资源模式进行后期导播

创建片头片尾:傻瓜式的生成片头片尾的图片文件

合成片头片尾:将片头片尾的图片文件添加到一个视频的起始和结束

转码上传:将多种格式的视频文件转码成MP4文件并上传到web平台

1.jpg



视频导出功能,实际上是导出一个课件,该课件可能录制的有电影模式的视频,资源模式的视频,或者都有。功能介绍如下:

2.png



视频剪切对视频进行剪切处理:

3.png



对视频进行截取:


4.png


对资源模式进行后期导播:


5.jpg


创建自定义片头片尾(其实就是傻瓜式的让用户生成自定义图片),里面的文字可以拖动,可以键盘微调,可以修改:

6.jpg



合并片头片尾,将生成好的片头片尾图片,合并到一个mp4的开始和结尾处,可以指定片头片尾的图片文件,并指定在mp4开头或结尾显示的时间:


7.png


转码与上传:


8.png


程序里面的外观包括,下拉列表,文本输入框,按钮,表格,tab等都是使用QSS设置的。QSS参考了CSS,所有里面有一些属性是与CSS相同的,但并不是所有的CSS属性都能够在QSS中使用,也并不是所有的Qt控件都支持CSS的盒模型,Qt帮助中关于QSS的部分详细描述了可用的QSS属性列表,以及控件列表,伪状态列表,子控件列表,QSS中可用的图标列表,以及QSS属性值的单位列表,限于篇幅不可能将所有这些都详细列出来,其实在安装QtCreator之后的帮助文档中有详细的描述,其列表类似下面的图(描述了部分伪状态的说明):

9.png

Qt 之资源系统

简述
Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制。当你的程序总需要这样的一系列文件(图标、翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用。
资源系统基于 qmake、rcc(Qt 资源编译器) 和 QFile 之间的紧密合作。
版权声明:一去、二三里,未经博主允许不得转载。
资源集合文件(.qrc)
与程序相关的资源在被指定在一个 .qrc 文件中,其基于 XML 的文件格式列出了磁盘上的文件,可以为它们指定一个应用程序访问资源时必须使用的资源名称。
下面是一个 .qrc 文件的示例:
<!DOCTYPE RCC><RCC version="1.0"><qresource>    <file>images/copy.png</file>    <file>images/cut.png</file>    <file>images/new.png</file>    <file>images/open.png</file>    <file>images/paste.png</file>    <file>images/save.png</file></qresource></RCC>

.qrc 文件中列出的资源文件是程序代码树中的一部分。指定的路径相对于 .qrc 文件所在目录。注意:列出的资源文件必须位于 .qrc 文件所在目录或其子目录。
资源数据可以编译成二进制,进而在程序代码中立即访问;或者创建一个二进制资源,在晚些时候用资源系统来注册程序代码。
默认情况下,程序可以使用与代码树中相同的名字访问资源,需要带有 “:/” 前缀,或者有qrc scheme的URL。
<file alias="cut-img.png">images/cut.png</file>

之后,在程序中就可以使用 :/cut-img.png 访问此文件了。还可以使用 qresource 标签的前缀属性为 .qrc 文件中列出的所有文件指定路径前缀:
<qresource prefix="/myresources">    <file alias="cut-img.png">images/cut.png</file></qresource>

这种情况下,就可以使用 :/myresources/cut-img.png 访问该文件了。
有些资源可能需要随着用户本地的配置而改变,例如:翻译文件、图标,可以通过为 qresource 标签添加一个 lang 属性,并指定一个适当的本地字符串来完成。例如:
<qresource>    <file>cut.jpg</file></qresource><qresource lang="fr">    <file alias="cut.jpg">cut_fr.jpg</file></qresource>

如果用户的语言是法语(即:QLocale::system().name() 返回“fr_FR”),:/cut.jpg 就变成了对 cut_fr.jpg 文件的引用。如果是其它语言,仍然使用 cut.jpg 。
使用语言字符串的格式的说明可以参考 QLocale 文档。
外部二进制资源
要创建外部二进制资源,必须通过传递 -binary 给 rcc 来创建资源数据(通常使用 .rcc 扩展名),一旦二进制资源被创建,就可以使用 QResource API 注册该资源。
例如, .qrc 文件中指定的一系列资源数据可以用下面的方式编译:
rcc -binary myresource.qrc -o myresource.rcc

在程序里,需要用以下代码注册该资源:
QResource::registerResource("/path/to/myresource.rcc");

内编译资源
要把一个资源编译到二进制文件中, 必须在 .pro 中明确指定.qrc 文件,以便于 qmake 可以正确处理。例如:
RESOURCES     = application.qrc

qmake 会产生 make 规则来生成一个链接到程序中的名为 qrc_application.cpp 的文件。这个文件以静态的 C++ 压缩二进制数组包含了所有图片和其它资源的数据。当 qrc_application.cpp 本身或者是其引用的资源文件发生改变后,该文件都会被自动重新生成。如果你不使用 .pro 文件,那么可以手工调用 rcc 或者为 build 系统添加 build 规则。


1.png

目前,Qt 总是把资源数据存储在可执行文件中,甚至是在 Windows、Mac OS X 和 iOS 这些原生支持资源机制的操作系统中。在将来的 Qt release 版本中可能会改变。
压缩
默认情况下,资源是被压缩的(ZIP 格式),也可以关闭压缩。如果你的资源已经包含了一个压缩格式(例如: .png 文件),这可能会比较有用。可以通过命令行传递 -no-compress 来指定:
rcc -no-compress myresources.qrc

rcc 也在压缩上给你提供了一些控制,当压缩文件时,可以指定压缩级别和阈值水平时,例如:
rcc -compress 2 -threshold 3 myresources.qrc

在程序中使用资源
在程序中,资源路径在大多数情况可以代替一般的文件系统路径。特别是,可以用资源路径取代文件名传递给 QIcon、 QImage ,或者 QPixmap 的构造函数:
cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);

可以参考 Application 示例,了解更多关于应用程序使用 Qt 资源系统来存储图标的内容。
在内存中,资源由一个资源对象树来表示。此树在程序启动时被自动生成,并被 QFile 用来解析路径到资源。你可以使用带有 “:/” 前缀的 QDir 从根目录开始遍历这棵树。
Qt 资源系统支持搜索路径列表。如果你用 : 代替 :/ 作为前缀,则会使用搜索路径列表来搜索资源。程序启动时搜索路径列表为空,可以用 QDir::addSearchPath() 为其添加路径。
如果有资源位于静态库中,需要用不带有后缀的 .qrc 文件名为参数调用 Q_INIT_RESOURCE() 来强制初始化资源系统。例如:
在库中使用资源
如果有资源位于一个库中,需要用不带有后缀的 .qrc 文件名为参数调用 Q_INIT_RESOURCE() 来强制初始化资源系统。例如:
MyClass::MyClass() : BaseClass() {    Q_INIT_RESOURCE(resources);    QFile file(":/myfile.dat");    ...}


在静态链接的情况下,这确保了资源被链接到最终应用程序的二进制文件中。应该把初始化代码接近库中资源被使用的地方,这样以来,如果库的客户端用库的特性,它们只会链接进资源。
注意:由于 rcc 初始化资源生成在全局命名空间中声明,你也需要在任何命名空间之外的地方调用 Q_INIT_RESOURCE()。
如果库内部包含未使用的资源,但是暴漏给库的客户端,初始化需要发生在应用程序代码中,例如:
int main(int argc, char *argv[]) {    QApplication app(argc, argv);    Q_INIT_RESOURCE(graphlib);    QFile file(":/graph.png");    ...    return app.exec(); }

和之前一样,这确保在静态链接的情况下,资源被链接到最终应用程序的二进制文件中,也触发加载动态链,例如:插件。
同样的,你必须明确地卸载一个显式设置的资源(因为一个插件被卸载或资源不再有效),你可以强制删除资源通过调用 Q_CLEANUP_RESOURCE() 与上面相同的基本名称。
注意:当资源被构建为应用程序的一部分时,没有必要使用 Q_INIT_RESOURCE() 和Q_CLEANUP_RESOURCE()。



QT中资源文件的使用

1.在工程中点右键,选添加文件;

1.png

在下一页中输入一个资源文件名,如uires,这样工程树下就会出现资源文件夹。

2.png

 

2.右键,选择“Open in Editor”打开它

3.png

3.添加或修改前缀名,前缀的作用类似于文件夹;或添加文件

4.png

4.在按钮中使用,pbStart和pbEnd是界面中的两个按钮:

    QIcon sIcon(":/style/111.png");
    ui->pbStart->setIcon(sIcon);
    QIcon eIcon(":/style/error.png");
    ui->pbEnd->setIcon(eIcon);

 5.按钮效果

5.png

也可以使用资源别名

1.png
这样,我们可以直接使用:/images/doc-open引用到这个资源,无需关心图片的真实文件名。



QT 直接添加资源

直接qrc

.pro文件

RESOURCES += \

    gallery.qml \

    pages/BusyIndicatorPage.qml \

1.png

使用

QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());

    engine.load(QUrl("qrc:/gallery.qml"));



Qt中使用qrc管理和使用资源文件

qrc资源文件一直有点搞不清楚,特别是前缀,到底加什么,讲的不太清楚,我的项目也因此搞的很乱.现在总结一下.


1. 比如qss资源文件,我在Resources文件夹下新增一个qss.qrc文件,这个文件会自动添加到pro文件中(?我好像自己添加进去的).


RESOURCES += res.qrc


2. 在Resources文件夹再新建几个要用到的目录qss用于放置需要用到的qss文件如test.qss.


3. 打开qss.qrc文件,添加前缀 /qss, 再添加文件 qss/test.qss.


4. 在代码中使用如下代码即可使用相应的qss文件设置相应的qss.


    //set qss

    QFile file(":/qss/qss/test.qss");

    file.open(QFile::ReadOnly);


    setStyleSheet(file.readAll())




qt开发的APP发布到appstore

文章


客户要求app上线appstore,因为没用做过,一脸懵逼,不知从何下手,技术群问问技术大佬,也都是三言两语,虽然有人做过,但是人家大佬根本不想鸟你,没办法了只能自己慢慢莫搜了。
 

写这篇博客就是为了以后有相关需求的人避免走弯路或者踩坑了,我从头到尾踩了一次,我把整个流程大概讲一遍,有坑的地方我会提出来。一开始因为不懂,拿到账号后,总是乱玩一通、各种创建删除、弄个xcode的app测试发布一下,结果悲剧了,有些东西会删除不掉的,比如那个appid我测试的数据都删除不了,因为被使用过,感觉是bug,具体也不去纠结了,还有就是在app store connect创建app的时候,使用过的app名字后面就没有办法在使用了,我就是测试的时候使用真实的app名字,导致后面我真的要发布的时候用不了这个名字,就算把测试的app删除了 ,还是用不了太坑了,被迫改了app名字。后面在签名的时候各种问题,感觉就是我测试的时候各种创建删除导致的信息错乱,最后把所有东西都删除了,只剩下测试的appid删除不了,全部都重新创建一套,包括:证书、appid、描述文件、app等一一对应的创建一套。总结就是,没事别瞎玩,完整的创建一套要使用的数据,没必要去测试发布demo。讲的有点多,下面进入主题。

第一步:你已经有了一个qt开发好的macos app,可以正常发给客户或者其他电脑上安装使用的app,一般是dmg或者pkg包。
 

第二步:注册一个苹果开发者账号,这个具体流程可以网上找一找,有个人、公司、企业三种账号,我注册的是公司的苹果开发者账号,也是第一次弄,前前后后差不多一个月的时间,终于注册申请好了苹果开发者账号。

第三步:登入开发者中心(https://developer.apple.com/),

主要操作都在我框出来的二级页面里面进行操作,主要有四个东西:

1、Certificates(证书)(开发证书、发布证书、安装证书(这个好像是macos app才需要的,ios app 不需要安装证书))、根据app类型进行添加证书,我创建Mac App Distribution、Mac Installer Distribution、Mac Development三个证书,其实qt开发的app根本就不需要用到开发证书,是给xcode开发的时候使用的,因为使用其他证书签名的程序在本地是运行不了,创建证书是为了后面的app签名用的,创建好下载安装到macos系统,双击下载文件就可以安装了,安装后打开系统工具钥匙串就可以看见安装的开发者证书了(具体这么创建证书可以百度下,我就不过于详细的描述了,比较签到)。

2、Identifiers(APPID),这个要注意下,一个appid可以对应创建一个app,后面在app store connect里面创建app的时候需要用到,一个app对于创建一个appid,这个id将会绑定描述文件个app,后面app的info.plist里面还要设置这个对于的appid,key叫做CFBundleIdentifier。

3、Profiles(描述文件),有开发和发布两种,qt app一般只需要创建发布的描述文件即可,创建的时候要选择上面创建的appid,进行绑定。

4. 进入app store connect创建app,最后app信息设置、提交审核、发布等都是在这边操作的,创建app的时候要选择appid 进行绑定,一个appid自能创建一个app,,先创建好,能填写的信息先填写好,剩下的就是,签名app,打包上传到app store connect对应的app上面,因为app上面绑定了appid,上传的时候会自己上传到对应的app的构建版本下面,到时候进行选择就好了。

上述准备都是在开发者中心进行设置的,下面回到代码上的处理,首先用qt编译好需要发布的app,拷贝好对应的依赖库和第三方库等(这个涉及到另外一个东西,macos打包app,个人感觉还是比较麻烦的,需要写一个脚本去执行拷贝依赖和修改依赖路径等)。

5、到了本文核心部分了,那就是给app签名,网上搜索到的都是使用xcode对项目进行自动签名的,这个就比较好操作,我也尝试过将qt项目转成xcode项目进行编译,使用自动签名,经过一番努力修改,还真的使用xcode把项目编译成功了,但是需要依赖qt库和其他你用到的第三方库,如果你编译的时候选择自动签名,可以编译成功,但是你的依赖库还没有拷贝过来,app无法运行,等你把依赖库都拷贝到app里面后,这些刚刚拷贝过来的库都是没有签名的,因为xcode签名的时候在编译的时候就完成了,这就导致了程序还是无法运行,折腾一番之后我又发现了另外一个方法,可以手动用命令对app进行签名,比较使用qt开发的还是选择手动签名会比较好管理整个项目,使用xcode应该也是可以实现的,后面那个问题,应该是可以设置xcode,使其在编译的时候就把依赖库都自动拷贝过来进行签名的,后面我也没有再去研究,有兴趣的可以自行研究。

6、回到正题,上述已经准备好了可以运行的qt编译的app,签名的时候还需要一个.entitlements文件,这个类似info.plist,也是配置app权限信息的,使用xcode创建一个macos项目就会生成一个.entitlements文件,根据自己app的需求进行设置,然后把这个.entitlements文件直接拷贝出来使用。

7、对info.plist添加必要的信息,之前因为信息不全审核的时候被打回来好几次,

CFBundleName :app的名字,需要跟app store connect上面创建的app名字一致

CFBundleVersion:大版本号(1)

CFBundleShortVersionString:完整版本号(1.0.1)

CFBundleIdentifier:appid(在开发者中心创建的那个appid,不能写错了)

LSApplicationCategoryType:app类,要选择一个app分类

ITSAppUsesNonExemptEncryption:这个好像时什么加密协议 写NO或者FALSE

可以用xcode打开info.plist文件可视化添加key比较直观

8、下面就是使用命令对app进行签名:(所有的库都需要签名,资源文件不需要)

注意:还有一个地方需要注意的,就是在app目录下面的的MacOS目录下面不要放其他的文件,我之前把语言包放在这个目录下面了,后面在上传app的时候一直报错,其他文件都放到资源文件夹里面

命令:codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${frameworkpath}*

${entitlementPath}  就是xcode拷贝过来的文件路径

${cert}  发布证书名字

${frameworkpath} 被签名的库路径

-v 输出详细信息

-f 覆盖签名,这个一定要加,因为后面你发布的时候经常会不会重新拷贝依赖库,不加就签名不上了,如果证书或者其他信息发生变化时就会存在不同的签名主体,我就是出现过这个问题,因为后面证书删除了重新创建,所以出问题了

-s 发布证书名字

因为依赖库很多,手动敲命令一个一个去签名就比较繁琐了,我写了一个脚本会把所有的库进行签名,然后把app打包成pkg,pkg就是上传app store connect的最终形式。

脚本如下:

#! /bin/bash# app 绝对路径apppath="/Users/michaellyn/Desktop/demo"#依赖库路径frameworkpath="${apppath}/demo.app/Contents/Frameworks/"#Qt插件路径pluginpath="${apppath}/demo.app/Contents/PlugIns/"#发布证书名字cert="3rd Party Mac Developer Application: xxxx. (xxx)"#安装证书名字certInstall="3rd Party Mac Developer Installer: xxxx. (xxx)"#沙盒权限配置文件entitlementPath="${apppath}/demo.entitlements"#给程序签名codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${frameworkpath}*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}bearer/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}audio/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}imageformats/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}mediaservice/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}platforms/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}printsupport/*codesign --entitlements ${entitlementPath} -v -f -s "${cert}" ${pluginpath}iconengines/*codesign --deep --entitlements ${entitlementPath} -v -f -s "${cert}" ${apppath}/demo.app#打包pkg并签名安装包productbuild --component ${apppath}/demo.app /demo --sign "${certInstall}" demo.pkg#检查安装包以及测试安装sudo installer -store -pkg demo.pkg -target /

执行上述脚本就可以完成对app的签名打包了,注意修改路径和app名字。

9、上传pkg到app store connect,首先在app store里面下载上传工具:Transporter.

然后使用这个工具上传pkg,上传成功就可以到app store connect对应app的构建版本里面选择刚刚上传的pkg,在编辑完善其他信息就可以提交审核了。

 

审核通过:

发布成功:

qt编写触摸事件的关键

其实,Qt的事件对象都有一个accept()函数和ignore()函数。正如它们的名字,前者用来告诉Qt,事件处理函数“接收”了这个事件,不要再传递;后者则告诉Qt,事件处理函数“忽略”了这个事件,需要继续传递,寻找另外的接受者

事件处理顺序:

事件过滤器eventFilter(QObject *obj, QEvent *e);----->事件分发event(QEvent *e);-------->具体事件keyPressEvent(QKeyEvent *e)等事件

如果对象使用installEventFilter()函数注册了事件过滤器,目标对象中而所有事件将先发给这个监视对象的eventFilter()函数

bool eventFilter(QOBject *,Event *);//事件过滤器

return true;//不让事件继续传播

在构造函数里添加触摸支持

setAttribute(Qt::WA_AcceptTouchEvents);//支持多点触控

setAttribute(Qt::WA_StaticContents);//那么绘制事件的区域就会被严格限定在之前没有被显示的像素部分上。这也就意味着,如果重新把窗口部件改变为比原来还要小的尺寸,那么就根本不会产生任何绘制事件。

在需要拖动的控件的类中重写鼠标的按下,移动,释放事件

//相关变量定义

 QPoint      m_beginPos;//鼠标点击的起始位置

 QPoint      m_leftTopPos;//窗口左上角的位置

 bool        m_isTitle;//点击的点是否在标题栏上

    

void configBtnNameDialog::mousePressEvent(QMouseEvent *event)

{

    if(event->button() == Qt::LeftButton)

    {

        //在标题栏中点击

        if(ui->widgetTitle->rect().contains(event->pos()))//注意此时使用pos()而不能使用globalPos()

        {

            m_beginPos = event->globalPos();//刚开始按下点的坐标

            m_leftTopPos = pos();//窗口左上角坐标

            m_isTitle = true;//是窗口标题栏

        }

    }

    QDialog::mousePressEvent(event);

}


void configBtnNameDialog::mouseMoveEvent(QMouseEvent *event)

{

    if(event->buttons() == Qt::LeftButton && m_isTitle)//如果按下的是左键,且位置在标题栏上

    {

        QPoint endPos = event->globalPos();//结束点坐标

        QPoint offPos = endPos - m_beginPos;//结束点与开始点坐标差

        QPoint pos = m_leftTopPos + offPos;//左上角坐标加偏移量

        move(pos);//移动到该点

    }

    QDialog::mouseMoveEvent(event);//不可忘掉

}


void configBtnNameDialog::mouseReleaseEvent(QMouseEvent *event)

{

    m_isTitle = false;

    QDialog::mouseReleaseEvent(event);

}

其中ui->widgetTitle为自定义的标题栏。实际上这种事件的编写,还是之前没有触摸屏时一样的事件,只是添加了支持多点触控的属性,便可以手动触摸拖动窗口标题栏。

触摸长按出现对话框(也可以是其它控件)

//变量定义

bool        m_isLongPress;//是否触屏长按

int         m_second = 0;//当前的秒数

QTime       m_begintime;//开始长按的时间


bool myButton::event(QEvent *event)

{

    switch(event->type())

    {

        case QEvent::TouchBegin:

        {

            QTouchEvent* touch = static_cast<QTouchEvent*>(event);

            QList<QTouchEvent::TouchPoint> touchPoints = touch->touchPoints();

            outPut<<"myButton::event->TouchBegin点的个数为:"<<touchPoints.count();

            if(touchPoints.count() == 1)

            {

                m_begintime = QTime::currentTime();

                m_second = m_begintime.hour()*3600+m_begintime.minute()*60+m_begintime.second();

                outPut<<"开始时间:"<<m_begintime<<"当前秒数:"<<m_second;

                m_isLongPress = true;

            }

            event->accept();

        }

        case QEvent::TouchUpdate:

        {

            QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);

            QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints();

            outPut<<"myButton::event->TouchUpdate点的个数为:"<<touchPoints.count();

            if (touchPoints.count() == 1)

            {

                outPut<<"touchPoints.count() == 1";

                if(m_isLongPress)

                {

                    outPut<<"正在被按下";

                    QTime current_time =QTime::currentTime();

                    int secondEnd = current_time.hour()*3600+current_time.minute()*60+current_time.second();

                    outPut<<"时间:"<<secondEnd;

                    if(secondEnd - m_second >= 1)

                    {

                        //长按2秒显示设置名称的对话框

                        showEditNameDlg();

                        outPut<<"长按显示对话框"<<"起始时间:"<<m_begintime<<"终止时间:"<<current_time;

                    }

                }

            }

            return true;

        }

        case QEvent::TouchEnd:

        {

            m_isLongPress = false;

            event->accept();

            outPut<<"释放";

            return true;

        }

        default:

            break;

    }

    return QPushButton::event(event);

}

以上是触摸长按出对话框,中间长按等待的时间可能会不一样,有时很短的时间可以出对话框,有时很长一段时间才出对话框,究其原因是进入TouchUpdate的时间不一样,如下面这样,可以很快出对话框,因为检测到了长按时间超过1秒。

总结

1.添加设置触摸属性,setAttribute(Qt::WA_AcceptTouchEvents);;

常规的按键之类的可以直接实现。

2.复杂一点在event事件中实现相关功能。

注意:以上可能只适用于窗口,小控件,至于场景视图这些不一定完全适用,还需做一些改动。

































Top