QT开发模式(二):QML/JS/C++混合编程
目录
1.引言
2.QML与C++的交互
2.1.QML访问C++类
2.2.QML访问C++属性列表
2.3.QML访问C++属性和方法
3.QML与JavaScript的交互
4.QML/JS/C++混合开发优势
5.总结
1.引言
QT开发模式(一):界面和业务逻辑分离-CSDN博客
在之前的博客中讲解了QT开发的几种模式,这里就来仔细说说qml/js/C++混合编程,其实就是采用QML做UI,JavaScript写逻辑,C++写后台,三种语言混合编程。
QML(Qt Modeling Language)是Qt框架中用于描述用户界面的声明式编程语言。它允许开发者以直观和高效的方式创建动态和吸引人的用户界面,而无需编写大量的传统UI代码。QML结合了HTML/CSS的灵活性和JavaScript的交互性,但专为Qt应用程序设计,提供了更丰富的控件和更紧密的与C++代码的集成。而JavaScript和C++是Qt框架支持的主要编程语言。在Qt应用程序中,这些技术可以相互协作,以构建高效且富有表现力的用户界面。
2.QML与C++的交互
QML与C++的交互稍微复杂一些,但非常强大。你可以通过注册C++类、类型、枚举等,使它们能够在QML中使用。
2.1.QML访问C++类
步骤:
1)编写C++类:确保你的类是可被Qt元对象系统(meta-object system)识别的,即使用Q_OBJECT
宏。
2)注册类:使用qmlRegisterType
、qmlRegisterSingletonType
或qmlRegisterTypeNamespace
(Qt 5.7及更高版本)在QML中注册你的C++类。
3)在QML中使用C++类:一旦注册,你就可以在QML文件中像使用QML组件一样使用C++类了。
示例:C++部分(类名为ChannelStatus
):
ChannelStatus.h
#ifndef _CHANNEL_STATUS_H_
#define _CHANNEL_STATUS_H_
#include <QObject>
//发送和接收通道状态,通过定时查询状态获取
class ChannelStatus : public QObject
{
Q_OBJECT
//ID
Q_PROPERTY(int channelId READ channelId WRITE setChannelId NOTIFY channelIdChanged FINAL)
//状态描述信息
Q_PROPERTY(QString tip READ tip WRITE setTip NOTIFY tipChanged FINAL)
//状态
Q_PROPERTY(StatusType statusValue READ statusValue WRITE setStatusValue NOTIFY statusValueChanged FINAL)
//时间
Q_PROPERTY(QString time READ time WRITE setTime NOTIFY timeChanged FINAL)
//描述信息
Q_PROPERTY(QString des READ des WRITE setDes NOTIFY desChanged FINAL)
public:
enum StatusType { NORMAL_STATUS, ERROR_STATUS };
Q_ENUM(StatusType)
explicit ChannelStatus(int id, const QString& name, const QString& time, StatusType status, QObject* parent = 0);
ChannelStatus(int id, const QString& name, const QString& time, StatusType status, const QString& des, QObject* parent = 0);
ChannelStatus(QObject* parent = 0);
QString tip() const;
void setTip(const QString &newTip);
StatusType statusValue() const;
void setStatusValue(StatusType newStatusValue);
int channelId() const;
void setChannelId(int newChannelId);
QString time() const;
void setTime(const QString &newTime);
QString des() const;
void setDes(const QString &newDes);
signals:
void tipChanged();
void statusValueChanged();
void channelIdChanged();
void timeChanged();
void desChanged();
private:
int m_id;
QString m_tip;
QString m_time;
QString m_des;
StatusType m_statusValue;
};
#endif
ChannelStatus.cpp
#include "ChannelStatus.h"
#include <QDebug>
ChannelStatus::ChannelStatus(int id, const QString& name, const QString& time,
ChannelStatus::StatusType status, QObject* parent)
: QObject(parent)
, m_id(id)
, m_tip(name)
, m_time(time)
, m_statusValue((StatusType)status)
, m_des("")
{
}
ChannelStatus::ChannelStatus(int id, const QString& name, const QString& time, StatusType status, const QString& des, QObject* parent)
: QObject(parent)
, m_id(id)
, m_tip(name)
, m_time(time)
, m_statusValue((StatusType)status)
, m_des(des)
{
}
ChannelStatus::ChannelStatus(QObject* parent)
: QObject(parent)
, m_id(0)
, m_tip("")
, m_time("")
, m_statusValue(StatusType::ERROR_STATUS)
, m_des("")
{
}
QString ChannelStatus::tip() const
{
return m_tip;
}
void ChannelStatus::setTip(const QString &newTip)
{
if (m_tip == newTip)
return;
m_tip = newTip;
emit tipChanged();
}
ChannelStatus::StatusType ChannelStatus::statusValue() const
{
return m_statusValue;
}
void ChannelStatus::setStatusValue(StatusType newStatusValue)
{
if (m_statusValue == newStatusValue)
return;
m_statusValue = newStatusValue;
emit statusValueChanged();
}
int ChannelStatus::channelId() const
{
return m_id;
}
void ChannelStatus::setChannelId(int newChannelId)
{
if (m_id == newChannelId)
return;
m_id = newChannelId;
emit channelIdChanged();
}
QString ChannelStatus::time() const
{
return m_time;
}
void ChannelStatus::setTime(const QString &newTime)
{
if (m_time == newTime)
return;
m_time = newTime;
emit timeChanged();
}
QString ChannelStatus::des() const
{
return m_des;
}
void ChannelStatus::setDes(const QString &newDes)
{
if (m_des == newDes)
return;
m_des = newDes;
emit desChanged();
}
在main注册类:
int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
//方式一
qmlRegisterType<ChannelStatus>("com.xy.transmitchannel", 1, 0, "ChannelStatus");
//方式二
//qmlRegisterUncreatableType<ChannelStatus>("com.xy.transmitchannel", 1,0,
// "ChannelStatus",
// "can not instantiate ScenarioUnit in qml!!!");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
qmlRegisterType 是一个可以将C++实现的类在QML中调用的;
qmlRegisterUncreatableType 是一个非常有用的函数,它允许你在 QML 中注册一个 C++ 类,但不允许直接在 QML 中实例化这个类。这个函数通常用于当你想要在 QML 中访问 C++ 对象的属性、信号和槽函数,而不希望或者不需要在 QML 中创建这个类的实例时。
QML部分:
import QtQuick 2.15
import com.xy.transmitchannel 1.0
Rectangle {
width: 200
height: 200
color: "lightblue"
ChannelStatus{
id: statusObject
channelId: 0
tip: "New message from QML"
}
Text {
text: statusObject.tip
anchors.centerIn: parent
}
}
2.2.QML访问C++属性列表
在很多的编程场景中,QML和C++都需要访问属性列表,这个时候就需要用到QQmlListProperty,QQmlListProperty是Qt元对象系统的一部分,它允许开发人员在QML中定义属性,其属性值是一个列表,可以包含多个元素。这种属性类型特别适用于处理复杂的数据结构或需要动态增减元素的场景。它的特点有:
- 半透明的包装器:QQmlListProperty本质上是一个半透明的包装器,它将QML列表属性的访问委托给C++类,使得C++能够管理这些列表数据。
- 类型安全性:通过模板参数,QQmlListProperty可以指定列表中的数据类型,确保类型安全。
- 信号通知:当列表被修改时(如添加、删除元素),QQmlListProperty能够发出信号,通知QML界面进行更新,实现数据的实时同步。
还是以ChannelStatus为例来说明是如何使用QQmlListProperty的:
定义QML和C++交互的上下文:
#include <QQmlListProperty>
class SimulateContextEx : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<ChannelStatus> channelStatus READ channelStatus NOTIFY channelStatusChanged FINAL)
public:
explicit SimulateContextEx(QObject* parent = 0) : QObject(parent){}
virtual ~SimulateContextEx();
public:
QQmlListProperty<ChannelStatus> channelStatus()
{
return QQmlListProperty<ChannelStatus>(this, &m_channelStatus);
}
// 添加通道状态对象
void appendChannelStatus(ChannelStatus* channelStatus) {
m_channelStatus.append(channelStatus);
emit channelStatusChanged();
}
signals:
void channelStatusChanged();
private:
QList<ChannelStatus*> m_channelStatus;
};
// 还需要为QQmlListProperty提供必要的函数,但在这个例子中,我们使用了默认的append函数
// 如果需要自定义行为,可以重写QQmlListProperty的append、count、at等函数
注意:在上面的代码中,QQmlListProperty
的构造函数接受两个参数:第一个是拥有列表的QObject(在这个例子中是School
对象),第二个是实际的列表(在这个例子中是QList<ChannelStatus*>
)。但是,这里有一个问题:QQmlListProperty
的默认构造函数并不直接接受一个QList
。实际上,QQmlListProperty
的构造函数通常与静态函数一起使用,这些静态函数实现了列表的append、count和at等操作。然而,Qt Quick 2(QML 2.x)和Qt Quick Controls 2中提供了一些简化的方法,可以直接使用QList
与QQmlListProperty
,如上面的示例所示(但这可能依赖于Qt的具体版本和配置)。
在更复杂的场景中,你可能需要为QQmlListProperty
提供自定义的append、count和at函数,这些函数将作为静态成员函数或友元函数在你的类中实现。
在main注册类并配置上下文类:
int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
//方式一
qmlRegisterType<ChannelStatus>("com.xy.transmitchannel", 1, 0, "ChannelStatus");
//方式二
//qmlRegisterUncreatableType<ChannelStatus>("com.xy.transmitchannel", 1,0,
// "ChannelStatus",
// "can not instantiate ScenarioUnit in qml!!!");
SimulateContextEx simulateContext();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("signalSimulateGlobal", &simulateContext);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
现在,你可以在QML中创建ChannelStatus
对象,并访问其ChannelStatus
属性了。
import QtQuick 2.15
import QtQuick.Window 2.15
import com.xy.transmitchannel 1.0
Window {
visible: true
width: 640
height: 480
title: "QQmlListProperty Example"
Rectangle{
id : mainRec
Component.onCompleted: {
for (var i = 0; i < 5; ++i) {
var channelStatus = Qt.createQmlObject('import com.xy.transmitchannel 1.0; ChannelStatus{}', mainRec);
signalSimulateGlobal.appendChannelStatus(channelStatus); // 注意:这里假设appendChannelStatus是公开的,但在上面的C++代码中它不是
}
}
Component {
id: contactDelegate
Item {
width: 180; height: 40
Column {
Text { text: '<b>提示:</b> ' + tip}
Text { text: '<b>描述:</b> ' + des}
}
}
}
//其他QML代码,可能用于显示状态列表
ListView {
anchors.fill: parent
model: signalSimulateGlobal.channelStatus
delegate: contactDelegate
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
focus: true
}
}
}
注意:上面的QML代码示例中直接调用了appendChannelStatus
,但在C++代码中,appendChannelStatus
并不是QQmlListProperty
的一部分,也不是QQmlListProperty
接口的一部分。在实际情况中,你可能需要通过其他方式(如信号槽、直接操作C++对象等)来添加元素到列表中。此外,QML中通常不会直接调用C++中的私有或受保护成员函数。
如果你想要从QML中直接操作列表,你可能需要提供一个公开的QML可访问的接口,比如一个可以调用以添加新元素的QML信号或方法。
2.3.QML访问C++属性和方法
QML访问的C++类属性必须通过Q_PROPERTY来声明,如类ChannelStatus的属性。
QML访问C++类方法必须用Q_INVOKABLE来修饰,如:
class SimulateContextEx : public QObject
{
Q_OBJECT
public:
//任务执行函数
Q_INVOKABLE int startTask();
Q_INVOKABLE int stopTask();
Q_INVOKABLE int suspendTask();
Q_INVOKABLE int resumeTask();
//...
};
3.QML与JavaScript的交互
QML(Qt Modeling Language)与JavaScript在Qt框架中进行了深度集成,为开发者提供了一种高效的方式来创建富交互式的用户界面。QML与JavaScript的交互主要体现在以下几个方面:
1. JavaScript表达式
QML允许在属性值中使用JavaScript表达式。这些表达式可以包括条件运算符、变量赋值、函数调用等,用于动态计算属性值。例如,可以使用JavaScript表达式来根据某些条件改变控件的颜色或大小。
2. JavaScript函数
QML文件中可以直接定义JavaScript函数,并在QML元素的事件处理程序中调用这些函数。这些函数可以处理用户交互,如按钮点击、滑动等,并执行相应的逻辑。此外,QML还支持将JavaScript函数作为属性值或信号处理器的一部分,以实现更复杂的交互逻辑。
3. 外部JavaScript文件
QML支持导入外部JavaScript文件(.js),这些文件可以包含函数、变量和其他JavaScript代码。通过在QML文件中导入这些JavaScript文件,可以将复杂的逻辑或数据处理代码分离到单独的文件中,以提高代码的可维护性和可读性。导入的JavaScript文件中的函数和变量可以在QML文件中直接使用,就像它们是QML对象的一部分一样。
4. 信号与槽机制
QML中的信号和槽是实现事件驱动编程的关键。JavaScript可以连接QML中的信号,并在相应的槽函数中执行JavaScript代码,从而响应各种事件。这种机制使得QML与JavaScript之间的交互变得非常灵活和强大。
5. 属性绑定
QML支持使用JavaScript表达式进行属性绑定,这允许开发者在QML元素之间建立动态关系。当某个属性的值发生变化时,与之绑定的其他属性也会自动更新其值。这种机制有助于实现响应式用户界面,并减少代码中的冗余。
6. 模块化开发
QML与JavaScript的交互还体现在模块化开发方面。通过将UI分解为可重用的组件,并使用JavaScript实现组件之间的交互逻辑,开发者可以构建出大型、复杂的UI系统。这些组件可以单独设计、测试和维护,从而提高开发效率和质量。
7. 特定限制
需要注意的是,QML提供的JavaScript环境与Web浏览器中的JavaScript环境有所不同。QML中的JavaScript环境更加严格,不允许随意修改全局对象或执行某些可能导致安全问题的操作。因此,在编写QML与JavaScript交互的代码时,需要遵守QML的特定限制和规则。
4.QML/JS/C++混合开发优势
- 界面与逻辑分离:
- QML(Qt Quick Markup Language)用于构建用户界面,它支持声明式编程,使得界面设计更加直观和灵活。
- C++则用于实现应用程序的后端逻辑和复杂计算,确保了高性能和稳定性。
- 这种分离使得开发人员可以专注于各自擅长的领域,提高了开发效率。
- 丰富的功能集成:
- QML和C++通过Qt框架紧密集成,QML可以直接调用C++编写的函数和对象,这使得开发者可以充分利用Qt提供的丰富功能,如网络通信、数据库操作、文件处理等。
- 同时,QML还支持JavaScript,允许在界面层实现更复杂的逻辑处理,如数据验证、动画效果等。
- 高效的跨平台开发:
- Qt框架支持多个操作系统,包括Windows、macOS、Linux以及移动操作系统(如Android和iOS),使用QML/JS/C++混合编程可以方便地开发出跨平台的应用程序。
- 灵活的扩展性:
- QML的扩展性很强,可以通过C++实现自定义的QML类型,这些类型可以在QML文件中直接使用,增强了QML的功能和灵活性。
- 良好的性能:
- C++作为底层语言,提供了高性能的计算能力,这对于处理大量数据或进行复杂计算的应用程序至关重要。
5.总结
不管是QT开发还是其它平台上开发,界面和业务逻辑分离都会是很重要的设计思想,QML/JS/C++混合开发很契合这个思想,如果你还没有用这种方式开发过,不妨试试看,可能会得到很多意外的收货。