GettingstartqmlChinese: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[toc align_right="yes" depth="3"]
[toc align_right="yes" depth="3"]


本文翻译自:"Getting Started Programming With QML":http://doc.qt.nokia.com/4.7/gettingstartedqml.html
本文翻译自:"Getting Started Programming With QML":http://doc.qt.nokia.com/4.7/gettingstartedqml.html


= QML编程入门 =
= QML编程入门 =


欢迎来到QML的世界,QML是说明式界面语言。在本文里,我们用QML创建一个简单的文本编辑器。读了本文后,您就可以用QML和Qt C+''开发你自己的应用程序了。
欢迎来到QML的世界,QML是说明式界面语言。在本文里,我们用QML创建一个简单的文本编辑器。读了本文后,您就可以用QML和Qt C+''开发你自己的应用程序了。
<br />h2. 用QML来创建用户界面
<br />我们要创建的程序是一个简单的文本编辑器,它可以读,写和其他的文本操作。本教程包含两部份。第一部分是用QML设计程序界面和功能。第二部分是用Qt C来实现文件读和写。使用Qt的Meta-Object系统,我们可以把C的函数当做QML元素可以使用的属性。使用QML和Qt C,我们可以有效地把界面逻辑和程序逻辑分开。
<br />p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|text editor]]
<br />要想运行QML程序,可以运行qmlviewer再加上QML文件名作为参数。这个教程的C部分需要你拥有基本的Qt程序编译知识。
<br />教程章节
<br />1 定义按钮和菜单<br />2 实现菜单条<br />3 创建文本编辑器<br />4 装饰文本编辑器<br />5 用Qt C扩展QML
<br />h2. 定义按钮和菜单
<br />h3. 基本的组件 — 按钮
<br />我们现开始创建一个按钮。从功能上来说,一个按钮有一个鼠标感应区和一个标签。当用户按按钮时,按钮执行动作。<br />i在QML里,基本的可视元素是Rectangle. Rectangle元素有属性来控制外观和位置。


h2. 用QML来创建用户界面


<br />首先要import Qt 4.7,<br />这样qmlviewer可以导入我们以后需要的QML元素。每一个QML文件必须有这一行。需要注意Qt的版本号要在这一个行里。<br />这个简单的rectangle有一个属性id, simplebutton,<br />Rectangle的属性和值绑定在一起,先是属性,冒号,然后是值。在例子程序里,颜色grey绑订到Rectangle的color属性。同理,我们也绑订width和height.
我们要创建的程序是一个简单的文本编辑器,它可以读,写和其他的文本操作。本教程包含两部份。第一部分是用QML设计程序界面和功能。第二部分是用Qt C来实现文件读和写。使用Qt的Meta-Object系统,我们可以把C的函数当做QML元素可以使用的属性。使用QML和Qt C,我们可以有效地把界面逻辑和程序逻辑分开。
<br />Text元素是不可编辑的区域。我们叫它buttonLabel.为了设置Text区域的字符,我们绑订一个值到text属性。为了使标签在Rectangle的中心,我们使用Text元素的anchor固定到它的parent,这里是simplebutton.Anchor也可以绑订其他元素的anchor.这样界面设计就非常简单了。
 
<br />我们把代码存到SimpleButton.qml.<br />运行qmlviewer,用这个文件明作为参数。就可以显示出下面的灰色矩形和文本标签。
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|text editor]]
<br />p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|simple button]]
 
<br />为了实现按钮功能,我们可以使用QML事件处理机制。QML事件处理机制和Qt的signal-slot的机制非常像。信号被出发,连接好的slot函数被调用。
要想运行QML程序,可以运行qmlviewer再加上QML文件名作为参数。这个教程的C部分需要你拥有基本的Qt程序编译知识。
<br /><code><br />Rectangle{<br /> id:simplebutton<br />
 
<br /> MouseArea{<br /> id: buttonMouseArea
教程章节
<br /> anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors<br /> //onClicked handles valid mouse button clicks<br /> onClicked: console.log(buttonLabel.text'' &quot; clicked&amp;quot; )<br /> }<br /> }<br /></code>
 
1 定义按钮和菜单
2 实现菜单条
3 创建文本编辑器
4 装饰文本编辑器
5 用Qt C扩展QML
 
h2. 定义按钮和菜单
 
h3. 基本的组件 — 按钮
 
我们现开始创建一个按钮。从功能上来说,一个按钮有一个鼠标感应区和一个标签。当用户按按钮时,按钮执行动作。
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,用这个文件明作为参数。就可以显示出下面的灰色矩形和文本标签。
 
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|simple button]]
 
为了实现按钮功能,我们可以使用QML事件处理机制。QML事件处理机制和Qt的signal-slot的机制非常像。信号被出发,连接好的slot函数被调用。
 
<code>
Rectangle{
id:simplebutton
 
MouseArea{
id: buttonMouseArea
 
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
//onClicked handles valid mouse button clicks
onClicked: console.log(buttonLabel.text'' " clicked" )
}
}
</code>


simplebutton中包含了MouseArea元素。MouseArea元素描述了检测鼠标移动的交互区域。对于这个按钮,我们把整个MouseArea固定在它的parent - simplebutton. anchors.fill语句可以读取属性anchors的fill属性。QML用基于anchor的布局来把几个项目固定另外一个项目上,这样可以创建稳定的布局。
simplebutton中包含了MouseArea元素。MouseArea元素描述了检测鼠标移动的交互区域。对于这个按钮,我们把整个MouseArea固定在它的parent - simplebutton. anchors.fill语句可以读取属性anchors的fill属性。QML用基于anchor的布局来把几个项目固定另外一个项目上,这样可以创建稳定的布局。
Line 32: Line 67:
SimpleButton.qml的代码可以在屏幕上显示按钮,并且鼠标点击时输出文本。
SimpleButton.qml的代码可以在屏幕上显示按钮,并且鼠标点击时输出文本。


<code><br />Rectangle {<br /> id:Button<br />
<code>
Rectangle {
id:Button


property color buttonColor: &quot;lightblue&amp;quot;<br /> property color onHoverColor: &quot;gold&amp;quot;<br /> property color borderColor: &quot;white&amp;quot;
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"


signal buttonClick()<br /> onButtonClick: {<br /> console.log(buttonLabel.text + &quot; clicked&amp;quot; )<br /> }
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}


MouseArea{<br /> onClicked: buttonClick()<br /> hoverEnabled: true<br /> onEntered: parent.border.color = onHoverColor<br /> onExited: parent.border.color = borderColor<br /> }
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<br /> color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br /> }<br /></code>
//determines the color of the button by using the conditional operator
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}
</code>


功能完整的按钮代码在Button.qml中。本文中的代码片断有遗漏,有些代码在前面的章节里已经介绍过,有些代码根本文无关。
功能完整的按钮代码在Button.qml中。本文中的代码片断有遗漏,有些代码在前面的章节里已经介绍过,有些代码根本文无关。
Line 46: Line 97:
自定义的属性用property type name语句来声明。在代码中,声明了buttonColor属性,并给它赋值lightblue. buttonColor在后面会在决定按钮的填充颜色条件语句中被用到。注意除了用冒号来赋值绑定,给属性赋值可以等号=。自定义属性允许内部属性被Rectangle的外部来访问。基本的QML类型有int,string, real, 和variant.
自定义的属性用property type name语句来声明。在代码中,声明了buttonColor属性,并给它赋值lightblue. buttonColor在后面会在决定按钮的填充颜色条件语句中被用到。注意除了用冒号来赋值绑定,给属性赋值可以等号=。自定义属性允许内部属性被Rectangle的外部来访问。基本的QML类型有int,string, real, 和variant.


通过把颜色绑定到onEntered和onExited信号处理函数,鼠标移到按钮上,按钮的边界会变成黄色,当鼠标移出按钮,边界会变成原来的颜色。<br />通过把signal放在信号名字的前面来声明buttonClick()信号。所有信号的处理函数都会被自动创建,信号的名字都是以on开始。在结果上onButtonClick就是buttonClick的处理函数。onButtonClick被指定去执行一个动作。在我们的按钮例子里,onClicked鼠标处理函数调用onButtonClick, onButtonClick会显示文本。onButtonClick可以使外部对象很容易访问按钮的鼠标区域。例如,声明多个MouseArea和一个buttonClick信号的对象可以在几个MouseArea信号处理函数中进行更好的区分。
通过把颜色绑定到onEntered和onExited信号处理函数,鼠标移到按钮上,按钮的边界会变成黄色,当鼠标移出按钮,边界会变成原来的颜色。
通过把signal放在信号名字的前面来声明buttonClick()信号。所有信号的处理函数都会被自动创建,信号的名字都是以on开始。在结果上onButtonClick就是buttonClick的处理函数。onButtonClick被指定去执行一个动作。在我们的按钮例子里,onClicked鼠标处理函数调用onButtonClick, onButtonClick会显示文本。onButtonClick可以使外部对象很容易访问按钮的鼠标区域。例如,声明多个MouseArea和一个buttonClick信号的对象可以在几个MouseArea信号处理函数中进行更好的区分。


我们现在基本了解用QML来实现可以处理鼠标移动的对象。我在Rectangle中创建一个Text标签,定义它的属性,实现处理鼠标移动的行为。我们在整个文本编辑器中将很多次在对象中创建对象。
我们现在基本了解用QML来实现可以处理鼠标移动的对象。我在Rectangle中创建一个Text标签,定义它的属性,实现处理鼠标移动的行为。我们在整个文本编辑器中将很多次在对象中创建对象。
Line 60: Line 112:
菜单显示列表的内容,列表中每一项都可以执行操作。在QML中我们有几种方法来创建菜单。第一种创建包含按钮的菜单,每个按钮可以执行特定的操作。菜单的代码在FileMenu.qml中.
菜单显示列表的内容,列表中每一项都可以执行操作。在QML中我们有几种方法来创建菜单。第一种创建包含按钮的菜单,每个按钮可以执行特定的操作。菜单的代码在FileMenu.qml中.


<code><br />import Qt 4.7 import the main Qt QML module<br />import &quot;folderName&amp;quot; import the contents of the folder<br />import &quot;script.js&amp;quot; as Script import a Javascript file and name it as Script</code>
<code>
import Qt 4.7 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</code>


上面的代码表示如何使用import关键字。这里面需要使用javascript文件或者在另外的目录下的QML文件。因为Button.qml和FileMenu.qml在同一个目录下,所以我们不用导入Button.qml.我们可以直接通过声明Button{}来创建Button元素,这跟Rectangle{}声明类似。
上面的代码表示如何使用import关键字。这里面需要使用javascript文件或者在另外的目录下的QML文件。因为Button.qml和FileMenu.qml在同一个目录下,所以我们不用导入Button.qml.我们可以直接通过声明Button{}来创建Button元素,这跟Rectangle{}声明类似。


<code><br />In FileMenu.qml:
<code>
In FileMenu.qml:


Row{<br /> anchors.centerIn: parent<br /> spacing: parent.width/6
Row{
anchors.centerIn: parent
spacing: parent.width/6


Button{<br /> id: loadButton<br /> buttonColor: &quot;lightgrey&amp;quot;<br /> label: &quot;Load&amp;quot;<br /> }<br /> Button{<br /> buttonColor: &quot;grey&amp;quot;<br /> id: saveButton<br /> label: &quot;Save&amp;quot;<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Exit&amp;quot;<br /> buttonColor: &quot;darkgrey&amp;quot;
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"


onButtonClick: Qt.quit()<br /> }<br /> }<br /></code>
onButtonClick: Qt.quit()
}
}
</code>


在FileMenu.qml中,我们在Row元素里声明了3个Button元素。Row元素里有三个垂直的子对象。Button在我们前面介绍的Button.qml里声明。可以在新建的按钮里声明新的属性绑定,覆盖Button.qml里的属性。名字叫做exitButton被点击时,程序退出。注意除了调用exitButton里的函数onButtonClickon,还有在Button.qml里的信号处理函数onButtonClick也会被调用。
在FileMenu.qml中,我们在Row元素里声明了3个Button元素。Row元素里有三个垂直的子对象。Button在我们前面介绍的Button.qml里声明。可以在新建的按钮里声明新的属性绑定,覆盖Button.qml里的属性。名字叫做exitButton被点击时,程序退出。注意除了调用exitButton里的函数onButtonClickon,还有在Button.qml里的信号处理函数onButtonClick也会被调用。
Line 94: Line 168:
我们在menuListModel声明两个可视条目,FileMenu和EditMenu。我们定制了两个菜单,使用一个ListView来显示他们。MenuBar.qml文件包含了QML声明,在EditMenu.qml里定义了一个简单的编辑菜单。
我们在menuListModel声明两个可视条目,FileMenu和EditMenu。我们定制了两个菜单,使用一个ListView来显示他们。MenuBar.qml文件包含了QML声明,在EditMenu.qml里定义了一个简单的编辑菜单。


<code><br /> VisualItemModel{<br /> id: menuListModel<br /> FileMenu{<br /> width: menuListView.width<br /> height: menuBar.height<br /> color: fileColor<br /> }<br /> EditMenu{<br /> color: editColor<br /> width: menuListView.width<br /> height: menuBar.height<br /> }<br /> }<br /></code>
<code>
VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}
</code>


ListView元素使用代理来显示Model。代理可以声明Model的条目,然后显示一排或者显示在网格里。在menulistModel里已经有了可视项目,我们不要再声明代理了。
ListView元素使用代理来显示Model。代理可以声明Model的条目,然后显示一排或者显示在网格里。在menulistModel里已经有了可视项目,我们不要再声明代理了。


<code><br /> ListView{<br /> id: menuListView
<code>
ListView{
id: menuListView


//Anchors are set to react to window anchors<br /> anchors.fill:parent<br /> anchors.bottom: parent.bottom<br /> width:parent.width<br /> height: parent.height
//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<br /> model: menuListModel
//the model contains the data
model: menuListModel


//control the movement of the menu switching<br /> snapMode: ListView.SnapOneItem<br /> orientation: ListView.Horizontal<br /> boundsBehavior: Flickable.StopAtBounds<br /> flickDeceleration: 5000<br /> highlightFollowsCurrentItem: true<br /> highlightMoveDuration:240<br /> highlightRangeMode: ListView.StrictlyEnforceRange<br /> }<br /></code>
//control the movement of the menu switching
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 5000
highlightFollowsCurrentItem: true
highlightMoveDuration:240
highlightRangeMode: ListView.StrictlyEnforceRange
}
</code>


另外,ListView从Flickable派生出来,因此列表可以响应鼠标拖放和其他动作。上面代码的最后一部份设置Flickable属性来给视图创建希望的flicking动作。特别地,higlightMOveDuration属性改变flick变化的时间。更大的highlightMoveDuration值会使菜单切换变得更慢。
另外,ListView从Flickable派生出来,因此列表可以响应鼠标拖放和其他动作。上面代码的最后一部份设置Flickable属性来给视图创建希望的flicking动作。特别地,higlightMOveDuration属性改变flick变化的时间。更大的highlightMoveDuration值会使菜单切换变得更慢。
Line 112: Line 216:
labelList矩形的z值是1,表明它被显示在菜单条的前面。有更高z值的项目显示在低z值项目的上面。缺省的z值是0.
labelList矩形的z值是1,表明它被显示在菜单条的前面。有更高z值的项目显示在低z值项目的上面。缺省的z值是0.


<code><br /> Rectangle{<br /> id: labelList<br /> <br /> z: 1<br /> Row{<br /> anchors.centerIn: parent<br /> spacing:40<br /> Button{<br /> label: &quot;File&amp;quot;<br /> id: fileButton<br /> <br /> onButtonClick: menuListView.currentIndex = 0<br /> }<br /> Button{<br /> id: editButton<br /> label: &quot;Edit&amp;quot;<br /> <br /> onButtonClick: menuListView.currentIndex = 1<br /> }<br /> }<br /> }<br /></code>
<code>
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
}
}
}
</code>


我们可以点击上面的菜单名字来弹出刚才创建的菜单条。切换菜单的感觉很直接,响应很快。
我们可以点击上面的菜单名字来弹出刚才创建的菜单条。切换菜单的感觉很直接,响应很快。
Line 124: Line 250:
我们的文本编辑器必须有文本区域。QML的TextEdit元素可以容纳多行可编辑的文本区域。TextEdit和Text元素不同,Text元素部允许编辑文本。
我们的文本编辑器必须有文本区域。QML的TextEdit元素可以容纳多行可编辑的文本区域。TextEdit和Text元素不同,Text元素部允许编辑文本。


<code><br /> TextEdit{<br /> id: textEditor<br /> anchors.fill:parent<br /> width:parent.width; height:parent.height<br /> color:&quot;midnightblue&amp;quot;<br /> focus: true
<code>
TextEdit{
id: textEditor
anchors.fill:parent
width:parent.width; height:parent.height
color:"midnightblue"
focus: true


wrapMode: TextEdit.Wrap
wrapMode: TextEdit.Wrap


onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)<br /> }<br /></code>
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}
</code>


编辑器有字体属性,可以自动换行。TextEdit区域在Flickable区域里面,如果光标出了可见区域,TextEdit可以自动滚动。ensureVisible()函数可以查看光标在边界外面,然后相应地移动文本区域。QML用Javascript的语法,Javascript文件可以被导入到QML文件里。
编辑器有字体属性,可以自动换行。TextEdit区域在Flickable区域里面,如果光标出了可见区域,TextEdit可以自动滚动。ensureVisible()函数可以查看光标在边界外面,然后相应地移动文本区域。QML用Javascript的语法,Javascript文件可以被导入到QML文件里。


<code><br /> function ensureVisible®{<br /> if (contentX &gt;= r.x)<br /> contentX = r.x;<br /> else if (contentX+width &lt;= r.x+r.width)<br /> contentX = r.x+r.width-width;<br /> if (contentY &gt;= r.y)<br /> contentY = r.y;<br /> else if (contentY+height &lt;= r.y+r.height)<br /> contentY = r.y+r.height-height;<br /> }<br /></code>
<code>
function ensureVisible®{
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;
}
</code>


=== 把组件整合成文本编辑器 ===
=== 把组件整合成文本编辑器 ===
Line 138: Line 283:
我们现在可以用QML来创建文本编辑器的界面。编辑器有两个组件,菜单条和文本区域。QML可以通过导入和重新定制来重用组件,使我们的代码更简单。文本编辑器把窗口分成两部份,三分之一是菜单条,三分之二是文本区域。菜单条显示在其他元素之上。
我们现在可以用QML来创建文本编辑器的界面。编辑器有两个组件,菜单条和文本区域。QML可以通过导入和重新定制来重用组件,使我们的代码更简单。文本编辑器把窗口分成两部份,三分之一是菜单条,三分之二是文本区域。菜单条显示在其他元素之上。


<code><br /> Rectangle{
<code>
Rectangle{


id: screen<br /> width: 1000; height: 1000
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<br /> property int partition: height/3
//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{<br /> id:menuBar<br /> height: partition<br /> width:parent.width<br /> z: 1<br /> }
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}


TextArea{<br /> id:textArea<br /> anchors.bottom:parent.bottom<br /> y: partition<br /> color: &quot;white&amp;quot;<br /> height: partition*2<br /> width:parent.width<br /> }<br /> }<br /></code>
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}
</code>


通过导入重用组件,我们的TextEditor代码非常简单。我们可以定制主程序,而不比担心已经定义的属性。用种方法可以很容易创建UI界面。
通过导入重用组件,我们的TextEditor代码非常简单。我们可以定制主程序,而不比担心已经定义的属性。用种方法可以很容易创建UI界面。
Line 160: Line 322:
我们可以加一个抽屉接口,当点击时,可以扩展菜单条。在我们的程序中,有一个很窄的矩形来响应鼠标点击。抽屉和程序一样,有两个状态,抽屉开和抽屉关。抽屉元素是一条矩形,高度非常小。在抽屉的中心,有一个内嵌的Image元素-箭头图标。当鼠标点击鼠标区域时,抽屉用id screen指定了整个程序的状态.
我们可以加一个抽屉接口,当点击时,可以扩展菜单条。在我们的程序中,有一个很窄的矩形来响应鼠标点击。抽屉和程序一样,有两个状态,抽屉开和抽屉关。抽屉元素是一条矩形,高度非常小。在抽屉的中心,有一个内嵌的Image元素-箭头图标。当鼠标点击鼠标区域时,抽屉用id screen指定了整个程序的状态.


<code><br /> Rectangle{<br /> id:drawer<br /> height:15
<code>
Rectangle{
id:drawer
height:15


Image{<br /> id: arrowIcon<br /> source: &quot;images/arrow.png&amp;quot;<br /> anchors.horizontalCenter: parent.horizontalCenter<br /> }
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}


MouseArea{<br /> id: drawerMouseArea<br /> anchors.fill:parent<br /> onClicked:{<br /> if (screen.state  &amp;quot;DRAWER_CLOSED&amp;quot;)&amp;#123;
MouseArea{
                     screen.state = &amp;quot;DRAWER_OPEN&amp;quot;
id: drawerMouseArea
                 &amp;#125;
anchors.fill:parent
                 else if (screen.state  &quot;DRAWER_OPEN&amp;quot;){<br /> screen.state = &quot;DRAWER_CLOSED&amp;quot;<br /> }<br /> }<br /> <br /> }<br /> }<br /></code>
onClicked:{
if (screen.state  "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state  "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
}
}
</code>


状态就是配置的集合,它声明在State元素里。一系列状态可以显示和帮定在states属性里。在我们的程序里有两个状态DRAWER_CLOSED和DRAWER_OPEN.在PropertyChange元素里声明状态配置。在DRAWER_OPEN状态,有四个条目会有属性变化。第一项,menuBar的y属性变成0。类似地,当状态是DRAWER_OPEN时,textArea会向下移。TextArea, drawer, 和drawer的图标的属性都有相应的变化来符合当前的状态。
状态就是配置的集合,它声明在State元素里。一系列状态可以显示和帮定在states属性里。在我们的程序里有两个状态DRAWER_CLOSED和DRAWER_OPEN.在PropertyChange元素里声明状态配置。在DRAWER_OPEN状态,有四个条目会有属性变化。第一项,menuBar的y属性变成0。类似地,当状态是DRAWER_OPEN时,textArea会向下移。TextArea, drawer, 和drawer的图标的属性都有相应的变化来符合当前的状态。


<code><br /> states:[<br /> State {<br /> name: &quot;DRAWER_OPEN&amp;quot;<br /> PropertyChanges { target: menuBar; y: 0}<br /> PropertyChanges { target: textArea; y: partition + drawer.height}<br /> PropertyChanges { target: drawer; y: partition}<br /> PropertyChanges { target: arrowIcon; rotation: 180}<br /> },<br /> State {<br /> name: &quot;DRAWER_CLOSED&amp;quot;<br /> PropertyChanges { target: menuBar; y:<s>height; }<br /> PropertyChanges { target: textArea; y: drawer.height; height: screen.height</s> drawer.height }<br /> PropertyChanges { target: drawer; y: 0 }<br /> PropertyChanges { target: arrowIcon; rotation: 0 }<br /> }<br /> ]<br /></code>
<code>
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 }
}
]
</code>


状态变化是不连贯的,应该是平滑的渐变。状态之间的渐变在Transition元素中定义,绑定到对象的transitions属性。文本编辑器状态变化到当DRAWER_OPEN或DRAWER_CLOSED时,状态是渐变的。要注意,渐变需要一个from状态和to状态,我们这里用*, 说明渐变适用于所有的装太变化。
状态变化是不连贯的,应该是平滑的渐变。状态之间的渐变在Transition元素中定义,绑定到对象的transitions属性。文本编辑器状态变化到当DRAWER_OPEN或DRAWER_CLOSED时,状态是渐变的。要注意,渐变需要一个from状态和to状态,我们这里用*, 说明渐变适用于所有的装太变化。
Line 177: Line 374:
在渐变中,我们可以在属性变化中使用动画。menuBar从位置y:0变到y:-partition,我们可以用NumberAnimation元素来产生动画。我们定义渐变的时间和曲线缓和来定制动画。曲线缓和在状态渐变过程中来控制动画的速度和插值。我们选择Easing.OutQuint作为曲线缓和,Easing.OutQuint在动画结束时减慢速度。详情请参考QML的动画文档。
在渐变中,我们可以在属性变化中使用动画。menuBar从位置y:0变到y:-partition,我们可以用NumberAnimation元素来产生动画。我们定义渐变的时间和曲线缓和来定制动画。曲线缓和在状态渐变过程中来控制动画的速度和插值。我们选择Easing.OutQuint作为曲线缓和,Easing.OutQuint在动画结束时减慢速度。详情请参考QML的动画文档。


<code><br /> transitions: [<br /> Transition {<br /> to: &quot;*&quot;<br /> NumberAnimation { target: textArea; properties: &quot;y, height&amp;quot;; duration: 100; easing.type:Easing.OutExpo }<br /> NumberAnimation { target: menuBar; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> NumberAnimation { target: drawer; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> }<br /> ]<br /></code>
<code>
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 }
}
]
</code>


另外一种属性变化动画的方法是声明Behavior元素。Transition只在属性变化时起作用,Behavior可以设定通用的属性变化动画。在文本编辑器中,箭头可以设置NumberAnimation的旋转动画属性。
另外一种属性变化动画的方法是声明Behavior元素。Transition只在属性变化时起作用,Behavior可以设定通用的属性变化动画。在文本编辑器中,箭头可以设置NumberAnimation的旋转动画属性。


<code><br />In TextEditor.qml:
<code>
In TextEditor.qml:


Behavior{<br /> NumberAnimation{property: &quot;rotation&amp;quot;;easing.type: Easing.OutExpo }<br /> }<br /></code>
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
</code>


让我们回到有状态和动画的组件,我们可以使组件的外感更好看。在Button.qml中,我们在按钮被按下时,加上color和scale属性变化。颜色类型动画使用ColorAnimation,数字使用NumberAnimation. 下面的on propertyName语法在对单一属性时非常有用。
让我们回到有状态和动画的组件,我们可以使组件的外感更好看。在Button.qml中,我们在按钮被按下时,加上color和scale属性变化。颜色类型动画使用ColorAnimation,数字使用NumberAnimation. 下面的on propertyName语法在对单一属性时非常有用。


<code><br />In Button.qml:<br />
<code>
In Button.qml:


color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br /> Behavior on color { ColorAnimation{ duration: 55} }
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
Behavior on color { ColorAnimation{ duration: 55} }


scale: buttonMouseArea.pressed ? 1.1 : 1.00<br /> Behavior on scale { NumberAnimation{ duration: 55} }<br /></code>
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
</code>


另外,我们可以加上颜色效果,比如梯度和透明效果。Gradient元素可以覆盖color属性。我们可以用GradientStop元素声明颜色的梯度。梯度位置的值在0.0和1.0之间。
另外,我们可以加上颜色效果,比如梯度和透明效果。Gradient元素可以覆盖color属性。我们可以用GradientStop元素声明颜色的梯度。梯度位置的值在0.0和1.0之间。


<code><br />In MenuBar.qml<br /> gradient: Gradient {<br /> GradientStop { position: 0.0; color: &quot;#8C8F8C&amp;quot; }<br /> GradientStop { position: 0.17; color: &quot;#6A6D6A&amp;quot; }<br /> GradientStop { position: 0.98;color: &quot;#3F3F3F&amp;quot; }<br /> GradientStop { position: 1.0; color: &quot;#0e1B20&amp;quot; }<br /> }<br /></code>
<code>
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" }
}
</code>


菜单条用梯度来显示颜色深度。第一个颜色从位置0.0开始,最后一个颜色在位置1.0结束。
菜单条用梯度来显示颜色深度。第一个颜色从位置0.0开始,最后一个颜色在位置1.0结束。
Line 202: Line 425:


我们完成了文本编辑器的用户界面。下一步,我们可以用Qt和C+''实现程序逻辑。QML是一个非常好原型工具,把程序逻辑和用户界面分开。
我们完成了文本编辑器的用户界面。下一步,我们可以用Qt和C+''实现程序逻辑。QML是一个非常好原型工具,把程序逻辑和用户界面分开。
<br />p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|text editor]]
 
<br />h3. 用Qt C来扩展QML
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|text editor]]
<br />现在我们有文本编辑器的界面,我们可以用C来实现编辑器的功能。用QML和C,我们可以用Qt创建程序。我们可以用Qt的Declarative类在C程序里创建QML环境。我们也可以创建qmlviewer工具可以读取的C插件。在我们的程序里,我们用C来实现读取和存储功能,然后导出插件。这样,我们只要直接读取QML,而不用运行程序文件。
 
<br />h4. 在QML中使用C的类
h3. 用Qt C来扩展QML
<br />我们用Qt和C来实现文件的读取和存储。在QML中注册C的类和函数后,可以在QML中使用他们。C类需要被编译成Qt插件,并且QML需要知道插件的位置。
 
<br />对于我们的程序,我们需要创建一下东西:<br />1. Directory类,处理目录的操作<br />2. File类,从QObject派生,模拟目录的文件列表<br />3. plugin类,注册到QML的环境中<br />4. Qt项目文件,编译插件<br />5. qmldir文件,告诉qmlviewer工具插件的位置
现在我们有文本编辑器的界面,我们可以用C来实现编辑器的功能。用QML和C,我们可以用Qt创建程序。我们可以用Qt的Declarative类在C程序里创建QML环境。我们也可以创建qmlviewer工具可以读取的C插件。在我们的程序里,我们用C来实现读取和存储功能,然后导出插件。这样,我们只要直接读取QML,而不用运行程序文件。
<br />h4. 创建Qt插件
 
<br />为了创建一个插件, 我们要在Qt项目文件里设置下列东西。首先,必须的源程序,头文件,Qt模块。所有的C代码和项目文件都在filedialg目录里。
h4. 在QML中使用C的类
<br /><code><br />In cppPlugins.pro:
 
<br /> TEMPLATE = lib<br /> CONFIG''= qt plugin<br /> QT ''= declarative
我们用Qt和C来实现文件的读取和存储。在QML中注册C的类和函数后,可以在QML中使用他们。C类需要被编译成Qt插件,并且QML需要知道插件的位置。
<br /> DESTDIR''= ../plugins<br /> OBJECTS_DIR = tmp<br /> MOC_DIR = tmp
 
对于我们的程序,我们需要创建一下东西:
1. Directory类,处理目录的操作
2. File类,从QObject派生,模拟目录的文件列表
3. plugin类,注册到QML的环境中
4. Qt项目文件,编译插件
5. qmldir文件,告诉qmlviewer工具插件的位置
 
h4. 创建Qt插件
 
为了创建一个插件, 我们要在Qt项目文件里设置下列东西。首先,必须的源程序,头文件,Qt模块。所有的C代码和项目文件都在filedialg目录里。
 
<code>
In cppPlugins.pro:
 
TEMPLATE = lib
CONFIG''= qt plugin
QT ''= declarative
 
DESTDIR''= ../plugins
OBJECTS_DIR = tmp
MOC_DIR = tmp


TARGET = FileDialog
TARGET = FileDialog


HEADERS ''= directory.h  file.h  dialogPlugin.h
HEADERS ''= directory.h  file.h  dialogPlugin.h
<br /> SOURCES''= directory.cpp  file.cpp  dialogPlugin.cpp<br /></code>
 
SOURCES''= directory.cpp  file.cpp  dialogPlugin.cpp
</code>


特别地,我们用declarative模块编译Qt插件,所以模板是lib. 我们把编译好的插件放在父目录的plugins目录里。
特别地,我们用declarative模块编译Qt插件,所以模板是lib. 我们把编译好的插件放在父目录的plugins目录里。
Line 223: Line 469:
==== 把类注册到QML中。 ====
==== 把类注册到QML中。 ====


<code><br />In dialogPlugin.h:
<code>
In dialogPlugin.h:


#include &lt;QtDeclarative/QDeclarativeExtensionPlugin&amp;gt;
#include <QtDeclarative/QDeclarativeExtensionPlugin>


class DialogPlugin : public QDeclarativeExtensionPlugin<br /> {<br /> Q_OBJECT
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT


public:<br /> void registerTypes(const char *uri);
public:
void registerTypes(const char *uri);


};<br /></code>
};
</code>


插件类DialogPlugin是QDeclarativeExtensionPlugin的派生类。我们需要实现虚函数registerTypes(). 下面是dialogplugin.cpp:
插件类DialogPlugin是QDeclarativeExtensionPlugin的派生类。我们需要实现虚函数registerTypes(). 下面是dialogplugin.cpp:


<code><br />DialogPlugin.cpp:
<code>
DialogPlugin.cpp:


#include &quot;dialogPlugin.h&amp;quot;<br /> #include &quot;directory.h&amp;quot;<br /> #include &quot;file.h&amp;quot;<br /> #include &lt;QtDeclarative/qdeclarative.h&amp;gt;
#include "dialogPlugin.h"
#include "directory.h"
#include "file.h"
#include <QtDeclarative/qdeclarative.h>


void DialogPlugin::registerTypes(const char '''uri){
void DialogPlugin::registerTypes(const char '''uri){
<br /> qmlRegisterType&amp;lt;Directory&amp;gt;(uri, 1, 0, &quot;Directory&amp;quot;);<br /> qmlRegisterType&amp;lt;File&amp;gt;(uri, 1, 0,&quot;File&amp;quot;);<br /> }
 
<br /> Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);<br /></code>
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
<br />registerTypes()函数把File类和Diretory类注册到QML里。这个函数需要类模板,重要版本号好,次要版本号和类名字。
qmlRegisterType<File>(uri, 1, 0,"File");
<br />我们用Q_EXPORT_PLUGIN2宏来导出插件。注意dialogPlugin.h文件,Q_OBJECT宏必须在类的最前面。此外,我们必须运行qmake来产生必须的元代码。
}
<br />h4. 在C+''类中创建QML属性
 
<br />我们可以用C和Qt的元对象系统来创建QML元素和属性。我们可以用slots和signal来实现属性,使Qt知道这些属性,这些属性可以用在QML里。
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);
<br />文本编辑器需要有文件读取和存储功能。这些功能存在于文件对话框里。幸运的是,我们可以用QDir,QFile和QTextStream来实现目录读取和输入输出流。
</code>
<br /><code><br /> class Directory : public QObject{
 
<br /> Q_OBJECT
registerTypes()函数把File类和Diretory类注册到QML里。这个函数需要类模板,重要版本号好,次要版本号和类名字。
<br /> Q_PROPERTY(int filesCount READ filesCount CONSTANT)<br /> Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)<br /> Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)<br /> Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )
 
<br /> <br /></code>
我们用Q_EXPORT_PLUGIN2宏来导出插件。注意dialogPlugin.h文件,Q_OBJECT宏必须在类的最前面。此外,我们必须运行qmake来产生必须的元代码。
<br />Directory类用Qt的元数据系统来注册文件处理所需要的属性。把Directory类导出成插件,在QML里作为Directory元素使用。每一个用Q_PROPERTY宏定义的属性也是QML里的属性。
 
<br />Q_PROPERTY声明了属性,同时也声明了元对象系统的读和写的函数。例如,filename属性,是QString类型,用filename()函数来读,用setFilename()来写。另外还声明了一个与filename属性关联的信号叫filenameChanged(),当属性变化时就发出这个信号。在头文件里读和写的函数都是公有函数。
h4. 在C+''类中创建QML属性
<br />类似地,我们还声明了其他属性。filesCount属性表示目录的文件数。filename属性就是当前所选的文件的名字,读取或存储的文件内容存在fileContent属性里。
 
<br /><code>Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )<code>
我们可以用C和Qt的元对象系统来创建QML元素和属性。我们可以用slots和signal来实现属性,使Qt知道这些属性,这些属性可以用在QML里。
<br />files列表属性是一个目录下所有经过过滤的文件列表。Directory类实现了滤掉无效文件的功能,只有.txt结尾的文件才是有效的。只要在C中声明为QDeclarativeListProperty,QLists可以用在QML文件中使用。模板对象需要从QObject派生出来,因此File类也要从QObject派生出来。在Directory类中,File对象的列表存储在QList中,名字是m_fileList.
 
<br /></code><br /> class File : public QObject{
文本编辑器需要有文件读取和存储功能。这些功能存在于文件对话框里。幸运的是,我们可以用QDir,QFile和QTextStream来实现目录读取和输入输出流。
<br /> Q_OBJECT<br /> Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
 
<br /> <br /> };<br /><code>
<code>
<br />这些属性可以在QML中作为Directory元素的属性来被使用。注意,我们不必在C中创建id属性。
class Directory : public QObject{
<br /></code><br /> Directory{<br /> id: directory
 
<br /> filesCount<br /> filename<br /> fileContent<br /> files
Q_OBJECT
<br /> files[0].name<br /> }<br /><code>
 
<br />因为QML使用Javascript的语法和结构,我们可以迭代列表并读取它的属性。如果要读取第一个文件的名字属性,我们用files[0].name.
Q_PROPERTY(int filesCount READ filesCount CONSTANT)
<br />从QML中可以访问通常的C函数。文件的读取和存储是用C''+来实现的,但需要用Q_INVOKABLE宏来声明的。我们也可以把函数声明成slot,这样也可以从QML中访问此函数。
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
<br /></code><br />In Directory.h:
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
<br /> Q_INVOKABLE void saveFile&amp;amp;#40;&amp;#41;;<br /> Q_INVOKABLE void loadFile&amp;amp;#40;&amp;#41;;<br /><code>
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
<br />Directory类还要在目录内容变化时通知其他对象。我们用signal来实现这个功能。我们前面提过,QML信号有相应的以on开始的处理函数。这个信号叫directoryChanged,当目录更新的时候就会发出这个信号。刷新就是重新读取目录内容,并更新目录的有效文件列表。通过把一个动作关联到onDirectoryChanged信号处理函数来通知QML对象。
 
<br />列表属性需要进一步讨论。列表属性用回调函数来读取和修改列表的内容。列表属性是QDeclarativeListProperty&amp;lt;File&amp;gt;类型。当列表被读取时,读取函数需要返回QDeclarativeListProperty&amp;lt;File&amp;gt;。File作为模板类型,需要从QObject派生出来。另外,要创建QDeclarativeListProperty,列表的读取和修改需要作为函数指针来传给构造函数。我们的QList的列表需要成为File指针的列表。
<br />下面是QDeclarativeListProperty的构造函数和Directory的实现:
</code>
<br /></code><br />QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )<br />QDeclarativeListProperty&amp;lt;File&amp;gt;( this, &amp;m_fileList, &amp;appendFiles, &amp;filesSize, &amp;fileAt, &amp;clearFilesPtr );<br /><code>
 
Directory类用Qt的元数据系统来注册文件处理所需要的属性。把Directory类导出成插件,在QML里作为Directory元素使用。每一个用Q_PROPERTY宏定义的属性也是QML里的属性。
 
Q_PROPERTY声明了属性,同时也声明了元对象系统的读和写的函数。例如,filename属性,是QString类型,用filename()函数来读,用setFilename()来写。另外还声明了一个与filename属性关联的信号叫filenameChanged(),当属性变化时就发出这个信号。在头文件里读和写的函数都是公有函数。
 
类似地,我们还声明了其他属性。filesCount属性表示目录的文件数。filename属性就是当前所选的文件的名字,读取或存储的文件内容存在fileContent属性里。
 
<code>Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>
 
files列表属性是一个目录下所有经过过滤的文件列表。Directory类实现了滤掉无效文件的功能,只有.txt结尾的文件才是有效的。只要在C中声明为QDeclarativeListProperty,QLists可以用在QML文件中使用。模板对象需要从QObject派生出来,因此File类也要从QObject派生出来。在Directory类中,File对象的列表存储在QList中,名字是m_fileList.
 
</code>
class File : public QObject{
 
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
 
};
<code>
 
这些属性可以在QML中作为Directory元素的属性来被使用。注意,我们不必在C中创建id属性。
 
</code>
Directory{
id: directory
 
filesCount
filename
fileContent
files
 
files[0].name
}
<code>
 
因为QML使用Javascript的语法和结构,我们可以迭代列表并读取它的属性。如果要读取第一个文件的名字属性,我们用files[0].name.
 
从QML中可以访问通常的C函数。文件的读取和存储是用C''+来实现的,但需要用Q_INVOKABLE宏来声明的。我们也可以把函数声明成slot,这样也可以从QML中访问此函数。
 
</code>
In Directory.h:
 
Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
<code>
 
Directory类还要在目录内容变化时通知其他对象。我们用signal来实现这个功能。我们前面提过,QML信号有相应的以on开始的处理函数。这个信号叫directoryChanged,当目录更新的时候就会发出这个信号。刷新就是重新读取目录内容,并更新目录的有效文件列表。通过把一个动作关联到onDirectoryChanged信号处理函数来通知QML对象。
 
列表属性需要进一步讨论。列表属性用回调函数来读取和修改列表的内容。列表属性是QDeclarativeListProperty<File>类型。当列表被读取时,读取函数需要返回QDeclarativeListProperty<File>。File作为模板类型,需要从QObject派生出来。另外,要创建QDeclarativeListProperty,列表的读取和修改需要作为函数指针来传给构造函数。我们的QList的列表需要成为File指针的列表。
 
下面是QDeclarativeListProperty的构造函数和Directory的实现:
 
</code>
QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
QDeclarativeListProperty<File>( this, &amp;m_fileList, &amp;appendFiles, &amp;filesSize, &amp;fileAt, &amp;clearFilesPtr );
<code>


构造函数把指针传递给添加列表,列表计数,通过索引来取内容和清空列表的函数。只有添加函数是必不可少的。注意函数指针必须与AppendFunction, CountFunction, AtFunction 和ClearFunction的定义匹配。
构造函数把指针传递给添加列表,列表计数,通过索引来取内容和清空列表的函数。只有添加函数是必不可少的。注意函数指针必须与AppendFunction, CountFunction, AtFunction 和ClearFunction的定义匹配。


</code><br />void appendFiles(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, File * file)<br /> File* fileAt(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, int index)<br /> int filesSize(QDeclarativeListProperty&amp;lt;File&amp;gt; * property)<br /> void clearFilesPtr(QDeclarativeListProperty&amp;lt;File&amp;gt; *property)<code>
</code>
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *property)<code>


为了简化我们的文件对话框,Directory类过滤掉了无效的文本文件,无效的文件的扩展名不是.txt的扩展名。如果一个文件名不是.txt的扩展名, 那我们的对话框就看不见。同时要确保被存储的文件名有.txt的扩展名。Directory用QTextStream来读和写文件。
为了简化我们的文件对话框,Directory类过滤掉了无效的文本文件,无效的文件的扩展名不是.txt的扩展名。如果一个文件名不是.txt的扩展名, 那我们的对话框就看不见。同时要确保被存储的文件名有.txt的扩展名。Directory用QTextStream来读和写文件。
Line 286: Line 601:
qmlviewer工具可以导入应用程序当前目录下的文件。我们还可以创建qmldir文件,qmldir包含要导入QML文件的位置,插件和其他资源的位置信息。
qmlviewer工具可以导入应用程序当前目录下的文件。我们还可以创建qmldir文件,qmldir包含要导入QML文件的位置,插件和其他资源的位置信息。


</code><br />In qmldir:
</code>
In qmldir:


Button ./Button.qml<br /> FileDialog ./FileDialog.qml<br /> TextArea ./TextArea.qml<br /> TextEditor ./TextEditor.qml<br /> EditMenu ./EditMenu.qml
Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml


plugin FileDialog plugins<code>
plugin FileDialog plugins<code>
Line 300: Line 620:
FileMenu.qml用Directory元素来通知FileDialog元素目录内容刷新。在信号处理函数onDirectoryChanged里发出通知。
FileMenu.qml用Directory元素来通知FileDialog元素目录内容刷新。在信号处理函数onDirectoryChanged里发出通知。


</code><br />In FileMenu.qml:
</code>
In FileMenu.qml:


Directory{<br /> id:directory<br /> filename: textInput.text<br /> onDirectoryChanged: fileDialog.notifyRefresh()<br /> }<br /><code>
Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
<code>


为了是程序简单,文件对话框总是可见的,并且不会显示无效的文件(不是.txt扩展名)。
为了是程序简单,文件对话框总是可见的,并且不会显示无效的文件(不是.txt扩展名)。


</code><br />In FileDialog.qml:
</code>
In FileDialog.qml:


signal notifyRefresh()<br /> onNotifyRefresh: dirView.model = directory.files<br /><code>
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
<code>


FileDialog元素读取files列表属性并显示目录内容。文件被当作GridView元素的文档,GridView通过代理用网格的形式来显示数据。代理来处理文档的外观,我们的文件对话框创建了文本在中间的网格。点击文件名就会出现一个矩形来选中文件名。当收到notifyRefresh信号时,FileDialog接到通知,重新读取目录的文件。
FileDialog元素读取files列表属性并显示目录内容。文件被当作GridView元素的文档,GridView通过代理用网格的形式来显示数据。代理来处理文档的外观,我们的文件对话框创建了文本在中间的网格。点击文件名就会出现一个矩形来选中文件名。当收到notifyRefresh信号时,FileDialog接到通知,重新读取目录的文件。


</code><br />In FileMenu.qml:
</code>
In FileMenu.qml:


Button{<br /> id: newButton<br /> label: &quot;New&amp;quot;<br /> onButtonClick:{<br /> textArea.textContent = &quot;&quot;<br /> }<br /> }<br /> Button{<br /> id: loadButton<br /> label: &quot;Load&amp;quot;<br /> onButtonClick:{<br /> directory.filename = textInput.text<br /> directory.loadFile&amp;amp;#40;&amp;#41;<br /> textArea.textContent = directory.fileContent<br /> }<br /> }<br /> Button{<br /> id: saveButton<br /> label: &quot;Save&amp;quot;<br /> onButtonClick:{<br /> directory.fileContent = textArea.textContent<br /> directory.filename = textInput.text<br /> directory.saveFile&amp;amp;#40;&amp;#41;<br /> }<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Exit&amp;quot;<br /> onButtonClick:{<br /> Qt.quit()<br /> }<br /> }<br /><code>
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()
}
}
<code>


我门的FileMenu现在连接好了相应的操作。saveButton会把文本从TextEdit传到目录的fileContent属性,然后从可编辑文本框拷贝文件名。最后,这个按钮调用saveFile&amp;amp;#40;&amp;#41;函数来存储文件。loadButton根前面的很类似。New action会清空TextEdit.
我门的FileMenu现在连接好了相应的操作。saveButton会把文本从TextEdit传到目录的fileContent属性,然后从可编辑文本框拷贝文件名。最后,这个按钮调用saveFile()函数来存储文件。loadButton根前面的很类似。New action会清空TextEdit.


另外,EditMenu按钮与TextEdit函数拷贝,粘贴和全选连接到一起。
另外,EditMenu按钮与TextEdit函数拷贝,粘贴和全选连接到一起。

Revision as of 10:23, 25 February 2015

[toc align_right="yes" depth="3"]

本文翻译自:"Getting Started Programming With QML":http://doc.qt.nokia.com/4.7/gettingstartedqml.html

QML编程入门

欢迎来到QML的世界,QML是说明式界面语言。在本文里,我们用QML创建一个简单的文本编辑器。读了本文后,您就可以用QML和Qt C+开发你自己的应用程序了。

h2. 用QML来创建用户界面

我们要创建的程序是一个简单的文本编辑器,它可以读,写和其他的文本操作。本教程包含两部份。第一部分是用QML设计程序界面和功能。第二部分是用Qt C来实现文件读和写。使用Qt的Meta-Object系统,我们可以把C的函数当做QML元素可以使用的属性。使用QML和Qt C,我们可以有效地把界面逻辑和程序逻辑分开。

p=. text editor

要想运行QML程序,可以运行qmlviewer再加上QML文件名作为参数。这个教程的C部分需要你拥有基本的Qt程序编译知识。

教程章节

1 定义按钮和菜单 2 实现菜单条 3 创建文本编辑器 4 装饰文本编辑器 5 用Qt C扩展QML

h2. 定义按钮和菜单

h3. 基本的组件 — 按钮

我们现开始创建一个按钮。从功能上来说,一个按钮有一个鼠标感应区和一个标签。当用户按按钮时,按钮执行动作。 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,用这个文件明作为参数。就可以显示出下面的灰色矩形和文本标签。

p=. simple button

为了实现按钮功能,我们可以使用QML事件处理机制。QML事件处理机制和Qt的signal-slot的机制非常像。信号被出发,连接好的slot函数被调用。

Rectangle{
 id:simplebutton
 

 MouseArea{
 id: buttonMouseArea

 anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
 //onClicked handles valid mouse button clicks
 onClicked: console.log(buttonLabel.text'' " clicked" )
 }
 }

simplebutton中包含了MouseArea元素。MouseArea元素描述了检测鼠标移动的交互区域。对于这个按钮,我们把整个MouseArea固定在它的parent - simplebutton. anchors.fill语句可以读取属性anchors的fill属性。QML用基于anchor的布局来把几个项目固定另外一个项目上,这样可以创建稳定的布局。

如果在指定的MouseArea边界里移动鼠标,MouseArea有很多被调用的信号处理函数,其中之一是onClicked,当鼠标按钮被按下时,onClicked被调用。缺省是鼠标左键。我们可以把动作绑定到onClicked处理函数。在我们的例子里,当鼠标在MouseArea被按下的时候,console.log()输出文本。在调试和输出文本时,函数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中。本文中的代码片断有遗漏,有些代码在前面的章节里已经介绍过,有些代码根本文无关。

自定义的属性用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标签,定义它的属性,实现处理鼠标移动的行为。我们在整个文本编辑器中将很多次在对象中创建对象。

如果这个按钮不想一个组件一样执行操作,那是没什么用的。在下一节里我们要创建包含几个按钮的菜单。

p=. button

创建菜单

现在我们介绍了在一个QML文件里创建元素和定制行为。在这部分,我们介绍如何导入QML元素,如何重用元素来创建新的元素。

菜单显示列表的内容,列表中每一项都可以执行操作。在QML中我们有几种方法来创建菜单。第一种创建包含按钮的菜单,每个按钮可以执行特定的操作。菜单的代码在FileMenu.qml中.

import Qt 4.7 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{}来创建Button元素,这跟Rectangle{}声明类似。

In 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元素里声明了3个Button元素。Row元素里有三个垂直的子对象。Button在我们前面介绍的Button.qml里声明。可以在新建的按钮里声明新的属性绑定,覆盖Button.qml里的属性。名字叫做exitButton被点击时,程序退出。注意除了调用exitButton里的函数onButtonClickon,还有在Button.qml里的信号处理函数onButtonClick也会被调用。

p=. buttons

在Rectangle里声明了Row,为一排按钮创建了一个矩形容器。这个额外的矩形间接地在菜单里组织了按钮。

编辑菜单的声明也非常类似。菜单包含按钮,按钮的标签是拷贝,粘贴和全选。

p=. edit buttons

我们已经知道了如何导入,定制刚才创建的组件,我们可以把这些菜单页组合起来创建一个菜单条。菜单条包含选择菜单的按钮,下面我们看看如何用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里定义了一个简单的编辑菜单。

 VisualItemModel{
 id: menuListModel
 FileMenu{
 width: menuListView.width
 height: menuBar.height
 color: fileColor
 }
 EditMenu{
 color: editColor
 width: menuListView.width
 height: menuBar.height
 }
 }

ListView元素使用代理来显示Model。代理可以声明Model的条目,然后显示一排或者显示在网格里。在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

//control the movement of the menu switching
 snapMode: ListView.SnapOneItem
 orientation: ListView.Horizontal
 boundsBehavior: Flickable.StopAtBounds
 flickDeceleration: 5000
 highlightFollowsCurrentItem: true
 highlightMoveDuration:240
 highlightRangeMode: ListView.StrictlyEnforceRange
 }

另外,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.

 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
 }
 }
 }

我们可以点击上面的菜单名字来弹出刚才创建的菜单条。切换菜单的感觉很直接,响应很快。

p=. menu bar

创建文本编辑器

声明文本区域

我们的文本编辑器必须有文本区域。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区域在Flickable区域里面,如果光标出了可见区域,TextEdit可以自动滚动。ensureVisible()函数可以查看光标在边界外面,然后相应地移动文本区域。QML用Javascript的语法,Javascript文件可以被导入到QML文件里。

 function ensureVisible®{
 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
 }
 }

通过导入重用组件,我们的TextEditor代码非常简单。我们可以定制主程序,而不比担心已经定义的属性。用种方法可以很容易创建UI界面。

p=. text editor

装饰编辑器

实现抽屉接口

我们的编辑器太简单了,我们要装饰它。应用QML我们可以实现渐变和动画。菜单条占了三分之一窗口,应该只有用它的时候才显示它。

我们可以加一个抽屉接口,当点击时,可以扩展菜单条。在我们的程序中,有一个很窄的矩形来响应鼠标点击。抽屉和程序一样,有两个状态,抽屉开和抽屉关。抽屉元素是一条矩形,高度非常小。在抽屉的中心,有一个内嵌的Image元素-箭头图标。当鼠标点击鼠标区域时,抽屉用id screen指定了整个程序的状态.

 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.在PropertyChange元素里声明状态配置。在DRAWER_OPEN状态,有四个条目会有属性变化。第一项,menuBar的y属性变成0。类似地,当状态是DRAWER_OPEN时,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元素中定义,绑定到对象的transitions属性。文本编辑器状态变化到当DRAWER_OPEN或DRAWER_CLOSED时,状态是渐变的。要注意,渐变需要一个from状态和to状态,我们这里用*, 说明渐变适用于所有的装太变化。

在渐变中,我们可以在属性变化中使用动画。menuBar从位置y:0变到y:-partition,我们可以用NumberAnimation元素来产生动画。我们定义渐变的时间和曲线缓和来定制动画。曲线缓和在状态渐变过程中来控制动画的速度和插值。我们选择Easing.OutQuint作为曲线缓和,Easing.OutQuint在动画结束时减慢速度。详情请参考QML的动画文档。

 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只在属性变化时起作用,Behavior可以设定通用的属性变化动画。在文本编辑器中,箭头可以设置NumberAnimation的旋转动画属性。

In TextEditor.qml:

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

让我们回到有状态和动画的组件,我们可以使组件的外感更好看。在Button.qml中,我们在按钮被按下时,加上color和scale属性变化。颜色类型动画使用ColorAnimation,数字使用NumberAnimation. 下面的on propertyName语法在对单一属性时非常有用。

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} }

另外,我们可以加上颜色效果,比如梯度和透明效果。Gradient元素可以覆盖color属性。我们可以用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是一个非常好原型工具,把程序逻辑和用户界面分开。

p=. text editor

h3. 用Qt C来扩展QML

现在我们有文本编辑器的界面,我们可以用C来实现编辑器的功能。用QML和C,我们可以用Qt创建程序。我们可以用Qt的Declarative类在C程序里创建QML环境。我们也可以创建qmlviewer工具可以读取的C插件。在我们的程序里,我们用C来实现读取和存储功能,然后导出插件。这样,我们只要直接读取QML,而不用运行程序文件。

h4. 在QML中使用C的类

我们用Qt和C来实现文件的读取和存储。在QML中注册C的类和函数后,可以在QML中使用他们。C类需要被编译成Qt插件,并且QML需要知道插件的位置。

对于我们的程序,我们需要创建一下东西: 1. Directory类,处理目录的操作 2. File类,从QObject派生,模拟目录的文件列表 3. plugin类,注册到QML的环境中 4. Qt项目文件,编译插件 5. qmldir文件,告诉qmlviewer工具插件的位置

h4. 创建Qt插件

为了创建一个插件, 我们要在Qt项目文件里设置下列东西。首先,必须的源程序,头文件,Qt模块。所有的C代码和项目文件都在filedialg目录里。

In cppPlugins.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模块编译Qt插件,所以模板是lib. 我们把编译好的插件放在父目录的plugins目录里。

把类注册到QML中。

In 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()函数把File类和Diretory类注册到QML里。这个函数需要类模板,重要版本号好,次要版本号和类名字。

我们用Q_EXPORT_PLUGIN2宏来导出插件。注意dialogPlugin.h文件,Q_OBJECT宏必须在类的最前面。此外,我们必须运行qmake来产生必须的元代码。

h4. 在C+类中创建QML属性

我们可以用C和Qt的元对象系统来创建QML元素和属性。我们可以用slots和signal来实现属性,使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声明了属性,同时也声明了元对象系统的读和写的函数。例如,filename属性,是QString类型,用filename()函数来读,用setFilename()来写。另外还声明了一个与filename属性关联的信号叫filenameChanged(),当属性变化时就发出这个信号。在头文件里读和写的函数都是公有函数。

类似地,我们还声明了其他属性。filesCount属性表示目录的文件数。filename属性就是当前所选的文件的名字,读取或存储的文件内容存在fileContent属性里。

Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>

files列表属性是一个目录下所有经过过滤的文件列表Directory类实现了滤掉无效文件的功能只有.txt结尾的文件才是有效的只要在C中声明为QDeclarativeListPropertyQLists可以用在QML文件中使用模板对象需要从QObject派生出来因此File类也要从QObject派生出来在Directory类中File对象的列表存储在QList中名字是m_fileList.
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.

从QML中可以访问通常的C函数文件的读取和存储是用C''+来实现的但需要用Q_INVOKABLE宏来声明的我们也可以把函数声明成slot,这样也可以从QML中访问此函数

In Directory.h:

Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
Directory类还要在目录内容变化时通知其他对象我们用signal来实现这个功能我们前面提过QML信号有相应的以on开始的处理函数这个信号叫directoryChanged当目录更新的时候就会发出这个信号刷新就是重新读取目录内容并更新目录的有效文件列表通过把一个动作关联到onDirectoryChanged信号处理函数来通知QML对象

列表属性需要进一步讨论列表属性用回调函数来读取和修改列表的内容列表属性是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 和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的扩展名 那我们的对话框就看不见同时要确保被存储的文件名有.txt的扩展名Directory用QTextStream来读和写文件

使用Directory元素我们可以读取文件列表在目录里文本文件的数目读取文件名和内容到字符串里如果目录内容发生变化会接到通知

要编译插件对cppPlugins.pro项目文件来运行qmake,运行make编译并把插件拷贝到plugins目录里

==== 在QML里导入插件 ====

qmlviewer工具可以导入应用程序当前目录下的文件我们还可以创建qmldir文件qmldir包含要导入QML文件的位置插件和其他资源的位置信息

In qmldir:

Button ./Button.qml

FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml

plugin FileDialog plugins

我们刚才创建的插件叫FileDialog,在项目文件里的TARGET里被定义的编译好的插件在plugins目录里

==== 把文件对话框集成到文件菜单里 ====

FileMenu要显示FileDialog元素FileDialog里包含目录里的文本文件列表这样用户可以通过点击列表来选择文件我们需要把相应的动作指定到存储读取和创建按钮. FileMenu包含可编辑的输入文本框让用户用键盘来输入文件名

FileMenu.qml用Directory元素来通知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列表属性并显示目录内容文件被当作GridView元素的文档GridView通过代理用网格的形式来显示数据代理来处理文档的外观我们的文件对话框创建了文本在中间的网格点击文件名就会出现一个矩形来选中文件名当收到notifyRefresh信号时FileDialog接到通知重新读取目录的文件

In 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传到目录的fileContent属性,然后从可编辑文本框拷贝文件名。最后,这个按钮调用saveFile()函数来存储文件。loadButton根前面的很类似。New action会清空TextEdit.

另外,EditMenu按钮与TextEdit函数拷贝,粘贴和全选连接到一起。

p=. text editor

文本编辑器完成

text editor

这个程序可以向简单的编辑器一样,接受文本和存储文件。文本编辑器可以读取文件并执行文件操作。