欢迎来到声明式UI语言QML的世界.在本入门教程中,我们使用QML创建一个简单的文本编辑器.阅读这个教程后,就可以使用QML和Qt C++开发应用程序了.
安装
首先需要安装包含Qt Quick的Qt最新版本,现在是Qt4.7.安装教程包括安装说明书和不同平台的需求.
Qt Quick包含一个叫做QML的声明式语言,Qt Declarative Module,和 QML Viewer.
QML构造用户界面
我们要构造的应用程序是一个简单的文本编辑器,可以加载,保存,以及执行一些文本处理.本教程包括两个部分.第一个部分使用QML设计应用程序布局和行为.第二个部分中使用Qt C++实现加载和保存文本.应用Qt元对象系统(Qt's Meta-Object System)可以将C++中的函数导入作为QML元素的属性进行访问.利用QML和Qt 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属性.同样设置了矩形的width和height属性.
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_CLOSED 和DRAWER_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_OPEN 或DRAWER_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 <QtDeclarative/QDeclarativeExtensionPlugin>
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 <QtDeclarative/qdeclarative.h>
void DialogPlugin::registerTypes(const char *uri){
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
qmlRegisterType<File>(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<File> 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<File> 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<File>类型.当访问列表时,访问器函数返回一个QDeclarativeListProperty<File>对象.模版类型File必须从QObject继承.而且,要创建QDeclarativeListProperty,列表的访问器和修改器需要传递构造函数作为函数指针.本例中列表为QList,需要存放File指针.
QDeclarativeListProperty的构造函数和Directory实现:
QDeclarativeListProperty ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );
构造函数传递一个可添加列表,返回列表数量,使用索引获取项,情况列表的函数指针.只有添加函数式必须的.注意函数指针必须与 AppendFunction, CountFunction, AtFunction, or ClearFunction匹配.
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *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目录中.