在declarative目录中,有个minehunt范例,实现了在C++中加载QML界面,并用C++来处理QML界面上的鼠标动作.这种思路和传统的GUI相似,感觉比较顺畅.否则运行一个QML,还要使用qmlviewer,上面带一大堆菜单按钮,看着够别扭的.
在main函数中,创建了一个QDeclarativeView实例,这个实例负责显示QML界面.接着创建负责处理业务逻辑的MinehuntGame实例,并在view加载QML文件后,将其设置为引擎的上下文对象.这样就可以直接在QML中使用MinehuntGame类中的属性和方法了.感觉设置上下文后,将上下文类实例与QML界面做了融合,QML中的鼠标点击等事件就可以调用类中方法进行处理,并可以绑定到实例的属性.
#include <QtGui/QApplication>
#include <QtDeclarative/QDeclarativeView>
#include <QtDeclarative/QDeclarativeContext>
#include <QtDeclarative/QDeclarativeEngine>
#include "minehunt.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDeclarativeView canvas;
qmlRegisterType<TileData>();//注册TileData类,这个类不需要在QML中实例化.
MinehuntGame* game = new MinehuntGame();
canvas.engine()->rootContext()->setContextObject(game);
canvas.setSource(QString("qrc:minehunt.qml"));
QObject::connect(canvas.engine(), SIGNAL(quit()), &app, SLOT(quit())); //QML退出,应用程序也退出
canvas.setGeometry(QRect(100, 100, 450, 450));
canvas.show();
return app.exec();
}
在程序中定义了代表扫雷界面中每个方块的TileData类,用来记录这个方块的状态,如做标记的,被点开的,或有雷的.那么类的实例是如何与QML进行关联同步的呢?在MinehuntGame中定义了一个属性QDeclarativeListProperty<TileData> tiles(),与_tiles成员绑定,并在类构造时向tiles中填充多个TileData实例.在QML中可以看到如下声明:
Grid {
anchors.horizontalCenter: parent.horizontalCenter
columns: 9; spacing: 1
Repeater {
id: repeater
model: tiles
delegate: Tile {}
}
}
这样就按MinehuntGame实例中的tiles属性进行构造Grid中的元素了,每个TileData实例生成一个Tile组件.在操作Tile组件时又会反过来调用MinehuntGame中的方法.先看看那个表情图标,点击时会重置界面.
Image {
anchors.bottom: field.bottom; anchors.bottomMargin: 15
anchors.right: field.right; anchors.rightMargin: 20
source: isPlaying ? 'MinehuntCore/pics/face-smile.png' ://isPlaying,hasWon是MinehuntGame实例中的属性,这里做了绑定,如果实例中的属性发生变化,则图标自动变化
hasWon ? 'MinehuntCore/pics/face-smile-big.png': 'MinehuntCore/pics/face-sad.png'
MouseArea { anchors.fill: parent; onPressed: reset() }//点击的时候调用MinehuntGame实例的reset方法,重置雷区,上面也有个关于雷区的绑定,重置后界面自动更新
}
在看看点击雷区的一个方块时的动作触发过程.这里有两个成员对象,index和modelData,都是和模型绑定有关的,index表示当前选中的模型元素索引,modelData表示当前选中子模型对应的元素.在QML中可通过modelData访问TileData元素属性.下面的声明中指定,鼠标左键点击调用MinehuntGame的flip方法,右击调用flag方法.在这两个C++函数中判断是否触雷等逻辑,并修改类实例的状态,通过绑定,界面元素自动更新.
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
field.clickx = flipable.x//鼠标点击时设置主界面的clickx,clicky属性
field.clicky = flipable.y
var row = Math.floor(index / 9)
var col = index - (Math.floor(index / 9) * 9)
if (mouse.button == undefined || mouse.button == Qt.RightButton) {
flag(row, col)
} else {
flip(row, col)
}
}
onPressAndHold: {
field.clickx = flipable.x
field.clicky = flipable.y
var row = Math.floor(index / 9)
var col = index - (Math.floor(index / 9) * 9)
flag(row, col)
}
}
那么触雷后的例子系统是如何声明的呢?
transitions: Transition {
SequentialAnimation {
ScriptAction {
script: {
var ret = Math.abs(flipable.x - field.clickx)
+ Math.abs(flipable.y - field.clicky);
if (modelData.hasMine && modelData.flipped)
pauseDur = ret * 3
else
pauseDur = ret
}
}
PauseAnimation {
duration: pauseDur
}
RotationAnimation { easing.type: Easing.InOutQuad }
ScriptAction { script: if (modelData.hasMine && modelData.flipped) { expl.explode = true } }//这里指定如果modelData即TileData数据对象属性hasMine或flipped为true,则触发例子效果.
}
}
expl的定义:Explosion { id: expl }.
Explosion的定义:
import QtQuick 1.0
import Qt.labs.particles 1.0
Item {
property bool explode : false
Particles {//定义了一个粒子对象
id: particles
width: 40
height: 40
lifeSpan: 1000
lifeSpanDeviation: 0
source: "pics/star.png"//粒子图像
count: 0
angle: 270
angleDeviation: 360
velocity: 100
velocityDeviation: 20
z: 100
}
states: State { name: "exploding"; when: explode
StateChangeScript {script: particles.burst(200); }
}
}
总结:要向将QML和C++结合起来,实现传统的开发方式,核心的代码就是canvas.engine()->rootContext()->setContextObject(game);这样就可以在QML中调用game实例的属性和函数了.
**************测试如何在C++中获取TextInput元素中输入的内容**************
根据上面的范例分析可以在QML中访问C++中定义的函数或属性,那么C++中怎么获取到QML中的界面内容呢?网上说有三种方法,一种是在C++的插件获取,一种是在C++中解析QML中的元素,最后一种是在C++中定义带参数的槽函数,在QML中的事件中调用这个槽,并将TextInput中的内容作为参数传到C++中.感觉最后一种方法最方便了,因此做了如下测试:
定义QML文件:
import QtQuick 1.0
Rectangle{
id: window
width: 240; height: 250
BorderImage{
y:0
id:button
source:"pics/back.png"
MouseArea{
id:mouseArea
anchors.fill:parent
onClicked:buttonClicked(textInput.text)//按钮点击的时候调用C++中的buttonClicked槽函数
}
Rectangle{
id:shade
anchors.fill:button; radius: 10; color: "black"; opacity: 0
}
states:State{
name:"pressed";when: mouseArea.pressed == true
PropertyChanges{ target: shade; opacity: 0.4}
}
}
BorderImage
{
source: "pics/back.png";width:82;height:22;y:50
TextInput{
id:textInput
text: qsTr("text")
y:1;x:1;width:80;height:20;
}
}
}
C++类:
头文件
#ifndef QMLCONTROL_H
#define QMLCONTROL_H
#include <QObject>
class QmlControl : public QObject
{
Q_OBJECT//必须加上Q_OBJECT宏,否则在QML中无法触发buttonClicked函数
public:
QmlControl(void);
~QmlControl(void);
public slots:
Q_INVOKABLE void buttonClicked(QString textInput);
};
#endif
cpp文件
#include "QmlControl.h"
#include <QMessageBox>
QmlControl::QmlControl(void)
{
setObjectName("mainObject");
}
QmlControl::~QmlControl(void)
{
}
void QmlControl::buttonClicked(QString textInput)
{
QMessageBox::information(NULL, "", textInput);
}
界面上放一个QDeclarativeView控件,并在构造函数中加载QML,设置引擎中的对象:
GetQMLInputTextValue::GetQMLInputTextValue(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
qmlRegisterType<QmlControl>();
QmlControl *ctrl = new QmlControl();
ui.declarativeView->engine()->rootContext()->setContextObject(ctrl);
ui.declarativeView->setSource(QString("qrc:/Resources/test.qml"));
connect(ui.declarativeView->engine(),SIGNAL(quit()), qApp, SLOT(quit()));
}
注意我将QML和其中的图片放入资源中,这里引用QML的时候需要以qrc开头,否则访问不到图片文件.