QML编程入门

欢迎来到声明式UI语言QML的世界.在本入门教程中,我们使用QML创建一个简单的文本编辑器.阅读这个教程后,就可以使用QMLQt C++开发应用程序了.

安装

首先需要安装包含Qt QuickQt最新版本,现在是Qt4.7.安装教程包括安装说明书和不同平台的需求.

Qt Quick包含一个叫做QML声明式语言,Qt Declarative Module, QML Viewer.

QML构造用户界面

我们要构造的应用程序是一个简单的文本编辑器,可以加载,保存,以及执行一些文本处理.本教程包括两个部分.第一个部分使用QML设计应用程序布局和行为.第二个部分中使用Qt C++实现加载和保存文本.应用Qt元对象系统(Qt's Meta-Object System)可以将C++中的函数导入作为QML元素的属性进行访问.利用QMLQt C++,可高效的将界面逻辑与应用程序逻辑解耦.

最终代码见 examples/tutorials/gettingStarted/gsQml目录.首先需要在examples/tutorials/gettingStarted/gsQml/编译C++插件.将C++插件生成到QML文件可访问的目录中.

要启动文本编辑器,仅需要使用qmlviewer工具,并包含一个QML文件名称为参数.本教程的C++部分假设读者了解基本的Qt编译过程.

教程章节:

1.   定义按钮和菜单Defining a Button and a Menu

2.   实现菜单栏Implementing a Menu Bar

3.   创建文本编辑器Building a Text Editor

4.   美化文本编辑器Decorating the Text Editor

5.   使用Qt C++扩展QMLExtending QML using Qt C++

定义按钮和菜单

基本组件按钮

我们构建一个按钮作为文本编辑器程序的开始.功能上,按钮具有鼠标敏感区域和一个标签(label).用户点击按钮后执行一个动作.

QML,基本的可视项是Rectangle 元素. Rectangle 元素拥有控制外观和位置的属性.

import QtQuick 1.0

 

 Rectangle {

     id: simplebutton

     color: "grey"

     width: 150; height: 75

 

     Text{

         id: buttonLabel

         anchors.centerIn: parent

         text: "button label"

     }

 }

首先,  import QtQuick 1.0使qmlviewer工具导入我们稍后需要的QML元素.这行代码在每个QML文件中都是必须的.注意导入语句中包含Qt模块的版本号.

这个矩形包含一个唯一标识simplebutton,绑定到id属性上. Rectangle 元素设置属性值的方式为:属性名称,后跟冒号,而后是值.本例中,颜色grey赋给了矩形的color属性.同样设置了矩形的widthheight属性.

Text元素为不可编辑的文本框.将Text元素命名为buttonLabel.要给Text元素设置字符串内容需要给其text属性赋值.标签包含在Rectangle中,为了让其居中,设置Text元素的相对于父元素(simplebutton)的描点属性.为了让布局更加简单,描点可与其他项的描点绑定.

将上面的代码保存为SimpleButton.qml. 使用这个文件名做参数启动qmlviewer将看到带有文本标签的灰色矩形.

为了实现按钮的点击功能,我们可以处理QML的事件.QML事件与Qt的信号槽机制类似.触发信号时会调用与其连接的槽.

 Rectangle{

     id:simplebutton

     ...

 

     MouseArea{

         id: buttonMouseArea

 

         anchors.fill: parent //在矩形区域内描定Mouse Area的所有边

                 //onClicked处理按钮点击事件

         onClicked: console.log(buttonLabel.text + " clicked" )

     }

 }

在simplebutton中包含一个MouseArea元素.MouseArea元素描述一个可检测鼠标移动的交互区域.在按钮中我们将MouseArea完全平铺到其父对象simplebutton上.anchors.fill语法用来访问叫做anchors的组合属性中的fill属性.QMl使用基于描点的布局(anchor-based layouts)可将项描定到其他项上,创建出强健的布局.

当鼠标在MouseArea区域内移动时会触发很多信号.其中当用户点击被许可的鼠标按钮(默认是左按钮)时会调用onClicked信号.可以设置onClicked的处理事件.本例中,当在MouseArea中点击鼠标时会调用console.log()输出文本.这个函数可用于在调试时输出文本信息.

SimpleButton.qml中的代码实现在屏幕上显示一个按钮,并在鼠标点击时输出文本.

 Rectangle {

     id: button

     ...

 

     property color buttonColor: "lightblue"

     property color onHoverColor: "gold"

     property color borderColor: "white"

 

     signal buttonClick()

     onButtonClick: {

         console.log(buttonLabel.text + " clicked" )

     }

 

     MouseArea{

         onClicked: buttonClick()

         hoverEnabled: true

         onEntered: parent.border.color = onHoverColor

         onExited:  parent.border.color = borderColor

     }

 

     //determines the color of the button by using the conditional operator

     color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor

 }

完整功能的按钮代码在Button.qml中.上述的代码片段有些被省略,因为有些已经在上节中介绍过或与当前讨论无关.

使用带有属性类型名的语法来自定义属性.代码中,buttonColor属性,是color类型的,声明并赋值为"lightblue".buttonColor稍后用在确定按钮填充颜色的条件操作中.注意属性赋值可能使用等号(=)操作符,而属性绑定使用冒号(:)操作符.自定义属性使内部项可与外部交互.QML基本类型(QML types)包括int,string,real,以及variant类型.

绑定onEntered和onExisted信号处理按钮边框颜色,鼠标悬停在按钮上时为黄色,鼠标移出时恢复颜色.

Button.qml中定义了一个buttonClick()信号,将signal关键字放在信号名称前面.所有信号的事件处理器会被自动创建,名称前以on做前缀.例如,onButtonClick是buttonClick的处理器.onButtonClick被赋予一个可执行的动作.在这个按钮范例中,onClick按钮事件中调用了onButtonClick,简单的输出一行文本.onButtonClick信号使外部对象可处理按钮的鼠标区域事件.例如,如果项中含有多个MouseArea声明,buttonClick信号可以更好的区分多个MouseArea的信号处理代码.

现在我们了解了如何定义一个可处理鼠标移动的QML元素.在Rectangle中定义了一个文本标签,自定义其属性,处理鼠标的移动.在元素内部创建子元素的概念会贯穿整个文本编辑器应用程序.

按钮必须作为组件来执行动作才有使用价值.下节中将创建一个包含这种按钮的菜单.

创建菜单页

上节中阐述了如何创建元素并在单独的QML文件中设置行为.本节将说明如何导入QML元素,如何重用已有组件构建其他组件.

菜单显示一列内容,其中的每个项都可以执行一个动作.在QML中,有很多种方式创建菜单.首先,我们创建包含可执行不同动作按钮的菜单.菜单代码在FileMenu.qml中.

 import QtQuick 1.0                \\import the main Qt QML module

 import "folderName"            \\import the contents of the folder

 import "script.js" as Script        \\import a Javascript file and name it as Script

上述语法展示如何使用import关键字.这里需要使用不在同一目录中的JavaScript文件或QML文件.由于Button.qml与FileMenu.qml在同一目录中,不必导入Button.qml就可直接使用.可直接使用Button{}声明一个按钮元素,与Rectangle{}的声明一样.

FileMenu.qml:

 

     Row{

         anchors.centerIn: parent

         spacing: parent.width/6

 

         Button{

             id: loadButton

             buttonColor: "lightgrey"

             label: "Load"

         }

         Button{

             buttonColor: "grey"

             id: saveButton

             label: "Save"

         }

         Button{

             id: exitButton

             label: "Exit"

             buttonColor: "darkgrey"

 

             onButtonClick: Qt.quit()

         }

     }

FileMenu.qml中,声明了三个按钮元素.他们都在一个Row元素中声明的,这是一个定位器,将其子元素按行定位.Button声明在Button.qml中,与上节定义的Button.qml一致.新创建的按钮可设置属性绑定,在exitButton上增加了onButtonClick处理函数,由Button.qml中定义的onButtonClick来触发调用.

Row定义在Rectangle中,创建了包含一行按钮的矩形容器.这个额外的矩形采用间接的方式在菜单中组织了一行按钮.

这个阶段定义的编辑菜单非常简单.菜单按钮具有的标签为:Copy,Paste,Select All.

结合前面介绍的知识和定义的组件,我们已经可以组合这些菜单页生成一个菜单栏了,包括选择菜单的按钮,下面看看如何在QML中组织这些数据.

实现菜单栏

我们的文本编辑器程序需要使用菜单栏显示菜单.菜单栏可以切换不同的菜单,用户可选择显示哪个菜单.菜单间的切换比仅仅显示在一行中需要更多组织信息.QML使用模型和视图来组织数据和显示数据.

使用数据模型和视图

QML有用来显示数据模型(data models)的不同的数据视图(data views ).我们的菜单栏将显示列表中的菜单,头部显示一行菜单名称.菜单列表定义在VisualItemModel中.VisualItemModel元素中包含需要显示的元素,如Rectangle元素和导入的UI元素.其他模型类型如 ListModel 元素则需要显示其数据的代理(视图).

我们在menuListModel中定义两个可视化项,一个FileMenu一个EditMenu.并自定义这两个菜单,显示在ListView中.MenuBar.qml文件包含了简单的QML定义,编辑菜单定义在EditMenu.qml中.

     VisualItemModel{

         id: menuListModel

         FileMenu{

             width: menuListView.width

             height: menuBar.height

             color: fileColor

         }

         EditMenu{

             color: editColor

             width:  menuListView.width

             height: menuBar.height

         }

     }

ListView元素用来显示其代理模型.代理声明可在Row元素中显示模型元素,也可在Grid中显示模型元素.menuListModel已经定义需要显示的项了,因此,我们不再需要声明模型了.

     ListView{

         id: menuListView

 

         //Anchors are set to react to window anchors

         anchors.fill:parent

         anchors.bottom: parent.bottom

         width:parent.width

         height: parent.height

 

         //the model contains the data

         model: menuListModel

 

         //控制菜单开关的移动

         snapMode: ListView.SnapOneItem

         orientation: ListView.Horizontal

         boundsBehavior: Flickable.StopAtBounds

         flickDeceleration: 5000

         highlightFollowsCurrentItem: true

         highlightMoveDuration:240

         highlightRangeMode: ListView.StrictlyEnforceRange

     }

另外,ListView从Flickable继承,使列表可响应鼠标拖拽和手势.上述代码的最后部分设置flick属性可在视图中创建出期望的flick移动效果.特别是highlightMoveDuration属性设置flick的延续时间.较高的highlightMoveDuration值使菜单切换变慢.

ListView通过索引维护模型项目,模型中的可视项都是通过索引进行访问的,索引顺序与声明顺序相同.修改当前索引值会改变ListView中的高亮项.菜单栏的头部分证实了这个效果.这里一行中有两个按钮,点击后会改变当前菜单.点击fileButton将当前菜单切换为file菜单,由于FileMenu在menuListModel中第一个声明,因此其索引为0.同样,点击editButton将当前菜单切换为EditMenu.

labelList矩形的z属性值为1,强调其显示在菜单栏的最前面.项的z属性值越高越靠前显示.z属性值默认为0.

     Rectangle{

         id: labelList

         ...

         z: 1

         Row{

             anchors.centerIn: parent

             spacing:40

             Button{

                 label: "File"

                 id: fileButton

                 ...

                 onButtonClick: menuListView.currentIndex = 0

             }

             Button{

                 id: editButton

                 label: "Edit"

                 ...

                 onButtonClick:    menuListView.currentIndex = 1

             }

         }

     }

菜单栏可以通过flick操作或点击上面的菜单名称按钮.切换菜单效果很直观生动.

构造文本编辑区

声明TextArea

如果不包含文本编辑区域应用程序是不完整的.QML的TextEdit元素是多行可编辑的文本区域.TextEdit与Text元素不同,Text不允许用户直接编辑文字.

     TextEdit{

         id: textEditor

         anchors.fill:parent

         width:parent.width; height:parent.height

         color:"midnightblue"

         focus: true

 

         wrapMode: TextEdit.Wrap

 

         onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)

     }

编辑器有一系列字体颜色属性集和文字范围集.TextEdit区域放在一个flick区域上,如果文字光标在可视范围之外则可滚动文字.ensureVisible()函数用来检查光标是否在可视区域之外,并适当的移动文本区域.QML使用JavaScript作为其脚本语言,如前所述,JavaScript文件可以导入到QML文件中.

     function ensureVisible(r){

         if (contentX >= r.x)

             contentX = r.x;

         else if (contentX+width <= r.x+r.width)

             contentX = r.x+r.width-width;

         if (contentY >= r.y)

             contentY = r.y;

         else if (contentY+height <= r.y+r.height)

             contentY = r.y+r.height-height;

     }

为文本编辑器组合组件

现在已经使用QML创建了文本编辑器布局.文本编辑器有两个组件,已经创建的菜单栏和文本区域.QML中可以重用组件,因此导入组件并在必要时自定义组件,可使我们的代码更加简洁.文本编辑器将窗口分为两个部分:屏幕的三分之一用于菜单栏,三分之二显示文本区域.菜单栏显示在其他元素之前.

     Rectangle{

 

         id: screen

         width: 1000; height: 1000

 

         //the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the MenuBar

         property int partition: height/3

 

         MenuBar{

             id:menuBar

             height: partition

             width:parent.width

             z: 1

         }

 

         TextArea{

             id:textArea

             anchors.bottom:parent.bottom

             y: partition

             color: "white"

             height: partition*2

             width:parent.width

         }

     }

通过导入可重用组件,我们的文本编辑器代码看起来很简单.可以自定义主应用程序,不必再过多考虑已经定义行为的属性.使用这种方式,很容易创建应用程序布局和UI组件.

美化文本编辑器Decorating the Text Editor

实现弹性界面(Drawer Interface)

现在文本编辑器看起来很简单,我们需要美化一下.使用QML,可为文本编辑器声明转换(transition)和动画.菜单栏占用了三分之一的屏幕区域,最好能在我们需要的时候才显示.

我们可以添加一个弹性界面(drawer interface),当点击的时候可弹出或收缩菜单栏.在我们的实现中,只有一个小矩形响应鼠标点击.应用程序具有两个状态:菜单栏打开和菜单栏关闭状态.弹出项是一个具有很小高度的矩形.其中嵌入了一个Image元素,具中显示一个箭头图标.弹出信息保存在应用程序中标识屏幕状态,当鼠标区域点击时对其赋值.

     Rectangle{

         id:drawer

         height:15

 

         Image{

             id: arrowIcon

             source: "images/arrow.png"

             anchors.horizontalCenter: parent.horizontalCenter

         }

 

         MouseArea{

             id: drawerMouseArea

             anchors.fill:parent

             onClicked:{

                 if (screen.state == "DRAWER_CLOSED"){

                     screen.state = "DRAWER_OPEN"

                 }

                 else if (screen.state == "DRAWER_OPEN"){

                     screen.state = "DRAWER_CLOSED"

                 }

             }

             ...

         }

     }

状态用于收集配置信息,使用State元素声明.多个状态可组织在states属性中.本程序中,定义了两个状态,为DRAWER_CLOSEDDRAWER_OPEN. 项目配置声明在PropertyChanges元素中.在DRAWER_OPEN状态中,有四个项目的属性被改变.第一个是菜单栏,修改其y值为0.同样,textArea的位置变低.textArea,drawer和drawer中的图标,属性都会变化.

     states:[

         State {

             name: "DRAWER_OPEN"

             PropertyChanges { target: menuBar; y: 0}

             PropertyChanges { target: textArea; y: partition + drawer.height}

             PropertyChanges { target: drawer; y: partition}

             PropertyChanges { target: arrowIcon; rotation: 180}

         },

         State {

             name: "DRAWER_CLOSED"

             PropertyChanges { target: menuBar; y:-height; }

             PropertyChanges { target: textArea; y: drawer.height; height: screen.height - drawer.height }

             PropertyChanges { target: drawer; y: 0 }

             PropertyChanges { target: arrowIcon; rotation: 0 }

         }

     ]

状态改变有些唐突需要更加平滑的转换(transition).状态间的转换使用Transition元素定义,可绑定项目的转换属性.文本编辑器的状态可能转换到 DRAWER_OPENDRAWER_CLOSED.注意,转换需要一个from和一个to状态,但本例中使用*号表示转换应用到所有状态变化.

转换中,可以设计属性变化的动画.菜单栏切换位置从y:0到y:-pertition,我们可以使用NumberAnimation元素驱动变换.我们声明了动画的目标属性并设置动画延续时间及趋势曲线.趋势曲线控制动画速度和状态转换的差值行为.这里使用 Easing.OutQuint作为趋势曲线,在动画结束时移动速度变慢.请见QML's Animation文档.

     transitions: [

         Transition {

             to: "*"

             NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }

             NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }

             NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }

         }

     ]

让属性变化具有动画效果的另外一种方式是使用Behavior元素.转换(transition)只能用于状态(state)见得变化,Behavior可以设置一个一般的属性变化实现动画.在文本编辑器中,箭头使用NumberAnimating动画来设置器旋转属性.

 In TextEditor.qml:

 

     Behavior{

         NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }

     }

回顾组件的states和动画知识,有助于改善组件的外观.在Button.qml中,可在按钮点击时增加颜色和缩放属性变化.Color类型的动画效果使用ColorAnimation,数值类型动画效果使用NumberAnimation.下面的范例中,属性名称用于设置唯一的属性.

 In Button.qml:

     ...

 

     color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor

     Behavior on color { ColorAnimation{ duration: 55} }

 

     scale: buttonMouseArea.pressed ? 1.1 : 1.00

     Behavior on scale { NumberAnimation{ duration: 55} }

另外,给QML组件添加颜色效果,如渐变和透明效果可以增强外观效果.定义Gradient元素会覆盖元素的颜色属性.可使用GradientStop元素在渐变中声明一个颜色.渐变使用比例进行定位,值在0.0到1.0之间.

 In MenuBar.qml

     gradient: Gradient {

         GradientStop { position: 0.0; color: "#8C8F8C" }

         GradientStop { position: 0.17; color: "#6A6D6A" }

         GradientStop { position: 0.98;color: "#3F3F3F" }

         GradientStop { position: 1.0; color: "#0e1B20" }

     }

这个渐变用于菜单栏显示一个模拟的渐变深度.第一个颜色值从0.0开始,最后的颜色值在1.0结束.

继续另外的内容

最后我们定义了一个简单的文本编辑器用户界面.接着我们可以使用Qt和C++来实现应用程序的逻辑了.QML是一个很棒的原型工具,将UI设计与应用程序逻辑分离.

使用C++扩展QML

现在完成了文本编辑器的布局,需要在C++中实现文本编辑器的功能了.将QML与C++结合我们可在Qt中创建应用程序的逻辑.在C++应用程序中可使用 Qt's Declarative类创建QML上下文,并在图像场景中显示QML元素.或者导出C++代码生成qmlviewer工具可读的插件.本例中,要在C++中实现导入和保存功能,并导出一个插件.这样我们只需直接加载QML文件,而不是执行可执行文件.

向QML导出C++类

我们使用Qt和C++来实现加载和保存功能.C++类和函数在注册后可用于QML中.类需要编译为Qt插件,而且QML文件需要知道插件的位置.

在我们的应用程序中,需要创建如下项目:

1.   处理目录相关操作的Directory类

2.   模拟目录中文件列表的文件类,继承于QObject

3.   要注册到QML上下文中的插件类

4.   编译插件的Qt项目文件

5.   告诉qmlviewer工具从哪里可发现插件的qmldir文件

生成Qt插件

要生成插件,需要在Qt项目文件中做如下设置.首先,将必要的源文件,头文件,和Qt模块添加到项目文件中.所有的C++代码和项目文件都在filedialog目录中.

在filedialog.pro中:

 

     TEMPLATE = lib

     CONFIG += qt plugin

     QT += declarative

 

     DESTDIR +=  ../plugins

     OBJECTS_DIR = tmp

     MOC_DIR = tmp

 

     TARGET = FileDialog

 

     HEADERS +=     directory.h \

             file.h \

             dialogPlugin.h

 

     SOURCES +=    directory.cpp \

             file.cpp \

             dialogPlugin.cpp

特别的使用declarative模块并配置为插件使用库(lib)模版来编译Qt.将编译后的插件放在上级plugins目录中.

向QML注册类

dialogPlugin.h中:

 

     #include

 

     class DialogPlugin : public QDeclarativeExtensionPlugin

     {

         Q_OBJECT

 

         public:

         void registerTypes(const char *uri);

 

     };

在我们的插件类中,DialogPlugin继承于QDeclarativeExtensionPlugin.我们需要实现继承的函数 registerTypes().dialogPlugin.cpp代码如下:

 DialogPlugin.cpp:

 

     #include "dialogPlugin.h"

     #include "directory.h"

     #include "file.h"

     #include

 

     void DialogPlugin::registerTypes(const char *uri){

 

         qmlRegisterType(uri, 1, 0, "Directory");

         qmlRegisterType(uri, 1, 0,"File");

     }

 

     Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

registerTypes()函数向QML中注册我们的File和Directory类.这个函数需要一个类名称作为模版参数的类别,一个主版本号,一个次版本号,和我们的类的名称.

我们需要使用Q_EXPORT_PLUGIN2宏导出插件.注意dialogPlugin.h文件中,在类声明的最前面加入了Q_OBJECT宏.同时需要在项目文件上使用qmake生成必要的元对象代码.

在C++类中创建QML属性

我们可以使用C++和Qt的元对象系统(Qt's Meta-Object System)创建QML元素和属性.可使用信号和槽实现属性,使这些属性在Qt中有效.这些属性可用于QML.

在文本编辑器中,我们需要加载和保存文件.通常,这个功能需要一个文件对话框.幸运的是我们可以使用QDir,QFile和QTextStream来实现读取目录和输入输出流.

     class Directory : public QObject{

 

         Q_OBJECT

 

         Q_PROPERTY(int filesCount READ filesCount CONSTANT)

         Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)

         Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)

         Q_PROPERTY(QDeclarativeListProperty files READ files CONSTANT )

 

         ...

Directory类使用Qt的元对象系统注册完成文件操作的属性.Directory类作为插件,在QML中为Directory元素.属性列表中使用Q_PROPERTY宏声明的都是QML属性.
Q_PROPERTY连同读写函数将属性声明到Qt的元对象系统.例如,QString类型的filename属性,可使用filename()函数读取,使用setFilename()函数设置.另外还有个与filename属性有关的信号叫做filenameChanged(),属性修改时被触发.读写函数在头文件中的public段中声明.

同样,我们还声明了其他属性.filesCount属性指示目录中的文件数量.filename属性设置当前选中的文件名称,加载或保存的文件内容存储在fileContent属性中.

     Q_PROPERTY(QDeclarativeListProperty files READ files CONSTANT )

files属性是目录中被过滤的文件列表.Directory类中过滤掉非文本文件.只有后缀为.txt的文件有效.而且,在C++中声明的QDeclarativeListProperty属性在QML中可作为QList使用.这个模版对象需要从QObject继承,因此,File类必须从QObject继承.在Directory类中,文件对象列表存储在叫做m_fileList的QList中.

     class File : public QObject{

 

         Q_OBJECT

         Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

 

         ...

     };

这些属性可在QML中作为Directory元素的属性.注意我们没有在C++中创建唯一标识id属性.

     Directory{

         id: directory

 

         filesCount

         filename

         fileContent

         files

 

         files[0].name

     }

因为QML使用Javascript语法和结构体,我们可以遍历文件列表并获取其属性.如获取第一个文件属性,可以调用files[0].name.

通常C++函数也可在QML中访问.文件加载和保存的函数在C++中使用Q_INVOKABLE 宏定义.而且,我们可以讲函数声明为槽,函数可在QML中直接访问.

 In Directory.h:

 

     Q_INVOKABLE void saveFile();

     Q_INVOKABLE void loadFile();

Directory类也会在文件内容发生变化的时候通知其他类.这个特性是使用信号实现的.如前面提到的,QML信号有一个相应的处理器,名称以on为前缀.信号叫做directoryChanged,在目录更新时触发.更新操作简单的从新加载目录内容并更新目录中有效的文件列表.QML项目设置onDirectoryChanged信号的处理槽函数就可得到通知.

列表属性需要特殊对待.因为列表属性使用回调来方位和修改列表内容.列表属性是QDeclarativeListProperty类型.当访问列表时,访问器函数返回一个QDeclarativeListProperty对象.模版类型File必须从QObject继承.而且,要创建QDeclarativeListProperty,列表的访问器和修改器需要传递构造函数作为函数指针.本例中列表为QList,需要存放File指针.

QDeclarativeListProperty的构造函数和Directory实现:

     QDeclarativeListProperty  ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )

     QDeclarativeListProperty( this, &m_fileList, &appendFiles, &filesSize, &fileAt,  &clearFilesPtr );

构造函数传递一个可添加列表,返回列表数量,使用索引获取项,情况列表的函数指针.只有添加函数式必须的.注意函数指针必须与 AppendFunction, CountFunction, AtFunction, or ClearFunction匹配.

     void appendFiles(QDeclarativeListProperty * property, File * file)

     File* fileAt(QDeclarativeListProperty * property, int index)

     int filesSize(QDeclarativeListProperty * property)

     void clearFilesPtr(QDeclarativeListProperty *property)

为简化文件对话框,Directory类将不以.txt为后缀的文件过滤掉.如果文件名称不以.txt为后缀,就不能在file对话框中可见.同样保存文件的实现确保在文件名后加上.txt后缀.Directory使用QTextStream读取文件并向文件中输出内容.

使用Directory元素,可以获取文件列表,知道应用程序目录中的文本文件数量,获取文件名称和文件内容字符串,当目录内容发生变化时得到通知.

生成插件,在filedialog.pro上运行qmake,然后运行make生成插件并拷贝到plugins目录.

在QML中导入插件

qmlviewer工具导入与应用程序同目录的文件.也可创建qmldir文件来指定希望导入的QML文件位置.qmldir文件可也指定插件位置和其他资源.

qmldir文件内容:

 

     Button ./Button.qml

     FileDialog ./FileDialog.qml

     TextArea ./TextArea.qml

     TextEditor ./TextEditor.qml

     EditMenu ./EditMenu.qml

 

     plugin FileDialog plugins

插件正是我们上面创建的FileDialog,在项目文件中指定了TARGET域.插件编译后的文件在plugins目录中.

向文件菜单整合文件对话框

文件菜单FileMenu需要显示一个FileDialog元素,其中显示了目录中含有的文件列表,用户可在列表中点击选择文件.同时也要设置保存,加载和新建按钮的响应事件.FileMenu中包含一个可编辑的文本输入框,让用户使用键盘输入文件名称.

Directory元素在FileMenu.qml文件中使用,并在目录中内容刷新时通知FileDialog元素.通知是使用onDirectoryChanged信号实现的.

 In FileMenu.qml:

 

     Directory{

         id:directory

         filename: textInput.text

         onDirectoryChanged: fileDialog.notifyRefresh()

     }

为了简化应用程序,文件对话框总是显示,并过滤掉不已.txt为后缀的文件.

 In FileDialog.qml:

 

     signal notifyRefresh()

     onNotifyRefresh: dirView.model = directory.files

FileDialog元素显示从files属性中读取的目录内容.files作为GridView(可将代理内容显示在网格中)元素的模型.由代理处理模型的外观,而文件对话框只简单的在中间生成一个带有文字的网格.点击文件名称使文件名称矩形高亮.FileDialog在notifyRefresh信号触发时得到通知,重新加载目录中的文件.

 FileMenu.qml:

 

     Button{

         id: newButton

         label: "New"

         onButtonClick:{

             textArea.textContent = ""

         }

     }

     Button{

         id: loadButton

         label: "Load"

         onButtonClick:{

             directory.filename = textInput.text

             directory.loadFile()

             textArea.textContent = directory.fileContent

         }

     }

     Button{

         id: saveButton

         label: "Save"

         onButtonClick:{

             directory.fileContent = textArea.textContent

             directory.filename = textInput.text

             directory.saveFile()

         }

     }

     Button{

         id: exitButton

         label: "Exit"

         onButtonClick:{

             Qt.quit()

         }

     }

FileMenu现在具备了各种功能.saveButton将TextEdit中的文本传递给目录文件的Content属性,并从可编辑的文本输入元素中复制文件名称.最后,按钮调用saveFile()函数,保存文件.loadButton执行方式同理.新建动作清空TextEdit的内容.

EditMenu中的按钮调用TextEdit的函数来拷贝,粘贴,全选文本编辑器中的内容.

完成文本编辑器

应用程序已经可以作为简单的文本编辑器了,可输入文本并保存到文件中.也可从文件中加载并手动修改文本.

运行文本编辑器

在运行文本编辑器前需要编译C++插件(文件对话框).要编译,进入gsQml目录,运行qmake,依据你使用的平台在使用make或nmake进行编译.启动qmlviewer,打开texteditor.qml文件.

源码在examples/tutorials/gettingStarted/gsQml目录中.

你可能感兴趣的:(Qt,QuickQML,跨平台-QT)