GettingstartqmlChinese
本文翻译自:“Getting Started Programming With QML”:http://doc.qt.nokia.com/4.7/gettingstartedqml.html
QML编程入门
欢迎来到QML的世界,QML是说明式界面语言。在本文里,我们用QML创建一个简单的文本编辑器。读了本文后,您就可以用QML和Qt C++开发你自己的应用程序了。
用QML来创建用户界面
我们要创建的程序是一个简单的文本编辑器,它可以读,写和其他的文本操作。本教程包含两部份。第一部分是用QML设计程序界面和功能。第二部分是用Qt C++来实现文件读和写。使用Qt的Meta-Object系统,我们可以把C++的函数当做QML元素可以使用的属性。使用QML和Qt C++,我们可以有效地把界面逻辑和程序逻辑分开。
要想运行QML程序,可以运行qmlviewer再加上QML文件名作为参数。这个教程的C++部分需要你拥有基本的Qt程序编译知识。
教程章节
1 定义按钮和菜单
2 实现菜单条
3 创建文本编辑器
4 装饰文本编辑器
5 用Qt C++扩展QML
定义按钮和菜单
基本的组件 — 按钮
我们现开始创建一个按钮。从功能上来说,一个按钮有一个鼠标感应区和一个标签。当用户按按钮时,按钮执行动作。
i在QML里,基本的可视元素是Rectangle. Rectangle元素有属性来控制外观和位置。
首先要import Qt 4.7,
这样qmlviewer可以导入我们以后需要的QML元素。每一个QML文件必须有这一行。需要注意Qt的版本号要在这一个行里。
这个简单的rectangle有一个属性id, simplebutton,
Rectangle的属性和值绑定在一起,先是属性,冒号,然后是值。在例子程序里,颜色grey绑订到Rectangle的color属性。同理,我们也绑订width和height.
Text元素是不可编辑的区域。我们叫它buttonLabel.为了设置Text区域的字符,我们绑订一个值到text属性。为了使标签在Rectangle的中心,我们使用Text元素的anchor固定到它的parent,这里是simplebutton.Anchor也可以绑订其他元素的anchor.这样界面设计就非常简单了。
我们把代码存到SimpleButton.qml.
运行qmlviewer,用这个文件明作为参数。就可以显示出下面的灰色矩形和文本标签。
为了实现按钮功能,我们可以使用QML事件处理机制。QML事件处理机制和Qt的signal-slot的机制非常像。信号被出发,连接好的slot函数被调用。
simplebutton中包含了MouseArea元素。MouseArea元素描述了检测鼠标移动的交互区域。对于这个按钮,我们把整个MouseArea固定在它的parent – simplebutton. anchors.fill语句可以读取属性anchors的fill属性。QML用基于anchor的布局来把几个项目固定另外一个项目上,这样可以创建稳定的布局。
如果在指定的MouseArea边界里移动鼠标,MouseArea有很多被调用的信号处理函数,其中之一是onClicked,当鼠标按钮被按下时,onClicked被调用。缺省是鼠标左键。我们可以把动作绑定到onClicked处理函数。在我们的例子里,当鼠标在MouseArea被按下的时候,console.log()输出文本。在调试和输出文本时,函数console.log()是一个在非常有用的工具。
SimpleButton.qml的代码可以在屏幕上显示按钮,并且鼠标点击时输出文本。
功能完整的按钮代码在Button.qml中。本文中的代码片断有遗漏,有些代码在前面的章节里已经介绍过,有些代码根本文无关。
自定义的属性用property type name语句来声明。在代码中,声明了buttonColor属性,并给它赋值lightblue. buttonColor在后面会在决定按钮的填充颜色条件语句中被用到。注意除了用冒号来赋值绑定,给属性赋值可以等号=。自定义属性允许内部属性被Rectangle的外部来访问。基本的QML类型有int,string, real, 和variant.
通过把颜色绑定到onEntered和onExited信号处理函数,鼠标移到按钮上,按钮的边界会变成黄色,当鼠标移出按钮,边界会变成原来的颜色。
通过把signal放在信号名字的前面来声明buttonClick()信号。所有信号的处理函数都会被自动创建,信号的名字都是以on开始。在结果上onButtonClick就是buttonClick的处理函数。onButtonClick被指定去执行一个动作。在我们的按钮例子里,onClicked鼠标处理函数调用onButtonClick, onButtonClick会显示文本。onButtonClick可以使外部对象很容易访问按钮的鼠标区域。例如,声明多个MouseArea和一个buttonClick信号的对象可以在几个MouseArea信号处理函数中进行更好的区分。
我们现在基本了解用QML来实现可以处理鼠标移动的对象。我在Rectangle中创建一个Text标签,定义它的属性,实现处理鼠标移动的行为。我们在整个文本编辑器中将很多次在对象中创建对象。
如果这个按钮不想一个组件一样执行操作,那是没什么用的。在下一节里我们要创建包含几个按钮的菜单。
创建菜单
现在我们介绍了在一个QML文件里创建元素和定制行为。在这部分,我们介绍如何导入QML元素,如何重用元素来创建新的元素。
菜单显示列表的内容,列表中每一项都可以执行操作。在QML中我们有几种方法来创建菜单。第一种创建包含按钮的菜单,每个按钮可以执行特定的操作。菜单的代码在FileMenu.qml中.
上面的代码表示如何使用import关键字。这里面需要使用javascript文件或者在另外的目录下的QML文件。因为Button.qml和FileMenu.qml在同一个目录下,所以我们不用导入Button.qml.我们可以直接通过声明Button{}来创建Button元素,这跟Rectangle{}声明类似。
在FileMenu.qml中,我们在Row元素里声明了3个Button元素。Row元素里有三个垂直的子对象。Button在我们前面介绍的Button.qml里声明。可以在新建的按钮里声明新的属性绑定,覆盖Button.qml里的属性。名字叫做exitButton被点击时,程序退出。注意除了调用exitButton里的函数onButtonClickon,还有在Button.qml里的信号处理函数onButtonClick也会被调用。
在Rectangle里声明了Row,为一排按钮创建了一个矩形容器。这个额外的矩形间接地在菜单里组织了按钮。
编辑菜单的声明也非常类似。菜单包含按钮,按钮的标签是拷贝,粘贴和全选。
我们已经知道了如何导入,定制刚才创建的组件,我们可以把这些菜单页组合起来创建一个菜单条。菜单条包含选择菜单的按钮,下面我们看看如何用QML组织数据。
实现菜单条
我们的文本编辑器需要用菜单条来显示菜单。菜单条要在不同的菜单间切换,用户可以选择那个菜单来显示。菜单切换意为着菜单需要比一排菜单更复杂的结构。QML用Models和Views来组织和显示数据。
使用数据的Models和Views
QML有不同的显示data Models的data views. 我们的菜单条在列表中显示菜单,用头来显示一排菜单的名字。在VisualItemModel里面声明菜单列表。VisualItemModel元素包含有视图的条目,比如Rectangle元素和导入的UI元素。其他的类型的Model需要一个代理来显示它们的数据。
我们在menuListModel声明两个可视条目,FileMenu和EditMenu。我们定制了两个菜单,使用一个ListView来显示他们。MenuBar.qml文件包含了QML声明,在EditMenu.qml里定义了一个简单的编辑菜单。
ListView元素使用代理来显示Model。代理可以声明Model的条目,然后显示一排或者显示在网格里。在menulistModel里已经有了可视项目,我们不要再声明代理了。
另外,ListView从Flickable派生出来,因此列表可以响应鼠标拖放和其他动作。上面代码的最后一部份设置Flickable属性来给视图创建希望的flicking动作。特别地,higlightMOveDuration属性改变flick变化的时间。更大的highlightMoveDuration值会使菜单切换变得更慢。
ListView包含数据项目,通过index以声明顺序来访问每个项目。改变currentIndex可以改变ListView里面被选中的项目。菜单条的头就是这样。在一排里有两个按钮,当点击按钮时,当前的菜单就被改变。fileButton把当前的菜单变成文件菜单,index是0,因为FileMenu在menuListModel第一个被声明。类似地,editButton把当前的菜单变成EditMenu.
labelList矩形的z值是1,表明它被显示在菜单条的前面。有更高z值的项目显示在低z值项目的上面。缺省的z值是0.
我们可以点击上面的菜单名字来弹出刚才创建的菜单条。切换菜单的感觉很直接,响应很快。
创建文本编辑器
声明文本区域
我们的文本编辑器必须有文本区域。QML的TextEdit元素可以容纳多行可编辑的文本区域。TextEdit和Text元素不同,Text元素部允许编辑文本。
编辑器有字体属性,可以自动换行。TextEdit区域在Flickable区域里面,如果光标出了可见区域,TextEdit可以自动滚动。ensureVisible()函数可以查看光标在边界外面,然后相应地移动文本区域。QML用Javascript的语法,Javascript文件可以被导入到QML文件里。
把组件整合成文本编辑器
我们现在可以用QML来创建文本编辑器的界面。编辑器有两个组件,菜单条和文本区域。QML可以通过导入和重新定制来重用组件,使我们的代码更简单。文本编辑器把窗口分成两部份,三分之一是菜单条,三分之二是文本区域。菜单条显示在其他元素之上。
通过导入重用组件,我们的TextEditor代码非常简单。我们可以定制主程序,而不比担心已经定义的属性。用种方法可以很容易创建UI界面。
装饰编辑器
实现抽屉接口
我们的编辑器太简单了,我们要装饰它。应用QML我们可以实现渐变和动画。菜单条占了三分之一窗口,应该只有用它的时候才显示它。
我们可以加一个抽屉接口,当点击时,可以扩展菜单条。在我们的程序中,有一个很窄的矩形来响应鼠标点击。抽屉和程序一样,有两个状态,抽屉开和抽屉关。抽屉元素是一条矩形,高度非常小。在抽屉的中心,有一个内嵌的Image元素-箭头图标。当鼠标点击鼠标区域时,抽屉用id screen指定了整个程序的状态.
状态就是配置的集合,它声明在State元素里。一系列状态可以显示和帮定在states属性里。在我们的程序里有两个状态DRAWER_CLOSED和DRAWER_OPEN.在PropertyChange元素里声明状态配置。在DRAWER_OPEN状态,有四个条目会有属性变化。第一项,menuBar的y属性变成0。类似地,当状态是DRAWER_OPEN时,textArea会向下移。TextArea, drawer, 和drawer的图标的属性都有相应的变化来符合当前的状态。
状态变化是不连贯的,应该是平滑的渐变。状态之间的渐变在Transition元素中定义,绑定到对象的transitions属性。文本编辑器状态变化到当DRAWER_OPEN或DRAWER_CLOSED时,状态是渐变的。要注意,渐变需要一个from状态和to状态,我们这里用*, 说明渐变适用于所有的装太变化。
在渐变中,我们可以在属性变化中使用动画。menuBar从位置y:0变到y:-partition,我们可以用NumberAnimation元素来产生动画。我们定义渐变的时间和曲线缓和来定制动画。曲线缓和在状态渐变过程中来控制动画的速度和插值。我们选择Easing.OutQuint作为曲线缓和,Easing.OutQuint在动画结束时减慢速度。详情请参考QML的动画文档。
另外一种属性变化动画的方法是声明Behavior元素。Transition只在属性变化时起作用,Behavior可以设定通用的属性变化动画。在文本编辑器中,箭头可以设置NumberAnimation的旋转动画属性。
让我们回到有状态和动画的组件,我们可以使组件的外感更好看。在Button.qml中,我们在按钮被按下时,加上color和scale属性变化。颜色类型动画使用ColorAnimation,数字使用NumberAnimation. 下面的on propertyName语法在对单一属性时非常有用。
另外,我们可以加上颜色效果,比如梯度和透明效果。Gradient元素可以覆盖color属性。我们可以用GradientStop元素声明颜色的梯度。梯度位置的值在0.0和1.0之间。
菜单条用梯度来显示颜色深度。第一个颜色从位置0.0开始,最后一个颜色在位置1.0结束。
下一步
我们完成了文本编辑器的用户界面。下一步,我们可以用Qt和C++实现程序逻辑。QML是一个非常好原型工具,把程序逻辑和用户界面分开。
用Qt C++来扩展QML
现在我们有文本编辑器的界面,我们可以用C++来实现编辑器的功能。用QML和C++,我们可以用Qt创建程序。我们可以用Qt的Declarative类在C++程序里创建QML环境。我们也可以创建qmlviewer工具可以读取的C++插件。在我们的程序里,我们用C++来实现读取和存储功能,然后导出插件。这样,我们只要直接读取QML,而不用运行程序文件。
在QML中使用C++的类
我们用Qt和C++来实现文件的读取和存储。在QML中注册C++的类和函数后,可以在QML中使用他们。C++类需要被编译成Qt插件,并且QML需要知道插件的位置。
对于我们的程序,我们需要创建一下东西:
1. Directory类,处理目录的操作
2. File类,从QObject派生,模拟目录的文件列表
3. plugin类,注册到QML的环境中
4. Qt项目文件,编译插件
5. qmldir文件,告诉qmlviewer工具插件的位置
创建Qt插件
为了创建一个插件, 我们要在Qt项目文件里设置下列东西。首先,必须的源程序,头文件,Qt模块。所有的C++代码和项目文件都在filedialg目录里。
特别地,我们用declarative模块编译Qt插件,所以模板是lib. 我们把编译好的插件放在父目录的plugins目录里。
把类注册到QML中。
插件类DialogPlugin是QDeclarativeExtensionPlugin的派生类。我们需要实现虚函数registerTypes(). 下面是dialogplugin.cpp:
registerTypes()函数把File类和Diretory类注册到QML里。这个函数需要类模板,重要版本号好,次要版本号和类名字。
我们用Q_EXPORT_PLUGIN2宏来导出插件。注意dialogPlugin.h文件,Q_OBJECT宏必须在类的最前面。此外,我们必须运行qmake来产生必须的元代码。
在C++类中创建QML属性
我们可以用C++和Qt的元对象系统来创建QML元素和属性。我们可以用slots和signal来实现属性,使Qt知道这些属性,这些属性可以用在QML里。
文本编辑器需要有文件读取和存储功能。这些功能存在于文件对话框里。幸运的是,我们可以用QDir,QFile和QTextStream来实现目录读取和输入输出流。
Directory类用Qt的元数据系统来注册文件处理所需要的属性。把Directory类导出成插件,在QML里作为Directory元素使用。每一个用Q_PROPERTY宏定义的属性也是QML里的属性。
Q_PROPERTY声明了属性,同时也声明了元对象系统的读和写的函数。例如,filename属性,是QString类型,用filename()函数来读,用setFilename()来写。另外还声明了一个与filename属性关联的信号叫filenameChanged(),当属性变化时就发出这个信号。在头文件里读和写的函数都是公有函数。
类似地,我们还声明了其他属性。filesCount属性表示目录的文件数。filename属性就是当前所选的文件的名字,读取或存储的文件内容存在fileContent属性里。
files列表属性是一个目录下所有经过过滤的文件列表。Directory类实现了滤掉无效文件的功能,只有.txt结尾的文件才是有效的。只要在C++中声明为QDeclarativeListProperty,QLists可以用在QML文件中使用。模板对象需要从QObject派生出来,因此File类也要从QObject派生出来。在Directory类中,File对象的列表存储在QList中,名字是m_fileList.
这些属性可以在QML中作为Directory元素的属性来被使用。注意,我们不必在C++中创建id属性。
从QML中可以访问通常的C++函数。文件的读取和存储是用C++来实现的,但需要用Q_INVOKABLE宏来声明的。我们也可以把函数声明成slot,这样也可以从QML中访问此函数。
Directory类还要在目录内容变化时通知其他对象。我们用signal来实现这个功能。我们前面提过,QML信号有相应的以on开始的处理函数。这个信号叫directoryChanged,当目录更新的时候就会发出这个信号。刷新就是重新读取目录内容,并更新目录的有效文件列表。通过把一个动作关联到onDirectoryChanged信号处理函数来通知QML对象。
列表属性需要进一步讨论。列表属性用回调函数来读取和修改列表的内容。列表属性是QDeclarativeListProperty<File>类型。当列表被读取时,读取函数需要返回QDeclarativeListProperty<File>。File作为模板类型,需要从QObject派生出来。另外,要创建QDeclarativeListProperty,列表的读取和修改需要作为函数指针来传给构造函数。我们的QList的列表需要成为File指针的列表。
下面是QDeclarativeListProperty的构造函数和Directory的实现:
构造函数把指针传递给添加列表,列表计数,通过索引来取内容和清空列表的函数。只有添加函数是必不可少的。注意函数指针必须与AppendFunction, CountFunction, AtFunction 和ClearFunction的定义匹配。
为了简化我们的文件对话框,Directory类过滤掉了无效的文本文件,无效的文件的扩展名不是.txt的扩展名。如果一个文件名不是.txt的扩展名, 那我们的对话框就看不见。同时要确保被存储的文件名有.txt的扩展名。Directory用QTextStream来读和写文件。
使用Directory元素,我们可以读取文件列表,在目录里文本文件的数目,读取文件名和内容到字符串里,如果目录内容发生变化会接到通知。
要编译插件,对cppPlugins.pro项目文件来运行qmake,运行make编译并把插件拷贝到plugins目录里。
在QML里导入插件
qmlviewer工具可以导入应用程序当前目录下的文件。我们还可以创建qmldir文件,qmldir包含要导入QML文件的位置,插件和其他资源的位置信息。
我们刚才创建的插件叫FileDialog,在项目文件里的TARGET里被定义的。编译好的插件在plugins目录里。
把文件对话框集成到文件菜单里
FileMenu要显示FileDialog元素,FileDialog里包含目录里的文本文件列表,这样用户可以通过点击列表来选择文件。我们需要把相应的动作指定到存储,读取和创建按钮. FileMenu包含可编辑的输入文本框,让用户用键盘来输入文件名。
FileMenu.qml用Directory元素来通知FileDialog元素目录内容刷新。在信号处理函数onDirectoryChanged里发出通知。
为了是程序简单,文件对话框总是可见的,并且不会显示无效的文件(不是.txt扩展名)。
FileDialog元素读取files列表属性并显示目录内容。文件被当作GridView元素的文档,GridView通过代理用网格的形式来显示数据。代理来处理文档的外观,我们的文件对话框创建了文本在中间的网格。点击文件名就会出现一个矩形来选中文件名。当收到notifyRefresh信号时,FileDialog接到通知,重新读取目录的文件。
我门的FileMenu现在连接好了相应的操作。saveButton会把文本从TextEdit传到目录的fileContent属性,然后从可编辑文本框拷贝文件名。最后,这个按钮调用saveFile()函数来存储文件。loadButton根前面的很类似。New action会清空TextEdit.
另外,EditMenu按钮与TextEdit函数拷贝,粘贴和全选连接到一起。
文本编辑器完成
这个程序可以向简单的编辑器一样,接受文本和存储文件。文本编辑器可以读取文件并执行文件操作。