您现在的位置是:网站首页> C/C++
QT学习笔记归纳
- C/C++
- 2024-12-15
- 2735人已阅读
QT学习笔记归纳
Qt Creator中,include路径包含过程(或如何找到对应的头文件)
Qt程序打包发布方法(使用官方提供的windeployqt工具)
QMediaPlayer视频没有画面显示,或播放卡顿问题解决(DirectShowPlayerService::doRender: Unresolved error code 80040266)
Qt-QProcess-启动子进程-控制台进程隐藏-获取子进程标准输出和返回码
Qt6 C++基础入门4 事件处理与eventFilter检测
Qt5.14编译
Qt5.14编译
用Android Studio 3.2编译
首先改地址
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 等
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动态库:
如果这里不添加,当程序部署到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包中字节数组参数的函数定义
Qt android调用jar包,字节数组参数类型的函数
该函数所在类的定义
调用jar包中的字节数组参数的函数
定义类的变量
QByteArray转换成jbyteArray
字节数组对于的signature为[B
使用callMethod方法调用函数
整体代码
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应用程序
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 程序:
生成的程序运行正常之后,找到项目的生成目录,比如 项目源码路径: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
然后可以在 D:\hellomw 文件夹里看到 windeployqt 工具自动复制的插件文件夹和 dll文件、qm文件。这时候得到的就完整的 exe 程序发布集合,依赖关系都解决好了。
把 D:\hellomw 文件夹 打包就可以发布了,不用自己一个个找 dll 文件了。D:\hellomw 文件夹里的qm文件是多国语言翻译文件,不需要可以删了,其他的都保留。
Qt Quick Application
首先用 QtCreator 新建一个 Qt Quick Application 项目,直接用默认的项目模版,点击下一步生成项目,项目名字假定是 helloqml。
然后以 Release 方式编译生成 exe 程序:
然后找到项目的构建目录,比如项目源码目录 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
注意不要跟完全一样照抄上条命令!–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版本的,说的是尽量,不是也没关系,毕竟是发布版本,调试版本还是算了吧
将exe单独拷贝出来,存放在一个空目录中
第二步,在开始菜单中找到Qt自带的命令符,有的同学可能安装了很多版本,这里要留意一下,我选择的是Qt5.15.2(MSVC 2019 64-bit)
第三步,进入刚才新建的文件夹目录中,下面的自己看仔细。
第四步,最最重要的就是下图中的1位置啊,兄弟们,一定要看清楚了,这是你项目代码的位置,不是网上传说的qml的安装位置,真的是大坑,害人不少。 2就是你要打包的exe文件名字,按tab键就能自动将2补齐,因为目录中此时就只有这一个文件。
注意上图命令行1中的位置是你代码的位置,如下图所示,我贴出来了。
最后,执行完上面的第4步后,在你拷贝出来的exe目录中就会多出了很多文件,都是exe需要的文件,在运行时需要的依赖文件。
最后一击,直接运行,应用发布成功。
NND,真心不容易,花了我好长好长时间,所以我决定英语继续学习下去,学无止境。
Qt6构建于打包发布
打包发布
release 单文件打包
参考文献:点击查看
首先准备我们欲打包发布的项目
默认情况下运行时发布的是 debug 类型的(包含冗余调试信息,文件大),我们需要切换到 release 类型!!!
切换完毕后点击绿色运行按钮,此时即编译完成 release 文件
按照下图步骤 1,切换至 release
构建文件生成位置根据下图 234 步即可查看
打开构建完毕的文件夹,找到构建完毕的 exe 文件
之后任意新建一个 model 文件夹,把该 exe 文件拷贝进去
之后在开始菜单里面搜索,找到 Qt 命令行,注意我这里使用的编译器是 MinGW,如果你用的是 MSVC 就要切换到对应的命令行!
使用 cd 指令,进入到 model 文件夹下,使用下方指令对其进行打包
windeployqt xxx.exe
打包完毕后双击对应的 exe 文件,发现可以正常运行,那么我们就进入下一步
这里我们需要使用到一个打包软件,叫做Enigma Virtual Box,它是免费的
点击这里前往官网下载:https://enigmaprotector.com/en/downloads.html
打开 Enigma Virtual Box
待封包的主程序,选择 model 文件夹下的 exe 文件
封包程序另存为,自己找一个顺眼的文件夹保存打包好的单文件
之后点击“文件选项”,勾选“压缩文件”,然后点击确定
点击“添加”,务必选择“递归添加文件夹”,弹出窗口选择我们的 model 文件夹即可
最后点击右下角的 执行封包 稍稍等待一分钟,就可以生成我们打包好的单文件应用了
该应用封装了所有 dll,移植到任何一台 windows 电脑都可以正常使用!
anaconda 冲突错误解决
有些情况下,我们使用 windeployqt 打包时,会出现unable find xxx
这是由于我们之前安装了 anaconda 环境,并配置了环境变量,命令行错误的寻址到了 anaconda 下并查找模块 windeployqt,自然是找不到的,所以必定报错
目前没有很好的解决办法,只能修改环境变量
打开环境变量,打开 path,找到我们设置的 anaconda 变量的位置,在该变量之前加上一个 0(目的就是为了使该变量失效,从而使命令行不要寻址到此位置!)
然后连点三个确定,才可正式应用变动
重新打开 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自学经验
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文件打开设计
手动先添加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)));
在设计界面连接函数
连接函数
然后左击控件打开页面,往下拖一段距离,再松开手,就会弹出控件关联槽函数的对话框。
添加OpenWeb
也可以直接添加
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)
- 数据传输:
- 共享数据:
- 通知事件:
- 资源共享:
- 进程控制:
- 管道(pipe):
- 有名管道(named pipe):
- 信号量(semophore):
- 消息队列(message queue):
- 信号(signal):
- 共享内存(shared memory):
- 套接字(socket):
Qt 之进程间通信(TCP/IP)
Qt 之进程间通信(Windows 消息)
效果
- 1
Qt 之进程间通信(共享内存)
- 检测该进程是否连接到共享内存段,如果连接,则将该进程与共享内存段分离。
- 从系统足够大的内存中得到一个新的共享内存段。
- 锁定该共享内存段,以阻止第二个对话框进程访问,将缓冲区中的图片复制进共享内存段。
- 将共享内存段解锁,然后第二个对话框进程就可以访问了。
- 将该进程与进程A创建的共享内存段绑定
- 锁定共享内存段,复制数据到缓冲区,然后写入到QImage中。
- 将共享内存段解锁,然后将该进程与共享内存段分离。
Qt 之进程间通信(QProcess)
- 一般编写程序时,严格来说,启动外部程序,需要判断版本是debug还是release。否则,有可能会造成错误。
- 判断将要启动的进程是否存在,如果不存在,则启动;否则,不启动。
- 传参:这里我列举的是json格式。
- 在main函数中初始化QApplication以后,获取命令行参数。
- 命令行参数中包含当前程序的名称、接收的参数等信息。
- 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
如何触发菜单项点击事件?
新建一个主菜单以及对应菜单项后,我们会在设计页面的右侧预览中找到对应的组件
如下图
menuBar 表示默认的菜单组件
menu 表示新添加的主菜单“文件”
actionFile 表示先添加的菜单项“new”
之后直接回到 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
定时器
定时器图片流水灯案例
实现效果:构建一个界面,点击开始按钮轮流播放文件夹下图片,点击停止按钮停止播放
构建页面,上部是一个没有内容的 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文件加入到工程资源里,如:
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是完全没有问题的:
登录界面,里面用到了Qt提供的正则验证功能,如果输入的ip不符合规则,则根本输入不了,例如如果输入一个非法的字符,则文本框中没有反应
登录进去之后的主界面,一共有7个选项卡,每一个选项卡代表一个功能:
视频导出:导出web平台上的视频数据,包括文件,片头片尾,视频的索引,可以导出电影模式,资源模式,支持断点续传。
视频剪切:可以将视频中不需要的内容剪切掉
视频截取:截取视频中需要的部分
后期导播:对资源模式进行后期导播
创建片头片尾:傻瓜式的生成片头片尾的图片文件
合成片头片尾:将片头片尾的图片文件添加到一个视频的起始和结束
转码上传:将多种格式的视频文件转码成MP4文件并上传到web平台
视频导出功能,实际上是导出一个课件,该课件可能录制的有电影模式的视频,资源模式的视频,或者都有。功能介绍如下:
视频剪切对视频进行剪切处理:
对视频进行截取:
对资源模式进行后期导播:
创建自定义片头片尾(其实就是傻瓜式的让用户生成自定义图片),里面的文字可以拖动,可以键盘微调,可以修改:
合并片头片尾,将生成好的片头片尾图片,合并到一个mp4的开始和结尾处,可以指定片头片尾的图片文件,并指定在mp4开头或结尾显示的时间:
转码与上传:
程序里面的外观包括,下拉列表,文本输入框,按钮,表格,tab等都是使用QSS设置的。QSS参考了CSS,所有里面有一些属性是与CSS相同的,但并不是所有的CSS属性都能够在QSS中使用,也并不是所有的Qt控件都支持CSS的盒模型,Qt帮助中关于QSS的部分详细描述了可用的QSS属性列表,以及控件列表,伪状态列表,子控件列表,QSS中可用的图标列表,以及QSS属性值的单位列表,限于篇幅不可能将所有这些都详细列出来,其实在安装QtCreator之后的帮助文档中有详细的描述,其列表类似下面的图(描述了部分伪状态的说明):
Qt 之资源系统
QT中资源文件的使用
1.在工程中点右键,选添加文件;
在下一页中输入一个资源文件名,如uires,这样工程树下就会出现资源文件夹。
2.右键,选择“Open in Editor”打开它
3.添加或修改前缀名,前缀的作用类似于文件夹;或添加文件
4.在按钮中使用,pbStart和pbEnd是界面中的两个按钮:
QIcon sIcon(":/style/111.png"); ui->pbStart->setIcon(sIcon); QIcon eIcon(":/style/error.png"); ui->pbEnd->setIcon(eIcon);
5.按钮效果
也可以使用资源别名
这样,我们可以直接使用:/images/doc-open引用到这个资源,无需关心图片的真实文件名。
QT 直接添加资源
直接qrc
.pro文件
RESOURCES += \
gallery.qml \
pages/BusyIndicatorPage.qml \
使用
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事件中实现相关功能。
注意:以上可能只适用于窗口,小控件,至于场景视图这些不一定完全适用,还需做一些改动。
上一篇:QML总结笔记
下一篇:Qt Android开发资料收集