在这篇文章中,我讲述如果使用QML来调用C++来扩展我们的应用。我们知道,在许多的场景中,QML虽然很强大,在UI方面是非常突出的,但是,有的时候,我们可能会想到应用我们的C++代码。这有一下方面的原因:
1)我们已经有成熟的C++引擎设计或协议等。比如我们已经用C++设计好了我们的Fetion协议代码。我们没要再用另外一个低性能的语言来重新写一边
2)有些功能我们没有办法使用Javascript来完成,必须使用系统或专用的一些功能包来完成。这时我们可以使用C++语言来完成我们需要的功能
3)有些代码对计算的要求非常高,需要使用大量的CPU时间,这时,我们可以考虑使用C++来完成这部分的工作
事实上,在Ubuntu OS上,我们可以可以使用QML/Javascript来创建非常炫的UI,同时我们也可以使用C++来完成我们特有的一些功能(比如需要大量计算)。两者之间的结合是非常自然的和密切的。你可以想像C++是可以用来拓展QML的功能的。
在这里,我们来创建一个应用读取Linux的一些环境变量。通过这个练习,我们可以掌握怎么在QML文件中调用C++的代码。
1)创建一个基本的应用
- 启动Ubuntu IDE,选定"App with QML extension Library"模版应用
- 设定应用的设置
- 选择所需要运行的架构。这里我们选择"Desktop"及在手机上能运行的“armhf"架构
至此,我们基本上已经产生了一个可以在手机和电脑上运行的应用。选择合适的架构,点击IDE左下角的运行键
如果你们在运行到这里,还有什么问题的话,请参照我先前的文章正确安装你的环境。
2)添加C++功能模块
首先我们找到项目的子目录“
ReadEnv/backend/modules/ReadEnv”,在这里我们创建两个文件“readenv.cpp”及“readenv.h”。他们的内容如下:
#ifndef READ_ENV_H
#define READ_ENV_H
#include <QObject>
class ReadEnv : public QObject
{
Q_OBJECT
public:
explicit ReadEnv(QObject *parent = 0);
Q_INVOKABLE QString getenv(const QString envVarName) const;
private:
};
#endif // READ_ENV_H
#include "readenv.h"
#include <QDebug>
ReadEnv::ReadEnv(QObject *parent) : QObject(parent)
{
}
QString ReadEnv::getenv(const QString envVarName) const
{
QByteArray result = qgetenv(envVarName.toStdString().c_str());
QString output = QString::fromLocal8Bit(result);
qDebug() << envVarName << " value is: " << output;
return output;
}
这是一个非常简单的C++类。这个类必须是从QObject类继承,这样可以使得这个类可以使用Qt的signal-slot机制。如果大家对这个还不熟的话,可以参阅一些资料。对一个这样的类来说,如果它的函数或方法可以被QML调用的话,必须定义为“Q_INVOKABLE”。另外所有被定义为"slot"的函数或方法也可以被QML中的JS直接调用。我们可以通过更多的例子来说明这个问题。“getenv"方法很简单。它直接读取所需要的环境变量的值,并通过转换返回。
我们也同时打开该目录下的另外一个文件"backend.cpp"。经过修改的文件显示如下:
#include <QtQml>
#include <QtQml/QQmlContext>
#include "backend.h"
#include "mytype.h"
#include "readenv.h"
void BackendPlugin::registerTypes(const char *uri)
{
Q_ASSERT(uri == QLatin1String("ReadEnv"));
qmlRegisterType<MyType>(uri, 1, 0, "MyType");
qmlRegisterType<ReadEnv>(uri, 1, 0, "ReadEnv");
}
void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
{
QQmlExtensionPlugin::initializeEngine(engine, uri);
}
这里其实也没有做什么。加入了一句
qmlRegisterType<ReadEnv>(uri, 1, 0, "ReadEnv");
这里的第一个“ReadEnv"对应于我们所定义的类的名称,而不是项目的名称。第二个“ReadEnv"究其实是在QML使用的来实例化一个这样的类。它的名称可以和实际的类不同名字,也可以是任何一个你喜欢的名称。不过你必须也要在QML文件中做相应的修改。通过这样的注册,该类就可以在QML中进行实例化,并引用!等我们已经把源码已经做好,我们必须把它加入到我们的项目中进行编译。我们打开位于"backend"目录下的”CMakeLists.txt"文件中,加入如下的语句:
set(
modules/ReadEnv/mytype.cpp
modules/ReadEnv/readenv.cpp
)
我们可以通过关掉项目再重新打开应用的方式,使得项目中的文件目录得到更新。也可以使用点击右键的方式使新加入的文件得到正确的显示:
这样,我们的C++部分的代码基本上就已经完成了。
3)修改QML部分的代码
上面我们已经完成了基本的C++部分的代码,我们在这里继续来完成QML部分的界面。首先我们来熟悉一下QML的layout。我们知道layout可以很方便地帮我们管理我们的控件,并在适当的时候自动适配合适的屏幕尺寸及手机方向的改变。QML中有Column及Row来帮我们管理我们的控件:
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Label {
text: "Value: "
fontSize: "large"
}
TextArea {
id:value
readOnly: true
contentWidth: units.gu(40)
contentHeight: units.gu(80)
}
}
这里我们并排放上两个控件,一个是Label,另外一个是"TextArea"。通过这样的组合,这整个又可以看做是一个复合的控件,里面有两个控件。它整个又可以被放到它外面的布局管理器(Row, Grid, Flow, Column等)中。通过我的修改,我们创建了如下的界面及界面。
我们通过修改“ReadEnv.qml” 来实现界面。具体的代码如下:
import QtQuick 2.0
import Ubuntu.Components 0.1
import ReadEnv 1.0
import "ui"
MainView {
// objectName for functional testing purposes (autopilot-qt5)
objectName: "mainView"
// Note! applicationName needs to match the "name" field of the click manifest
applicationName: "com.ubuntu.developer.liu-xiao-guo.ReadEnv"
anchorToKeyboard: true
/*
This property enables the application to change orientation
when the device is rotated. The default is false.
*/
//automaticOrientation: true
width: units.gu(100)
height: units.gu(75)
ReadEnv {
id: readEnv
}
Column {
// anchors.fill: parent
// anchors.verticalCenter: parent.verticalCenter
// anchors.horizontalCenter: parent.horizontalCenter
anchors.centerIn: parent
spacing: 20
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Label {
text: "Variable:"
fontSize: "large"
}
TextField {
id:input
text:"PWD"
focus: true
placeholderText: "Please input a env variable"
Keys.onPressed: {
if (event.key === Qt.Key_Return) {
print("Enter is pressed!")
value.text = readEnv.getenv(input.text)
}
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Label {
text: "Value: "
fontSize: "large"
}
TextArea {
id:value
readOnly: true
contentWidth: units.gu(40)
contentHeight: units.gu(80)
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "Get"
onClicked: {
print("Button is clicked!")
value.text = readEnv.getenv(input.text)
}
}
}
}
特别注意的是:
- 在调用backend的库时,我们必须使用如下的动作来完成。
import ReadEnv 1.0
这里的“ReadEnv",实际上对应于在“backend.cpp"中的如下语句中的“ReadEnv"。如果该处发生变化,import语句也得变化
Q_ASSERT(uri == QLatin1String("ReadEnv"));
我们也可以在手机的安装环境中可以看出。"
ReadEnv"同时也是在“lib"下的一个目录名称。其实这个理解是非常重要的。所有的import中的文字其实就是表示的是路经。它标
示了库所在的位置
- 我们通过如下的语句来对"ReadEnv"类进行实例化。我们在实例化的同时,也给了一个ID,这样就可以在其他的地方引用了。在本应用中,我们在Button中调用
ReadEnv {
id: readEnv
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "Get"
onClicked: {
print("Button is clicked!")
value.text = readEnv.getenv(input.text)
}
}
这个应用的运行效果为:
好了今天我们已经完成了一个练习。它实现了从QML中调用C++代码。我们以后还可以添加更多的功能到这个应用中。本例程的代码可以在如下网站找到:
bzr branch
lp:~liu-xiao-guo/debiantrial/readenv