QML负责GUI,C++负责业务逻辑的范例

在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 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开头,否则访问不到图片文件.

1347946124_1097.jpg