Qt 学习笔记

2020/02/14 Qt

文件和文件夹相关操作

QFile 文件属性区区别

    QString strSourceFile = "D:/Qt/source-target.testFileName.tar.gz";
    QFile file(strSourceFile);

    QFileInfo fileInfo(strSourceFile);

    qDebug() << "file:" << file.fileName();

    qDebug() << "fileName:" << fileInfo.fileName();
    qDebug() << "baseName:" << fileInfo.baseName();
    qDebug() << "filePath:" << fileInfo.filePath();
    qDebug() << "suffix:" << fileInfo.suffix();
    qDebug() << "absolutePath:" << fileInfo.absolutePath();
    qDebug() << "absoluteFilePath:" << fileInfo.absoluteFilePath();
    qDebug() << "absoluteDir-path:" << fileInfo.absoluteDir().path();
    qDebug() << "canonicalFilePath:" << fileInfo.canonicalFilePath();
    qDebug() << "canonicalPath:" << fileInfo.canonicalPath();
    qDebug() << "completeSuffix:" << fileInfo.completeSuffix();
    qDebug() << "dir-path:" << fileInfo.dir().path();

输出信息如下:

file: "D:/Qt/source-target.testFileName.tar.gz"
fileName: "source-target.testFileName.tar.gz"
baseName: "source-target"
filePath: "D:/Qt/source-target.testFileName.tar.gz"
suffix: "gz"
absolutePath: "D:/Qt"
absoluteFilePath: "D:/Qt/source-target.testFileName.tar.gz"
absoluteDir-path: "D:/Qt"
canonicalFilePath: "D:/Qt/source-target.testFileName.tar.gz"
canonicalPath: "D:/Qt"
completeSuffix: "testFileName.tar.gz"
dir-path: "D:/Qt"

QDir 常见使用

QDir 常用在文件夹操作,提供了文件夹的增、删、改,常见的操作主要有:

  • path: 返回当前 dir 的路径
  • currentPath: 返回当前程序 exe 的路径
  • cdUp: 会返回到上一级目录(只有文件路径存在时返回成功,文件路径不存在返回失败)
    QString path("D:/Qt/Qt5.9.0/5.9/msvc2013_64/bin");
    QDir dir(path);

    qDebug() << "path:" << dir.path();
    qDebug() << "currentPath:" << dir.currentPath();
    qDebug() << "dirName:" << dir.dirName();

    bool bResult = dir.cdUp();

    qDebug() << bResult;
    qDebug() << "path:" << dir.path();

输出信息如下:

path: "D:/Qt/Qt5.9.0/5.9/msvc2013_64/bin"
currentPath: "D:/Project/untitled5/temple"
dirName: "bin"
true
path: "D:/Qt/Qt5.9.0/5.9/msvc2013_64"

打开资源管理器并定位到指定文件

  • 方法1 QDesktopServices::openUr

有局限,仅仅能打开文件/文件夹,不能定位到

  • 方法2 QProcess 调用 explorer

使用时注意空格等特殊路径问题,最好转换下

核心命令:

explorer.exe /select,"D:/Qt/Qt5.9.0/5.9/msvc2013_64/bin/qmlscene.exe"
    QString srcFile = "D:/Qt/Qt5.9.0/5.9/msvc2013_64/bin/qmlscene.exe";
    QString program = "explorer";
    QStringList arguments;
    arguments << "/select,"
              << QDir::toNativeSeparators(srcFile);

    QProcess::startDetached(program, arguments);

这个其实是利用了 explorer.exe 来执行一些命令,还有其他可用的命令可以使用:

Explorer [/n][/e][[,/root],[path]][[,/select],[path filename]]


Q_PROPERTY 自动化生成

只需要定义好 Q_PROPERTY,然后光标在该属性上面按下Ctrl + 回车,会自动给你创建好对应的成员变量以及 setget 方法


QML 使用枚举

C++ 中定义然后注册给 QML 使用

自从 Qt 5.5开始,支持在 C++ 中定义一个枚举,然后使用 Q_ENUM定义关键字,最后注册枚举到原对象系统中,这样在 QML 中可以直接使用

class DefineGlobal : public QObject
{
    Q_OBJECT
public:
    explicit DefineGlobal(QObject *parent = nullptr);

    enum StatusType {
        Normal      = 0x0,
        Hovering    = 0x1,
        Pressed     = 0x2,
        Checked     = 0x3
    };
    Q_ENUMS(StatusType)
};

然后用注册的方式

 qmlRegisterType<DefineGlobal>("DefineGlobal.kevinlq.com", 1, 0, "DefineGlobal");

然后在 QML 中使用

import QtQuick 2.15
import QtQuick.Window 2.15
import DefineGlobal.kevinlq.com 1.0

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Component.onCompleted: {
        console.log("###StatusType:",DefineGlobal.Checked)
    }
}

还有一种方法,不需要定义类对象,直接将定义的枚举类型注册完给 QML 使用

namespace Button_State {
Q_NAMESPACE
enum Button_StateType {
    Normal      = 0x0,
    Hovering    = 0x1,
    Pressed     = 0x2,
    Checked     = 0x3,
};
Q_ENUMS(Button_StateType);
}

// 注册
qmlRegisterUncreatableMetaObject(Button_State::staticMetaObject,"kevinlq.com.devstone",
major,minor,"ButtonState", "Access to enums & flags only");

// QML 使用

ButtonState.Normal

QML 中直接定义

到了 Qt 5.10已经支持在 QML 中定义枚举了 官方介绍连接

MyComponent.qml

Rectangle {
    id: root

    // Define Shape enum
    enum Shape {
        None,
        Round,
        Pointy,
        Bobbly,
        Elusive
    }

    // Note: property using enum is of type int
    property int selectedShape: MyComponent.Shape.None

    visible: selectedShape !== MyComponent.Shape.None
    color: selectedShape === MyComponent.Shape.Pointy? "red": "green"
}

注册 C++对象给 QML 使用

日常在C++中使用的对象,如果需要可以注册给 QML 来使用,包括类对象、枚举、单例类等,提前是这些类必须继承 QObject

普通对象注册

class HandleMessage: public QObject
{
    Q_OBJECT
public:

    enum MsgCode
    {
        MsgRun
    };
    Q_ENUMS(MsgCode)

    HandleMessage(QObject *parent = nullptr) : QObject(parent)
    {
    }
};

// 注册

const char *uri = "kevinlq.com.devstone";
int versionMajor = 1;
int versionMinor = 0;

qmlRegisterType<HandleMessage>(uri,versionMajor,versionMinor,"HandleMessage");

使用

//Main.qml

Rectangle
{
    HandleMessage
    {
        id: msgHandle
    }

    Component.onCompleted: 
    {
        // 使用C++枚举
        console.log("###StatusType:",HandleMessage.MsgRun)
    }
}

单例对象注册

单例对象比较特殊,不能使用上述方法进行注册

class HandleMessage: public QObject
{
    Q_OBJECT
public:

    enum MsgCode
    {
        MsgRun
    };
    Q_ENUMS(MsgCode)

    static HandleMessage* getInstance()
    {
        static HandleMessage _instance;
        return &_instance;
    }

private:
    HandleMessage(QObject *parent = nullptr) : QObject(parent)
    {
    }
};

// 注册
// 需要先提供一个静态方法用于返回该对象,注册的时候回调使用
 static QObject *msgHandleProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
 {
     Q_UNUSED(engine)
     Q_UNUSED(scriptEngine)

     return HandleMessage::getInstance();
 }

const char *uri = "kevinlq.com.devstone";
int versionMajor = 1;
int versionMinor = 0;

// Second, register the singleton type provider with QML by calling this 
qmlRegisterSingletonType<HandleMessage>(uri, versionMajor, versionMinor, "HandleMessage", msgHandleProvider);

单例对象使用时直接用该对象名即可调用对象成员属性和方法


Rectangle
{
    // 这种写法是错误的,单例类不需要在这里初始化了
    // HandleMessage
    // {
    //     id: msgHandle
    // }

    Component.onCompleted: 
    {
        // 使用C++枚举
        console.log("###StatusType:",HandleMessage.MsgRun)
    }
}

ListView 注意事项

  • 平滑滚动+取消拖动反弹效果
    interactive: true
    boundsMovement: Flickable.StopAtBounds
    

    interactive 默认为 true ,如果设置为 false, 则整个View 不会随着鼠标滚动而进行翻页显示, 也就失去了翻页功能,一般不更改整个属性,通过设置其它属性来达到目的

默认我们可以拖动 view 到最顶部、最底部,此时会有一个反弹效果,很多时候是不需要这个效果的,可以通过 boundsMovement 属性来控制

Flickable.StopAtBounds: 取消反弹效果
Flickable.FollowBoundsBehavior:  默认效果

原文

Flickable.StopAtBounds - the contents can not be dragged beyond the boundary of the flickable, and flicks will not overshoot.
Flickable.DragOverBounds - the contents can be dragged beyond the boundary of the Flickable, but flicks will not overshoot.
Flickable.OvershootBounds - the contents can overshoot the boundary when flicked, but the content cannot be dragged beyond the boundary of the flickable. (since QtQuick 2.5)
Flickable.DragAndOvershootBounds (default) - the contents can be dragged beyond the boundary of the Flickable, and can overshoot the boundary when flicked
  • 设置列表滚动速度
    maximumFlickVelocity : 700	// 每秒移动多少像素
    

QML 绘制的三种方式

  • 继承 QQuickPaintedItem 重写void paint(QPainter *painter)
  • 继承 QQuickItem 重写 QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
  • Canvas 绘制

QObject 派生类不在.h声明报错问题

一般我们继承 QObject 总是写在 .h 头文件中,有时候也写在 .cpp 文件中,这个时候就会报错,原因是 moc 工具无法识别我们 cpp 中写的内容,因此需要我们手动引入来让其识别

下面以 main.cpp 为例子


class DS_Student: public QObject
{
    Q_OBJECT
    AUTO_PROPERTY(QString, StuName)
    AUTO_PROPERTY(int, StuAge)
public:
    DS_Student() {;}
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    DS_Student stu;
    stu.setStuName("admin");
    stu.setStuAge(12);

    return a.exec();
}

#include "main.moc"

关键就是最后一句引入xx.moc,添加后清除项目、qmake、重新构建即可正常编译通过

Q_D,Q_Q 使用模板


KSerialize.h

class KSerialize : public QObject
{
	Q_OBJECT
public:
    explicit KSerialize(QObject *parent = nullptr);
	
private:
    Q_DECLARE_PRIVATE(KSerialize)
};

KSerialize.cpp

KSerialize::KSerialize(QObject *parent)
    : QObject{*new KSerializePrivate{}, parent}
{
    //Q_D(KSerialize);
}

调用 D指针前:
Q_D(const KSerialize);

KSerializePrivate.h

class KSerializePrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(KSerialize)
public:
}

如果是多继承方式,还需要增加保护的构造函数

protected:
	explicit KSerializerBase(QObject *parent = nullptr);
	explicit KSerializerBase(KSerializerBasePrivate &dd, QObject *parent);
	
这样派生类就可以初始化自己的 D指针.

Qt 内置日志使用

  • 声明&定义:
    #include <QLoggingCategory>
    Q_LOGGING_CATEGORY(logSerialize, "kserial.serialize")
    Q_LOGGING_CATEGORY(logDeSerialze, "kserial.deserialize")
    
  • 使用
    qCDebug(logSerialize) << __FUNCTION__ << "line:" <<__LINE__
                               << "object is null." << propertyType;
    
  • 设置日志过滤
      QLoggingCategory::setFilterRules("kserial.serialize.debug=false");
      QLoggingCategory::setFilterRules("kserial.deserialize.debug=true");
      QLoggingCategory::setFilterRules("kserial.*.debug=true");
    

qmake 一些变量值

message($$[QMAKE_VERSION])
message($$[QT_VERSION])
message($$[QMAKE_MKSPECS])
message($$[QT_INSTALL_BINS])
message($$[QT_INSTALL_DATA])
message($$[QT_INSTALL_DEMOS])
message($$[QT_INSTALL_DOCS])
message($$[QT_INSTALL_EXAMPLES])
message($$[QT_INSTALL_HEADERS])
message($$[QT_INSTALL_LIBS])
message($$[QT_INSTALL_PLUGINS])
message($$[QT_INSTALL_PREFIX])
message($$[QT_INSTALL_QML])
message($$[QT_INSTALL_TRANSLATIONS])

输出内容如下:

Project MESSAGE: 3.1
Project MESSAGE: 6.6.0
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/mkspecs
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/bin
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64
Project MESSAGE: D:/Qt6.6.0/Examples/Qt-6.6.0
Project MESSAGE: D:/Qt6.6.0/Docs/Qt-6.6.0
Project MESSAGE: D:/Qt6.6.0/Examples/Qt-6.6.0
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/include
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/lib
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/plugins
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/qml
Project MESSAGE: D:/Qt6.6.0/6.6.0/mingw_64/translations

Qt 正确读取图片并显示

正常情况下,读取图片使用QPixmap 或者QImage 都可以,异常场景时会加载失败:

例如:比如文件后缀名和文件实际格式不匹配时,(一个png图片,后缀名是.jpg时会失败)

这个时候读取图片需要显示指定该图片的格式了,但是事先是不知道该图片格式的,或者说直接从图片后缀判断图片是什么格式是不靠谱的, 这个时候就需要使用间接办法了,代码如下:

const QString img = "./test.jpg";
QMimeDatabase qmimedatabase;

const QMimeType mimeType = qmimedatabase.mimeTypeForFile(img, QMimeDatabase::MatchContent);
const QStringList suffixes = mimeType.suffixes();

QPixmap p;
for(const auto &s : suffixes)
{
	if(p.load(img, s.toLocal8Bit().data()))
	{
		break;
		qDebug() << "load image success.." << s;
	}
}

上述为啥要用循环?因为有的图片返回的 suffixes 有多种,如果用 QString preferredSuffix() const 去加载可能会失败.


作者:鹅卵石
时间: 2020年2月14日21:43:20
版本:V 0.0.1
邮箱:kevinlq@163.com
版权:本博客若无特别声明,均属于作者原创文章,欢迎大家转载分享。但是,
希望您注明来源,并留下原文地址,这是对作者最大的尊重,也是对知识的尊重。

如果您对本文有任何问题,可以在下方留言,或者Email我.

Show Disqus Comments

Search

    欢迎关注我的微信号

    一个不羁的码农

    不羁的程序员

    转载请注明出处!

    Table of Contents