草庐IT

QML进阶(八)实现QML界面与C++类型交互

码农飞飞 2023-04-11 原文

在QML工程中,一般QML界面只负责前端交互,而真正的业务逻辑都是C++模块实现的。为了实现前端和后端的顺利衔接,我们需要做好QML界面与C++的交互。这里就介绍一下如何在QML中调用对应的C++模块。在QML中调用C++模块的方法主要有三种,分别是:

1.设置上下文属性(setContextProperty())

2.在QML引擎里面注册新类型(qmlRegisterType)

3.导出对应的QML扩展插件。

下面介绍一下三个方法的优缺点:

对于小型应用来说,方法一设置上下文属性是最简单实用的方法。开发者只需要将对应的接口和变量暴露给QML就行。由于设置在QML中的变量是全局的,一定要注意避免名称冲突。

在QML引擎里面注册新的类型,允许用户在QML文件中控制C++对象的生命周期,这是设置上下文属性这种方法无法实现的。同时注册新类型的方法,不会污染全局命名空间。但是这种方法也有一个缺点,就是QML中的类型都需要提前注册,所有用到的库都需要在程序启动的时候链接,无法动态链接。但在绝大多数情况下,这并不是一个问题。

QML扩展插件是弹性最好,但也是最复杂的方法。QML允许用户在插件里面注册对应的新类型。这些新类型在QML第一次导入对应的符号的时候被加载。同时,通过使用QML单例引入,我们的新类型不会污染全局命名空间。由于新类型被插件化了,我们可以很轻松的在多个项目中复用我们之前定义的新类型。

下面分别通过实例介绍一下三种方法的调用过程

1.设置上下文属性(setContextProperty())

首先在QML工程中添加一个C++类FileIO,该类主要负责文件的打开读取和保存。类继承自QObject,导出了QML需要访问的接口和成员变量,代码如下:

//fileio.h
#ifndef FILEIO_H
#define FILEIO_H

#include <QtCore>
//用来打开的保存对应的文件
class FileIO : public QObject
{
    Q_OBJECT
    //定义QML可以访问的属性,定义格式如下
    //Q_PROPERTY(变量类型 访问名称 READ 读方法 WRITE 写方法 NOTIFY 发生变化的通知信号)
    //需要定义在Q_OBJECT之后第一个public之前
    Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

    //ui_title是在QML使用的别名,m_title_content是对应的变量名称
    //CONSTANT说明是只读的
    Q_PROPERTY(QString ui_title MEMBER m_title_content CONSTANT)
public:
    FileIO(QObject *parent = 0);
    ~FileIO();

    //定义QML可以访问的方法
    Q_INVOKABLE void read();
    Q_INVOKABLE void write();

    QUrl source() const;
    QString text() const;
public slots:
    void setSource(QUrl source);
    void setText(QString text);
signals:
    void sourceChanged(QUrl arg);
    void textChanged(QString arg);
private:
    QUrl m_file_source;
    QString m_file_content;
    //用来测试的只读title数据
    QString m_title_content;
};

#endif // FILEIO_H
//fileio.cpp
#include "fileio.h"
FileIO::FileIO(QObject *parent)
    : QObject(parent),
      m_title_content(QString("fileio"))
{
}

FileIO::~FileIO()
{
}

void FileIO::read()
{
    if(m_file_source.isEmpty()) {
        return;
    }
    QFile file(m_file_source.toLocalFile());
    if(!file.exists()) {
        qWarning() << "Does not exits: " << m_file_source.toLocalFile();
        return;
    }
    if(file.open(QIODevice::ReadOnly)) {
        QTextStream stream(&file);
        m_file_content = stream.readAll();
        emit textChanged(m_file_content);
    }
}

void FileIO::write()
{
    if(m_file_source.isEmpty()) {
        return;
    }
    QFile file(m_file_source.toLocalFile());
    if(file.open(QIODevice::WriteOnly)) {
        QTextStream stream(&file);
        stream << m_file_content;
    }
}

QUrl FileIO::source() const
{
    return m_file_source;
}

QString FileIO::text() const
{
    return m_file_content;
}

void FileIO::setSource(QUrl source)
{
    if (m_file_source == source)
        return;

    m_file_source = source;
    emit sourceChanged(source);
}

void FileIO::setText(QString text)
{
    if (m_file_content == text)
        return;
    m_file_content = text;
    emit textChanged(text);
}

定义完对应的C++类型之后,我们就可以在QML工程中添加对应的上下文属性了,添加方法如下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QFont>
#include "fileio.h"
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    //根据不同的QT版本设置对应的编码
    app.setFont(QFont("Microsoft Yahei", 9));
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
#if _MSC_VER
    QTextCodec *codec = QTextCodec::codecForName("gbk");
#else
    QTextCodec *codec = QTextCodec::codecForName("utf-8");
#endif
    QTextCodec::setCodecForLocale(codec);
    QTextCodec::setCodecForCStrings(codec);
    QTextCodec::setCodecForTr(codec);
#else
    QTextCodec *codec = QTextCodec::codecForName("utf-8");
    QTextCodec::setCodecForLocale(codec);
#endif

    //定义对应的类型指针
    QScopedPointer<FileIO> current_file_io(new FileIO());
    QQmlApplicationEngine engine;
    //在加载对应的URL之前, 设置上下文属性
    engine.rootContext()->setContextProperty("qmlfileio",current_file_io.data());
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

在QML引擎里面添加了对应的C++类型之后,我们就可以在QML文件中使用对应的类型了,QML中的调用方法如下:

import QtQuick 2.8
import QtQuick.Window 2.2
import Qt.labs.platform 1.0

Window {
    visible: true
    width: 440
    height: 300
    title: qsTr("Context fileIO")
    Column{
        Rectangle{
            id:contentRect
            x:10; y:10
            width: 400
            height: 150

            //用于显示打开的文本文件的内容
            Text{                
                id:content
                anchors.top :contentRect.top
                anchors.bottom: contentRect.bottom
                anchors.left: contentRect.left
                anchors.right: contentRect.right
                text: qmlfileio.text
            }
            border.color: "#CCCCCC"
        }
        
        //点击的按钮用来选择对应的文件
        Rectangle{
            anchors.horizontalCenter:contentRect.horizontalCenter
            color: "#4D9CF8"
            width:200
            height: 30
            Text{
                anchors.centerIn: parent
                text:"点击打开文件"
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    fileDialog.open();
                }
            }
        }
    }
    //文件选择窗口,选择需要打开的文件
    //并读取文件中对应的内容
    FileDialog{
        id: fileDialog
        folder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
        onFileChanged: {
            qmlfileio.source = fileDialog.file;
            qmlfileio.read();
        }
    }
}

显示效果如下图所示:

2.在QML引擎里面注册新类型(qmlRegisterType)

通过注册的方法使用新类型的话,我们首先在main函数里面注册一下上面创建的C++类型,注册方法如下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QFont>
#include "fileio.h"
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    //在engine声明之前注册C++类型
    //@1:类在qml中别名 @2:版本主版本号 @3:版本的次版本号 @4类的名称
    qmlRegisterType<FileIO>("org.fileio",1,0,"FileIO");
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

 注册完成之后,我们就可以在QML文件中导入对应的模块并使用新创建的类型了,导入方法如下:

import org.fileio 1.0

导入完成之后,我们就可以在QML中使用对应的类型了,使用方法如下:

import QtQuick 2.8
import QtQuick.Window 2.2
import Qt.labs.platform 1.0
import org.fileio 1.0

Window {
    visible: true
    width: 440
    height: 300
    title: qsTr("Context fileIO")
    Column{
        Rectangle{
            id:contentRect
            x:10; y:10
            width: 400
            height: 150

            //用于显示打开的文本文件的内容
            Text{                
                id:content
                anchors.top :contentRect.top
                anchors.bottom: contentRect.bottom
                anchors.left: contentRect.left
                anchors.right: contentRect.right
                text: fileIO.text
            }
            border.color: "#CCCCCC"
        }

        //点击的按钮用来选择对应的文件
        Rectangle{
            anchors.horizontalCenter:contentRect.horizontalCenter
            color: "#4D9CF8"
            width:200
            height: 30
            Text{
                anchors.centerIn: parent
                text:"点击打开文件"
            }
            //点击按钮弹出选择文件的对话框
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    fileDialog.open();
                }
            }
        }
    }
    //文件选择窗口,选择需要打开的文件
    //并读取文件中对应的内容
    FileDialog{
        id: fileDialog
        folder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
        onFileChanged: {
            fileIO.source = fileDialog.file;
            fileIO.read();
        }
    }
    
    //外部导入的C++类型,可以直接定义使用
    //外部通过ID来访问该模块
    FileIO{
        id:fileIO
    }
}

3.导出对应的QML扩展插件

首先新建一个QML扩展插件的工程,创建方法如下图所示:

模块路径和导出类型的名称如下所示:

 

创建完成之后,就会在对应的工程里面创建一个名为qmldir的文件,用来标记这个插件的模块名称和插件名称。qmldir文件的内容如下:

//qmldir
module MyPlugin
plugin TestPlugin

工程中会增加两个插件的导出接口文件,文件内容如下:

//testplugin_plugin.h
#pragma once
#include <QQmlExtensionPlugin>
//继承自QQmlExtensionPlugin的类型
class TestPluginPlugin : public QQmlExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)

public:
    void registerTypes(const char *uri);
};
//testplugin_plugin.cpp
#include "testplugin_plugin.h"
#include "fileio.h"

#include <qqml.h>
//该接口在插件被加载的时候调用,根据模块名称注册对应的类型
void TestPluginPlugin::registerTypes(const char *uri)
{
    // @uri MyPlugin
    qmlRegisterType<FileIO>(uri, 1, 0, "FileIO");
}

将一开始我们定义的C++类FileIO添加到插件工程中,我们就可以将自定义的C++类型添加到插件模块导出类型中了。编译工程,我们就可以输出对应的插件扩展库了,其实本质上qml扩展库就是一个共享动态库。

 

分别编译debug版本和release版本的插件,为插件的调用做好准备。

为了让QtCreator找到我们自定义的插件,我们需要将对应的插件拷贝到工程目录下,并将其添加到调用工程中。插件的存放目录名称为plugin,目录结构如下所示:

├── project
#    │   └── plugin
#    │       └── debug
#    │       │   └── libTestPlugind.a
#    │       │   └── TestPlugind.dll
#    │       └── release
#    │       │   └── libTestPlugin.a
#    │       │   └── TestPlugin.dll
#    ├── project.pro

在工程中添加了对应的插件目录之后,我们就可以根据需要导入对应的插件了,在pro文件下添加对应的插件目录,代码如下:

#构建对应的配置项
CONFIG(debug, release|debug) {
      QML_IMPORT_PATH = $$PWD/plugin/release
}
else
{
      QML_IMPORT_PATH = $$PWD/plugin/debug
}

添加完成之后,我们就可以在QML开发的时候导入使用对应的自定义模块了,示例代码如下:

import QtQuick 2.8
import QtQuick.Window 2.2
import Qt.labs.platform 1.0
//导入自定义的插件
import MyPlugin 1.0
Window {
    visible: true
    width: 440
    height: 300
    title: qsTr("Context fileIO")
    Column{
        Rectangle{
            id:contentRect
            x:10; y:10
            width: 400
            height: 150

            //用于显示打开的文本文件的内容
            Text{                
                id:content
                anchors.top :contentRect.top
                anchors.bottom: contentRect.bottom
                anchors.left: contentRect.left
                anchors.right: contentRect.right
                text: fileIO.text
            }
            border.color: "#CCCCCC"
        }

        //点击的按钮用来选择对应的文件
        Rectangle{
            anchors.horizontalCenter:contentRect.horizontalCenter
            color: "#4D9CF8"
            width:200
            height: 30
            Text{
                anchors.centerIn: parent
                text:"点击打开文件"
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    fileDialog.open();
                }
            }
        }
    }
    //文件选择窗口,选择需要打开的文件
    //并读取文件中对应的内容
    FileDialog{
        id: fileDialog
        folder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
        onFileChanged: {
            fileIO.source = fileDialog.file;
            fileIO.read();
        }
    }
    //引用自定义插件中的类型
    FileIO{
        id:fileIO
    }
}

在pro文件中添加了插件的导入路径之后,虽然编译成功了,但是在运行的时候,却报插件没有安装,提示信息如下:

qrc:/main.qml:5 module "MyPlugin" is not installed

这是因为程序运行的时候找不到对应的插件导致的。解决这个问题有两个方案。

方案一:将插件放到对应的系统qml目录里面

//我本地的qml目录
C:\Qt\Qt5.9.0\5.9\mingw53_32\qml

方案二:动态添加自定义插件所在的目录,直接使用

相比于方案一,方案二更加灵活建议采用。

首先我们建立一个文件夹用来存放用户自定义的插件,名称可以随意起。我创建一个名为plugin的目录用来存放自定义插件,在该目录下创建一个和插件名称相同的目录来存放对应的插件。qmldir文件中说明了插件的名称,我的插件名称是TestPlugin。这里要区别一下插件的名称和模块的名称。

//qmldir
//模块名称
module MyPlugin
//插件名称
plugin TestPlugin

创建完成插件目录之后,我们还需要在插件目录下创建对应的模块目录,这里的模块名称是MyPlugin.在对应的模块目录下拷贝对应的插件和插件描述文件。记住别忘了拷贝插件的描述文件也就是qmldir文件。拷贝完成之后的目录结构如下所示:

├── outputdir
#    │   └── plugin
#    │       └── TestPlugin
#    │           └── MyPlugin
#    │              └── TestPlugind.dll
#    │              └── TestPlugin.dll
#    |              └── qmldir 
#    ├── ContextProperty.exe

这里说明一下如果模块是多级路径的话需要创建对应的子目录,假如模块的名称是

org.module.test,那么模块路径应该是TestPlugin/org/module/test/。在普通的小项目中不建议采用多级路径的模块。

插件目录完成之后,我们将插件目录拷贝到执行程序的输出路径。然后在调用程序的入口中将插件目录动态添加进去,添加方法如下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QFont>
#include <QTextCodec>
#include <QDir>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    QString plugin_path = app.applicationDirPath() + "/plugin/TestPlugin/";
    engine.addImportPath(plugin_path);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

添加完成之后,我们就可以正常的调用对应的自定义插件了。

相比于前两种方法来说,第三种方法配置起来繁琐不少,但是总体来说使用插件的方式程序可扩展性更好,便于对自定义类型的复用。

对应的例子下载地址如下:

链接:https://pan.baidu.com/s/1bigN779S94IMXJUdqvw_xw

提取码:nlfl

有关QML进阶(八)实现QML界面与C++类型交互的更多相关文章

  1. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  2. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  3. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  6. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  7. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  8. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  9. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐