QML Getting Started Chinese Simplified: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[[Category:SimplifiedChinese]]<br />英文原文请参考&amp;quot;这里&amp;quot;:http://doc.qt.nokia.com/4.7/gettingstartedqml.html。
[[Category:SimplifiedChinese]]
英文原文请参考"这里":http://doc.qt.nokia.com/4.7/gettingstartedqml.html。


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


欢迎来到QML的世界!QML是一款全新的陈述性UI编程语言。在本手册中,我们将用QML创建一个简单的文本编辑程序。在阅读完本手册后,你将能够用QML和Qt C+''开发自己的程序。
欢迎来到QML的世界!QML是一款全新的陈述性UI编程语言。在本手册中,我们将用QML创建一个简单的文本编辑程序。在阅读完本手册后,你将能够用QML和Qt C+''开发自己的程序。
<br />h2. 用QML构建用户界面
 
<br />我们要创建的程序是一个简单的文本编辑器,能够加载、保存,并对文本文件进行简单操作。本手册包括两部分:第一部分将使用QML设计程序布局和行为,第二部分则用Qt C实现文件的加载和保存。通过使用Qt的Meta-Object系统,我们能够将C函数以属性的形式暴露给QML元素。通过组合QML和Qt C,我们能够有效的分离程序的界面和逻辑。
h2. 用QML构建用户界面
<br />[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|你的浏览器不支持图片显示]]
 
<br />通过运行qmlviewer工具,并将QML文件路径作为参数传递给她,你就可以运行QML的实例代码了。我们假设你对Qt的编译环节有最基本的了解,以便运行本手册中的C代码。
我们要创建的程序是一个简单的文本编辑器,能够加载、保存,并对文本文件进行简单操作。本手册包括两部分:第一部分将使用QML设计程序布局和行为,第二部分则用Qt C实现文件的加载和保存。通过使用Qt的Meta-Object系统,我们能够将C函数以属性的形式暴露给QML元素。通过组合QML和Qt C,我们能够有效的分离程序的界面和逻辑。
<br />章节:<br />定义按钮和菜单<br />实现菜单栏<br />创建文本编辑器<br />装饰文本编辑器<br />用Qt C扩展QML
 
<br />h2. 定义按钮和菜单
[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|你的浏览器不支持图片显示]]
<br />h3. 基础组件 - 按钮
 
<br />我们从创建按钮开始文本编辑器的工作。功能上讲,按钮是一个能够接受鼠标事件的区域和一个标签。在用户点击按钮后,按钮才开始工作。
通过运行qmlviewer工具,并将QML文件路径作为参数传递给她,你就可以运行QML的实例代码了。我们假设你对Qt的编译环节有最基本的了解,以便运行本手册中的C代码。
<br />在QML中,基本的可视化组件是Rectangle元素。Rectangle元素拥有能够控制元素外观和位置的属性。
 
<br /><code>import Qt 4.7<br />Rectangle {<br /> id: simplebutton<br /> color: &quot;grey&amp;quot;<br /> width: 150; height: 75
章节:
<br /> Text{<br /> id: buttonLabel<br /> anchors.centerIn: parent<br /> text: &quot;button label&amp;quot;<br /> }<br />}</code>
定义按钮和菜单
<br />首先,我们使用“import Qt 4.7”导入预定义的QML元素,以便进一步使用。所有的QML文件中都必须使用该行。需要注意的是,所使用的Qt模块的版本号也必须在该声明中提及。
实现菜单栏
<br />这里用到的简单Rectangle的标识(即id属性)为simplebutton。在QML中,元素的属性定义方式为“属性名:值”。这里,值grey被赋给Rectangle的color属性。类似的,我们也给定了Rectangle的width和height值。
创建文本编辑器
<br />Text元素是不可编辑的文本域。我们将其命名为buttonLabel,并通过设置其text属性来定义其显示的内容。通过设置该标签的anchors.centerIn属性值为parent,我们将标签定位于Rectangle的中心位置。锚可以和其他元素的锚相绑定,从而简化了布局。
装饰文本编辑器
<br />我们将这段代码保存为SimpleButton.qml文件,然后运行qmlviewer,并将文件路径作为参数传递,就能看到如下所示的灰色矩形和文本标签。
用Qt C扩展QML
<br />[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|你的浏览器不支持图片显示]]
 
<br />为了实现按钮的点击功能,我们将用到QML的事件处理。在QML中,事件处理与Qt的信号/槽机制类似:当信号被触发时,与其连接的槽被调用。
h2. 定义按钮和菜单
<br /><code>Rectangle{<br /> id:simplebutton<br />
 
<br /> MouseArea{<br /> id: buttonMouseArea
h3. 基础组件 - 按钮
<br /> anchors.fill: parent // 将鼠标区域设置为该Rectangle所在的区域<br /> // onClicked处理鼠标点击事件<br /> onClicked: console.log(buttonLabel.text'' &quot; clicked&amp;quot; )<br /> }<br />}</code>
 
我们从创建按钮开始文本编辑器的工作。功能上讲,按钮是一个能够接受鼠标事件的区域和一个标签。在用户点击按钮后,按钮才开始工作。
 
在QML中,基本的可视化组件是Rectangle元素。Rectangle元素拥有能够控制元素外观和位置的属性。
 
<code>import Qt 4.7
Rectangle {
id: simplebutton
color: "grey"
width: 150; height: 75
 
Text{
id: buttonLabel
anchors.centerIn: parent
text: "button label"
}
}</code>
 
首先,我们使用“import Qt 4.7”导入预定义的QML元素,以便进一步使用。所有的QML文件中都必须使用该行。需要注意的是,所使用的Qt模块的版本号也必须在该声明中提及。
 
这里用到的简单Rectangle的标识(即id属性)为simplebutton。在QML中,元素的属性定义方式为“属性名:值”。这里,值grey被赋给Rectangle的color属性。类似的,我们也给定了Rectangle的width和height值。
 
Text元素是不可编辑的文本域。我们将其命名为buttonLabel,并通过设置其text属性来定义其显示的内容。通过设置该标签的anchors.centerIn属性值为parent,我们将标签定位于Rectangle的中心位置。锚可以和其他元素的锚相绑定,从而简化了布局。
 
我们将这段代码保存为SimpleButton.qml文件,然后运行qmlviewer,并将文件路径作为参数传递,就能看到如下所示的灰色矩形和文本标签。
 
[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|你的浏览器不支持图片显示]]
 
为了实现按钮的点击功能,我们将用到QML的事件处理。在QML中,事件处理与Qt的信号/槽机制类似:当信号被触发时,与其连接的槽被调用。
 
<code>Rectangle{
id:simplebutton
 
MouseArea{
id: buttonMouseArea
 
anchors.fill: parent // 将鼠标区域设置为该Rectangle所在的区域
// onClicked处理鼠标点击事件
onClicked: console.log(buttonLabel.text'' " clicked" )
}
}</code>


我们在simplebutton内包含了一个MouseArea元素,其描述了能够接受鼠标事件的区域。这里,我们将鼠标的区域设置为整个Rectangle所在的区域。anchors.fill提供了一种用以访问属性anchors所具有的属性fill的方法。QML使用基于锚的布局,以便元素之间相互绑定,从而创建稳定的布局。
我们在simplebutton内包含了一个MouseArea元素,其描述了能够接受鼠标事件的区域。这里,我们将鼠标的区域设置为整个Rectangle所在的区域。anchors.fill提供了一种用以访问属性anchors所具有的属性fill的方法。QML使用基于锚的布局,以便元素之间相互绑定,从而创建稳定的布局。
Line 31: Line 73:
这段在SimpleButton.qml中的代码已经足够被用于在屏幕上显示按钮,并在按钮被点击时输出一段文本信息。
这段在SimpleButton.qml中的代码已经足够被用于在屏幕上显示按钮,并在按钮被点击时输出一段文本信息。


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


// 通过条件操作符来决定按钮的颜色<br /> color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br />}</code>
// 通过条件操作符来决定按钮的颜色
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}</code>


Button.qml中的代码展示了一个功能完整的按钮。这里展示的代码中则省略了一部分,因为在前面已经介绍过,或是和当前的讨论没有直接关系。
Button.qml中的代码展示了一个功能完整的按钮。这里展示的代码中则省略了一部分,因为在前面已经介绍过,或是和当前的讨论没有直接关系。
Line 61: Line 117:
菜单能够显示多个菜单项,每个菜单项都能够执行一个特定的动作。在QML中,我们能用不同的方法创建菜单。首先,我们将创建创建一个包含多个按钮的菜单,点击这些按钮将触发不同的动作。菜单的源代码在FileMenu.qml文件中。
菜单能够显示多个菜单项,每个菜单项都能够执行一个特定的动作。在QML中,我们能用不同的方法创建菜单。首先,我们将创建创建一个包含多个按钮的菜单,点击这些按钮将触发不同的动作。菜单的源代码在FileMenu.qml文件中。


<code>import Qt 4.7 // 导入Qt QML模块<br />import &quot;folderName&amp;quot; //导入该目录的内容<br />import &quot;script.js&amp;quot; as Script // 导入一个名为script.js的JavaScript文件,并将其命名为Script</code>
<code>import Qt 4.7 // 导入Qt QML模块
import "folderName" //导入该目录的内容
import "script.js" as Script // 导入一个名为script.js的JavaScript文件,并将其命名为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{}”声明一样。


在FileMenu.qml文件中:<br /><code>Row{<br /> anchors.centerIn: parent<br /> spacing: parent.width/6
在FileMenu.qml文件中:
<code>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 /> }
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}


Button{<br /> buttonColor: &quot;grey&amp;quot;<br /> id: saveButton<br /> label: &quot;Save&amp;quot;<br /> }
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}


Button{<br /> id: exitButton<br /> label: &quot;Exit&amp;quot;<br /> buttonColor: &quot;darkgrey&amp;quot;
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"


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


我们定义了三个Button元素。他们都在Row元素内被定义,Row元素能够自动将其子元素放置在同一行中。Button的定义则位于同一目录下的Button.qml文件中,在前一节中已经讨论和使用过。我们可以在这些新创建的按钮中添加新的属性,从而覆盖在Button.qml中定义的那些属性。点击名为exitButton的按钮能够关闭当前关闭当前窗口。而在Button.qml中定义的onButtonClick也同样会被调用。
我们定义了三个Button元素。他们都在Row元素内被定义,Row元素能够自动将其子元素放置在同一行中。Button的定义则位于同一目录下的Button.qml文件中,在前一节中已经讨论和使用过。我们可以在这些新创建的按钮中添加新的属性,从而覆盖在Button.qml中定义的那些属性。点击名为exitButton的按钮能够关闭当前关闭当前窗口。而在Button.qml中定义的onButtonClick也同样会被调用。
Line 97: Line 171:
我们在menuListModel中声明了两个可视化项,FileMenu和EditMenu。我们自定义了这两个菜单,并用ListView来展现它们。MenuBar.qml文件中包含了相应的QML声明,而编辑菜单则在EditMenu.qml中定义。
我们在menuListModel中声明了两个可视化项,FileMenu和EditMenu。我们自定义了这两个菜单,并用ListView来展现它们。MenuBar.qml文件中包含了相应的QML声明,而编辑菜单则在EditMenu.qml中定义。


<code>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 />}</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元素能够通过delegate来展现模型的数据。而delegate能够通过声明模型项来在Row元素或格子中展现数据。这里的menuListModel已经具有了可视化项,因此我们不再需要声明delegate。
ListView元素能够通过delegate来展现模型的数据。而delegate能够通过声明模型项来在Row元素或格子中展现数据。这里的menuListModel已经具有了可视化项,因此我们不再需要声明delegate。


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


// 锚被定义来反应窗口的锚<br /> anchors.fill:parent<br /> anchors.bottom: parent.bottom<br /> width:parent.width<br /> height: parent.height
// 锚被定义来反应窗口的锚
anchors.fill:parent
anchors.bottom: parent.bottom
width:parent.width
height: parent.height


// 该模型包含了数据<br /> model: menuListModel
// 该模型包含了数据
model: menuListModel


// 控制菜单交换时的移动<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 />}</code>
// 控制菜单交换时的移动
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 5000
highlightFollowsCurrentItem: true
highlightMoveDuration:240
highlightRangeMode: ListView.StrictlyEnforceRange
}</code>


此外,ListView继承自Flickable,使得其能够相应鼠标的拖拽和其他姿势。上面这段代码的最后一部分设置了Flickable的属性,以便创建所需的效果。特别的,highlightMoveDuration属性改变了按键转换。更高的highlightMoveDuration值意味着更慢的菜单转换。
此外,ListView继承自Flickable,使得其能够相应鼠标的拖拽和其他姿势。上面这段代码的最后一部分设置了Flickable的属性,以便创建所需的效果。特别的,highlightMoveDuration属性改变了按键转换。更高的highlightMoveDuration值意味着更慢的菜单转换。
Line 115: Line 215:
labelList矩形的z值为1,意味着它显示在菜单栏之前。z值越高的项显示得越靠前,默认值为0。
labelList矩形的z值为1,意味着它显示在菜单栏之前。z值越高的项显示得越靠前,默认值为0。


<code>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 />}</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 127: Line 247:
如果没有可编辑的文本域,我们的程序就不能被称为文本编辑器了。而QML就恰好提供了一个TextEdit元素来进行多行的文本编辑。TextEdit和Text元素不同,后者不允许用户直接编辑其中的文本。
如果没有可编辑的文本域,我们的程序就不能被称为文本编辑器了。而QML就恰好提供了一个TextEdit元素来进行多行的文本编辑。TextEdit和Text元素不同,后者不允许用户直接编辑其中的文本。


<code>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 />}</code>
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}</code>


编辑器能够设置其中文本的字体颜色属性。这里,TextEdit所在的区域位于一个flickable区域之内。如果文本过长,则能够提供滚动条的功能。函数ensureVisible()会自动检查光标所在位置,并移动文本到相应位置。QML使用JavaScript作为其脚本语言。正如前面提及的那样,JavaScript文件能够导入到QML文件中。
编辑器能够设置其中文本的字体颜色属性。这里,TextEdit所在的区域位于一个flickable区域之内。如果文本过长,则能够提供滚动条的功能。函数ensureVisible()会自动检查光标所在位置,并移动文本到相应位置。QML使用JavaScript作为其脚本语言。正如前面提及的那样,JavaScript文件能够导入到QML文件中。


<code>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 />}</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 141: Line 276:
我们现在可以用QML创建文本编辑器的布局了。这个文本编辑器有两个组件:先前创建的菜单栏和文本区域。QML允许我们重用组件,因此我们能够通过导入组件和在必要时自定义来简化代码。我们的文本编辑器的窗口包含两个部分:三分之一为菜单栏,剩下三分之二为文本域。菜单栏在其他元素前显示。
我们现在可以用QML创建文本编辑器的布局了。这个文本编辑器有两个组件:先前创建的菜单栏和文本区域。QML允许我们重用组件,因此我们能够通过导入组件和在必要时自定义来简化代码。我们的文本编辑器的窗口包含两个部分:三分之一为菜单栏,剩下三分之二为文本域。菜单栏在其他元素前显示。


<code>Rectangle{<br /> id: screen<br /> width: 1000; height: 1000
<code>Rectangle{
id: screen
width: 1000; height: 1000


// 屏幕被分为MenuBar和TextArea两部分。MenuBar占据1/3的空间<br /> property int partition: height/3
// 屏幕被分为MenuBar和TextArea两部分。MenuBar占据1/3的空间
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 />}</code>
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}</code>


通过导入可重用的组件,我们的TextEditor的代码显得相当简单。我们能够自定义主程序,而不用担心已经被定义好的属性等。通过该途径,程序布局和UI组件都能够很容易的被创建。
通过导入可重用的组件,我们的TextEditor的代码显得相当简单。我们能够自定义主程序,而不用担心已经被定义好的属性等。通过该途径,程序布局和UI组件都能够很容易的被创建。
Line 161: Line 312:
我们需要添加一个绘图接口,在被点击时显示或收回菜单栏。在我们的实现中,我们有一个矩形来响应鼠标点击事件。和程序一样,这个绘图接口有“打开”和“关闭”两个状态。该绘图项是一个高度不大的矩形。其内部有一个Image元素来显示箭头图标。该绘图接口向整个程序赋予了一个状态,以响应用户的鼠标点击事件。
我们需要添加一个绘图接口,在被点击时显示或收回菜单栏。在我们的实现中,我们有一个矩形来响应鼠标点击事件。和程序一样,这个绘图接口有“打开”和“关闭”两个状态。该绘图项是一个高度不大的矩形。其内部有一个Image元素来显示箭头图标。该绘图接口向整个程序赋予了一个状态,以响应用户的鼠标点击事件。


<code>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 />}</code>
onClicked:{
if (screen.state  "DRAWER_CLOSED"){
         screen.state = "DRAWER_OPEN"
       }
       else if (screen.state  "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
}
}</code>


状态就是各种配置的一个简单集合,用State元素表示。一系列的状态能够被一一列出,并绑定到状态属性上。在我们的程序中,有两个被称为DRAWER_CLOSED和DRAWER_OPEN的状态。配置通过PropertyChanges元素被声明。在DRAWER_OPEN状态上,有四个项能够接受属性的变化。第一个目标,menuBar,会将其y属性改为0。类似的,textArea会在DRAWER_OPEN状态时降低到一个新的位置。而textArea、绘图器及其图标都会改变属性以实现当前状态。
状态就是各种配置的一个简单集合,用State元素表示。一系列的状态能够被一一列出,并绑定到状态属性上。在我们的程序中,有两个被称为DRAWER_CLOSED和DRAWER_OPEN的状态。配置通过PropertyChanges元素被声明。在DRAWER_OPEN状态上,有四个项能够接受属性的变化。第一个目标,menuBar,会将其y属性改为0。类似的,textArea会在DRAWER_OPEN状态时降低到一个新的位置。而textArea、绘图器及其图标都会改变属性以实现当前状态。


<code>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 />]</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元素定义,并且能够绑定到该项的transition属性上。这里,我们的文本编辑器在其状态转换到DRAWER_OPEN或者DRAWER_CLOSED时都会触发一个转换。重要的是,转换需要设置一个from和一个to属性,定义在何时被触发。我们也可以用*使其在任何状态转换发生时被触发。
状态改变需要辅以更加流畅的转换。状态间的转换通过Transition元素定义,并且能够绑定到该项的transition属性上。这里,我们的文本编辑器在其状态转换到DRAWER_OPEN或者DRAWER_CLOSED时都会触发一个转换。重要的是,转换需要设置一个from和一个to属性,定义在何时被触发。我们也可以用*使其在任何状态转换发生时被触发。
Line 178: Line 360:
在转换过程中,我们可以将动画和属性改变相绑定。我们能够用NumberAnimation元素将menuBar从y:0到y:-partition的位置改变添加动画效果。我们声明目标的属性会以一个特定的曲线在一段时间内改变。这个曲线(easing curve)负责控制动画的速度,以及在状态转化间的行为。我们在这里选择的曲线是Easing.OutQuint,能够使移动在快结束时放慢。请参考QML的相关文章获取更多的动画信息。
在转换过程中,我们可以将动画和属性改变相绑定。我们能够用NumberAnimation元素将menuBar从y:0到y:-partition的位置改变添加动画效果。我们声明目标的属性会以一个特定的曲线在一段时间内改变。这个曲线(easing curve)负责控制动画的速度,以及在状态转化间的行为。我们在这里选择的曲线是Easing.OutQuint,能够使移动在快结束时放慢。请参考QML的相关文章获取更多的动画信息。


<code>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 />]</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元素。转换只在状态变化的过程中有效,并且Behavior能够为属性的变化设置动画。在这个文本编辑器中,该箭头使用了NumberAnimation来为其rotation属性设置动画。
另一个对属性改变添加动画的方法是声明Behavior元素。转换只在状态变化的过程中有效,并且Behavior能够为属性的变化设置动画。在这个文本编辑器中,该箭头使用了NumberAnimation来为其rotation属性设置动画。


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


回想那些有状态和动画的组件,我们能够改进其外观。在Button.qml中,我们能够定义按钮被点击时的颜色和scale属性,还能通过ColorAnimation来为颜色改变添加动画,也能用NumberAnimation为数字属性的改变添加动画效果。下面的&amp;quot;on 属性名&amp;quot;语法在为单个属性添加动画时非常有用。
回想那些有状态和动画的组件,我们能够改进其外观。在Button.qml中,我们能够定义按钮被点击时的颜色和scale属性,还能通过ColorAnimation来为颜色改变添加动画,也能用NumberAnimation为数字属性的改变添加动画效果。下面的"on 属性名"语法在为单个属性添加动画时非常有用。


在Button.qml中:<br /><code>color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br />Behavior on color { ColorAnimation{ duration: 55} }
在Button.qml中:
<code>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} }</code>
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }</code>


此外,我们能够通过添加例如gradients和透明度等颜色效果来改进QML组件的外观。声明一个Gradient元素能够覆盖元素的颜色属性。你可以在gradient中用GradientStop元素来声明颜色。gradient取值为0.0到1.0之间。
此外,我们能够通过添加例如gradients和透明度等颜色效果来改进QML组件的外观。声明一个Gradient元素能够覆盖元素的颜色属性。你可以在gradient中用GradientStop元素来声明颜色。gradient取值为0.0到1.0之间。


在MenuBar.qml中:<br /><code>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 />}</code>
在MenuBar.qml中:
<code>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>


这里的gradient被用到菜单栏上来显示渐变的深度。第一个颜色被0.0位置定义,最后一个则在1.0处。
这里的gradient被用到菜单栏上来显示渐变的深度。第一个颜色被0.0位置定义,最后一个则在1.0处。
Line 199: Line 400:


我们已经完成了对这个简单的文本编辑器的用户界面的构建。现在,我们可以用普通的Qt和C+''来实现程序的逻辑了。QML能够很好的被用作构建原型的工具,将程序的逻辑和UI设计相分离。
我们已经完成了对这个简单的文本编辑器的用户界面的构建。现在,我们可以用普通的Qt和C+''来实现程序的逻辑了。QML能够很好的被用作构建原型的工具,将程序的逻辑和UI设计相分离。
<br />[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|你的浏览器不支持显示图片]]
 
<br />h2. 用Qt C扩展QML
[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|你的浏览器不支持显示图片]]
<br />现在,我们已经拥有了文本编辑器的界面,接下来就要用C来实现其功能。通过QML和C的混合使用,我们能够用Qt开发程序的逻辑。我们可以在C程序中通过Qt的Declarative类来创建QML上下文,并在Graphics Scene中显示QML元素。此外,我们也能将C代码编译成插件,以便在qmlviewer中被调用。这里,我们会将开发后的程序编译成插件。这样,我们就能直接(用qmlviewer)加载QML文件,而不必运行一个可执行文件了。
 
<br />h3. 将C类暴露给QML
h2. 用Qt C扩展QML
<br />我们会用Qt和C实现文件的加载和保存。通过注册,我们可以在QML中使用C类和函数。然后将C代码编译称Qt的插件,以便QML文件能够加载。
 
<br />在我们的程序中,我们需要创建以下项:<br /># Directory类已负责处理目录相关的操作<br /># File类是QObject,展现目录里的文件列表<br /># 插件类以便将C类注册到QML上下文<br /># Qt工程文件,以便编译插件<br /># 一个qmldir文件,告诉qmlviewer何处加载插件
现在,我们已经拥有了文本编辑器的界面,接下来就要用C来实现其功能。通过QML和C的混合使用,我们能够用Qt开发程序的逻辑。我们可以在C程序中通过Qt的Declarative类来创建QML上下文,并在Graphics Scene中显示QML元素。此外,我们也能将C代码编译成插件,以便在qmlviewer中被调用。这里,我们会将开发后的程序编译成插件。这样,我们就能直接(用qmlviewer)加载QML文件,而不必运行一个可执行文件了。
<br />h3. 创建Qt plugin
 
<br />我们需要在Qt工程文件中添加如下代码以编译插件,包括必要的源文件、头文件,以及Qt模块等。所有的C代码和工程文件都存放在filedialog目录下。
h3. 将C类暴露给QML
<br />在cppPlugins.pro文件中:<br /><code>TEMPLATE = lib<br />CONFIG''= qt plugin<br />QT ''= declarative
 
<br />DESTDIR''= ../plugins<br />OBJECTS_DIR = tmp<br />MOC_DIR = tmp
我们会用Qt和C实现文件的加载和保存。通过注册,我们可以在QML中使用C类和函数。然后将C代码编译称Qt的插件,以便QML文件能够加载。
 
在我们的程序中,我们需要创建以下项:
# Directory类已负责处理目录相关的操作
# File类是QObject,展现目录里的文件列表
# 插件类以便将C类注册到QML上下文
# Qt工程文件,以便编译插件
# 一个qmldir文件,告诉qmlviewer何处加载插件
 
h3. 创建Qt plugin
 
我们需要在Qt工程文件中添加如下代码以编译插件,包括必要的源文件、头文件,以及Qt模块等。所有的C代码和工程文件都存放在filedialog目录下。
 
在cppPlugins.pro文件中:
<code>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</code>
 
SOURCES''= directory.cpp  file.cpp  dialogPlugin.cpp</code>


特别的,我们添加了Qt的declarative模块,将工程设置为插件类型,并把编译好的插件放到上层目录的plugins目录下。
特别的,我们添加了Qt的declarative模块,将工程设置为插件类型,并把编译好的插件放到上层目录的plugins目录下。
Line 219: Line 441:
=== 向QML注册类 ===
=== 向QML注册类 ===


在dialogPlugin.h文件中:<br /><code>#include &lt;QtDeclarative/QDeclarativeExtensionPlugin&amp;gt;
在dialogPlugin.h文件中:
<code>#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);


};</code>
};</code>
Line 229: Line 455:
我们的插件类DialogPlugin继承自QDeclarativeExtensionPlugin,需要实现其registerTypes()函数。而相应的源文件dialogPlugin.cpp如下所示:
我们的插件类DialogPlugin继承自QDeclarativeExtensionPlugin,需要实现其registerTypes()函数。而相应的源文件dialogPlugin.cpp如下所示:


DialogPlugin.cpp:<br /><code>#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;
DialogPlugin.cpp:
<code>#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);</code>
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
<br />这里的registerTypes()函数将我们的File和Directory两个类注册到QML上下文中。注册用的函数qmlRegisterType需要相应的类名作为其模板,一个主版本号、副版本号,以及QML中展示的类名作为参数。
qmlRegisterType<File>(uri, 1, 0,"File");
<br />最后,我们需要用Q_EXPORT_PLUGIN2宏来导出插件。注意,我们在头文件的类声明中也添加了Q_OBJECT宏,需要运行qmake来自动创建相应的元对象代码。
}
<br />h3. 在C+''类中创建QML属性
 
<br />我们能够使用C,通过Qt的元对象系统来创建QML元素。我们能够通过信号和槽机制来实现属性,使其能被Qt用到QML上。
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);</code>
<br />我们需要为文本编辑器提供加载和保存文件的功能。典型的,该功能包含在文件对话框中。幸运的是,我们可以使用QDir、QFile和QTextStream来实现目录的读取和输入输出流。
 
<br /><code>class Directory : public QObject{
这里的registerTypes()函数将我们的File和Directory两个类注册到QML上下文中。注册用的函数qmlRegisterType需要相应的类名作为其模板,一个主版本号、副版本号,以及QML中展示的类名作为参数。
<br /> Q_OBJECT
 
<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 )
最后,我们需要用Q_EXPORT_PLUGIN2宏来导出插件。注意,我们在头文件的类声明中也添加了Q_OBJECT宏,需要运行qmake来自动创建相应的元对象代码。
<br /> …</code>
 
<br />该Directory类使用了Qt的元对象系统来注册其为文件操作而需要的属性,并以插件形式导出到QML中作为Directory元素。这里用Q_PROPERTY宏定义的每个属性都被导出称一个QML属性。
h3. 在C+''类中创建QML属性
<br />Q_PROPERTY宏声明了属性,使其能够通过Qt的元对象系统被读写。例如QString类型的filename属性,可以通过filename()函数进行读取,并通过setFilename()函数进行写操作。此外,还有一个名为filenameChanged()的信号与其绑定,会在该属性被改变时触发。读写的函数都在头文件中被定义为public函数。
 
<br />Similarly, we have the other properties declared according to their uses. The filesCount property indicates the number of files in a directory. The filename property is set to the currently selected file's name and the loaded/saved file content is stored in fileContent property.
我们能够使用C,通过Qt的元对象系统来创建QML元素。我们能够通过信号和槽机制来实现属性,使其能被Qt用到QML上。
<br /><code>Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )<code>
 
<br />The files list property is a list of all the filtered files in a directory. The Directory class is implemented to filter out invalid text files; only files with a .txt extension are valid. Further, QLists can be used in QML files by declaring them as a QDeclarativeListProperty in C. The templated object needs to inherit from a QObject, therefore, the File class must also inherit from QObject. In the Directory class, the list of File objects is stored in a QList called m_fileList.
我们需要为文本编辑器提供加载和保存文件的功能。典型的,该功能包含在文件对话框中。幸运的是,我们可以使用QDir、QFile和QTextStream来实现目录的读取和输入输出流。
<br /></code>class File : public QObject{
 
<br /> Q_OBJECT<br /> Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
<code>class Directory : public QObject{
<br /> <br />};<code>
 
<br />The properties can then be used in QML as part of the Directory element's properties. Note that we do not have to create an identifier id property in our C''+ code.
Q_OBJECT
<br /></code>Directory{<br /> id: directory
 
<br /> filesCount<br /> filename<br /> fileContent<br /> files
Q_PROPERTY(int filesCount READ filesCount CONSTANT)
<br /> files[0].name<br />}<code>
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
<br />Because QML uses Javascript's syntax and structure, we can iterate through the list of files and retrieve its properties. To retrieve the first file's name property, we can call files[0].name.
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
<br />Regular C++ functions are also accessible from QML. The file loading and saving functions are implemented in C++ and declared using the Q_INVOKABLE macro. Alternatively, we can declare the functions as a slot and the functions will be accessible from QML.
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
<br />In Directory.h:<br /></code>Q_INVOKABLE void saveFile&amp;amp;#40;&amp;#41;;<br />Q_INVOKABLE void loadFile&amp;amp;#40;&amp;#41;;<code>
 
<br />The Directory class also has to notify other objects whenever the directory contents change. This feature is performed using a signal. As previously mentioned, QML signals have a corresponding handler with their names prepended with on. The signal is called directoryChanged and it is emitted whenever there is a directory refresh. The refresh simply reloads the directory contents and updates the list of valid files in the directory. QML items can then be notified by attaching an action to the onDirectoryChanged signal handler.
…</code>
<br />The list properties need to be explored further. This is because list properties use callbacks to access and modify the list contents. The list property is of type QDeclarativeListProperty&amp;lt;File&amp;gt;. Whenever the list is accessed, the accessor function needs to return a QDeclarativeListProperty&amp;lt;File&amp;gt;. The template type, File, needs to be a QObject derivative. Further, to create the QDeclarativeListProperty, the list's accessor and modifiers need to be passed to the consructor as function pointers. The list, a QList in our case, also needs to be a list of File pointers.
 
<br />The constructor of QDeclarativeListProperty constructor and the Directory implementation:<br /></code>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 );<code>
该Directory类使用了Qt的元对象系统来注册其为文件操作而需要的属性,并以插件形式导出到QML中作为Directory元素。这里用Q_PROPERTY宏定义的每个属性都被导出称一个QML属性。
 
Q_PROPERTY宏声明了属性,使其能够通过Qt的元对象系统被读写。例如QString类型的filename属性,可以通过filename()函数进行读取,并通过setFilename()函数进行写操作。此外,还有一个名为filenameChanged()的信号与其绑定,会在该属性被改变时触发。读写的函数都在头文件中被定义为public函数。
 
Similarly, we have the other properties declared according to their uses. The filesCount property indicates the number of files in a directory. The filename property is set to the currently selected file's name and the loaded/saved file content is stored in fileContent property.
 
<code>Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>
 
The files list property is a list of all the filtered files in a directory. The Directory class is implemented to filter out invalid text files; only files with a .txt extension are valid. Further, QLists can be used in QML files by declaring them as a QDeclarativeListProperty in C. The templated object needs to inherit from a QObject, therefore, the File class must also inherit from QObject. In the Directory class, the list of File objects is stored in a QList called m_fileList.
 
</code>class File : public QObject{
 
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
 
};<code>
 
The properties can then be used in QML as part of the Directory element's properties. Note that we do not have to create an identifier id property in our C''+ code.
 
</code>Directory{
id: directory
 
filesCount
filename
fileContent
files
 
files[0].name
}<code>
 
Because QML uses Javascript's syntax and structure, we can iterate through the list of files and retrieve its properties. To retrieve the first file's name property, we can call files[0].name.
 
Regular C++ functions are also accessible from QML. The file loading and saving functions are implemented in C++ and declared using the Q_INVOKABLE macro. Alternatively, we can declare the functions as a slot and the functions will be accessible from QML.
 
In Directory.h:
</code>Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();<code>
 
The Directory class also has to notify other objects whenever the directory contents change. This feature is performed using a signal. As previously mentioned, QML signals have a corresponding handler with their names prepended with on. The signal is called directoryChanged and it is emitted whenever there is a directory refresh. The refresh simply reloads the directory contents and updates the list of valid files in the directory. QML items can then be notified by attaching an action to the onDirectoryChanged signal handler.
 
The list properties need to be explored further. This is because list properties use callbacks to access and modify the list contents. The list property is of type QDeclarativeListProperty<File>. Whenever the list is accessed, the accessor function needs to return a QDeclarativeListProperty<File>. The template type, File, needs to be a QObject derivative. Further, to create the QDeclarativeListProperty, the list's accessor and modifiers need to be passed to the consructor as function pointers. The list, a QList in our case, also needs to be a list of File pointers.
 
The constructor of QDeclarativeListProperty constructor and the Directory implementation:
</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>


The constructor passes pointers to functions that will append the list, count the list, retrieve the item using an index, and empty the list. Only the append function is mandatory. Note that the function pointers must match the definition of AppendFunction, CountFunction, AtFunction, or ClearFunction.
The constructor passes pointers to functions that will append the list, count the list, retrieve the item using an index, and empty the list. Only the append function is mandatory. Note that the function pointers must match the definition of AppendFunction, CountFunction, AtFunction, or ClearFunction.


</code>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>


To simplify our file dialog, the Directory class filters out invalid text files, which are files that do not have a .txt extension. If a file name doesn't have the .txt extension, then it won't be seen in our file dialog. Also, the implementation makes sure that saved files have a .txt extension in the file name. Directory uses QTextStream to read the file and to output the file contents to a file.
To simplify our file dialog, the Directory class filters out invalid text files, which are files that do not have a .txt extension. If a file name doesn't have the .txt extension, then it won't be seen in our file dialog. Also, the implementation makes sure that saved files have a .txt extension in the file name. Directory uses QTextStream to read the file and to output the file contents to a file.
Line 276: Line 554:
The qmlviewer tool imports files that are in the same directory as the application. We can also create a qmldir file containing the locations of QML files we wish to import. The qmldir file can also store locations of plugins and other resources.
The qmlviewer tool imports files that are in the same directory as the application. We can also create a qmldir file containing the locations of QML files we wish to import. The qmldir file can also store locations of plugins and other resources.


In qmldir:<br /></code>Button ./Button.qml<br />FileDialog ./FileDialog.qml<br />TextArea ./TextArea.qml<br />TextEditor ./TextEditor.qml<br />EditMenu ./EditMenu.qml
In qmldir:
</code>Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml


plugin FileDialog plugins<code>
plugin FileDialog plugins<code>
Line 288: Line 571:
The Directory element is used in the FileMenu.qml file and it notifies the FileDialog element that the directory refreshed its contents. This notification is performed in the signal handler, onDirectoryChanged.
The Directory element is used in the FileMenu.qml file and it notifies the FileDialog element that the directory refreshed its contents. This notification is performed in the signal handler, onDirectoryChanged.


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


Keeping with the simplicity of our application, the file dialog will always be visible and will not display invalid text files, which do not have a .txt extension to their filenames.
Keeping with the simplicity of our application, the file dialog will always be visible and will not display invalid text files, which do not have a .txt extension to their filenames.


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


The FileDialog element will display the contents of a directory by reading its list property called files. The files are used as the model of a GridView element, which displays data items in a grid according to a delegate. The delegate handles the appearance of the model and our file dialog will simply create a grid with text centered in the middle. Clicking on the file name will result in the appearance of a rectangle to highlight the file name. The FileDialog is notified whenever the notifyRefresh signal is emitted, reloading the files in the directory.
The FileDialog element will display the contents of a directory by reading its list property called files. The files are used as the model of a GridView element, which displays data items in a grid according to a delegate. The delegate handles the appearance of the model and our file dialog will simply create a grid with text centered in the middle. Clicking on the file name will result in the appearance of a rectangle to highlight the file name. The FileDialog is notified whenever the notifyRefresh signal is emitted, reloading the files in the directory.


In FileMenu.qml:<br /></code>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 />}<code>
In FileMenu.qml:
</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>


Our FileMenu can now connect to their respective actions. The saveButton will transfer the text from the TextEdit onto the directory's fileContent property, then copy its file name from the editable text input. Finally, the button calls the saveFile&amp;amp;#40;&amp;#41; function, saving the file. The sloadButton has a similar execution. Also, the New action will empty the contents of the TextEdit.
Our FileMenu can now connect to their respective actions. The saveButton will transfer the text from the TextEdit onto the directory's fileContent property, then copy its file name from the editable text input. Finally, the button calls the saveFile() function, saving the file. The sloadButton has a similar execution. Also, the New action will empty the contents of the TextEdit.


Further, the EditMenu buttons are connected to the TextEdit functions to copy, paste, and select all the text in the text editor.
Further, the EditMenu buttons are connected to the TextEdit functions to copy, paste, and select all the text in the text editor.

Revision as of 09:24, 25 February 2015

英文原文请参考"这里":http://doc.qt.nokia.com/4.7/gettingstartedqml.html。

QML编程入门

欢迎来到QML的世界!QML是一款全新的陈述性UI编程语言。在本手册中,我们将用QML创建一个简单的文本编辑程序。在阅读完本手册后,你将能够用QML和Qt C+开发自己的程序。

h2. 用QML构建用户界面

我们要创建的程序是一个简单的文本编辑器,能够加载、保存,并对文本文件进行简单操作。本手册包括两部分:第一部分将使用QML设计程序布局和行为,第二部分则用Qt C实现文件的加载和保存。通过使用Qt的Meta-Object系统,我们能够将C函数以属性的形式暴露给QML元素。通过组合QML和Qt C,我们能够有效的分离程序的界面和逻辑。

你的浏览器不支持图片显示

通过运行qmlviewer工具,并将QML文件路径作为参数传递给她,你就可以运行QML的实例代码了。我们假设你对Qt的编译环节有最基本的了解,以便运行本手册中的C代码。

章节: 定义按钮和菜单 实现菜单栏 创建文本编辑器 装饰文本编辑器 用Qt C扩展QML

h2. 定义按钮和菜单

h3. 基础组件 - 按钮

我们从创建按钮开始文本编辑器的工作。功能上讲,按钮是一个能够接受鼠标事件的区域和一个标签。在用户点击按钮后,按钮才开始工作。

在QML中,基本的可视化组件是Rectangle元素。Rectangle元素拥有能够控制元素外观和位置的属性。

import Qt 4.7
Rectangle {
 id: simplebutton
 color: "grey"
 width: 150; height: 75

 Text{
 id: buttonLabel
 anchors.centerIn: parent
 text: "button label"
 }
}

首先,我们使用“import Qt 4.7”导入预定义的QML元素,以便进一步使用。所有的QML文件中都必须使用该行。需要注意的是,所使用的Qt模块的版本号也必须在该声明中提及。

这里用到的简单Rectangle的标识(即id属性)为simplebutton。在QML中,元素的属性定义方式为“属性名:值”。这里,值grey被赋给Rectangle的color属性。类似的,我们也给定了Rectangle的width和height值。

Text元素是不可编辑的文本域。我们将其命名为buttonLabel,并通过设置其text属性来定义其显示的内容。通过设置该标签的anchors.centerIn属性值为parent,我们将标签定位于Rectangle的中心位置。锚可以和其他元素的锚相绑定,从而简化了布局。

我们将这段代码保存为SimpleButton.qml文件,然后运行qmlviewer,并将文件路径作为参数传递,就能看到如下所示的灰色矩形和文本标签。

你的浏览器不支持图片显示

为了实现按钮的点击功能,我们将用到QML的事件处理。在QML中,事件处理与Qt的信号/槽机制类似:当信号被触发时,与其连接的槽被调用。

Rectangle{
 id:simplebutton
 

 MouseArea{
 id: buttonMouseArea

 anchors.fill: parent // 将鼠标区域设置为该Rectangle所在的区域
 // onClicked处理鼠标点击事件
 onClicked: console.log(buttonLabel.text'' " clicked" )
 }
}

我们在simplebutton内包含了一个MouseArea元素,其描述了能够接受鼠标事件的区域。这里,我们将鼠标的区域设置为整个Rectangle所在的区域。anchors.fill提供了一种用以访问属性anchors所具有的属性fill的方法。QML使用基于锚的布局,以便元素之间相互绑定,从而创建稳定的布局。

MouseArea元素预定义了多个信号处理方法,会在相应事件被触发时自动调用。例如,onClicked会在鼠标点击事件(默认为左键)发生时被调用。我们能够将需要进行的任务绑定到onClicked上。这里,我们调用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
 }

// 通过条件操作符来决定按钮的颜色
 color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}

Button.qml中的代码展示了一个功能完整的按钮。这里展示的代码中则省略了一部分,因为在前面已经介绍过,或是和当前的讨论没有直接关系。

我们可以通过“property 类型 名称”来自定义新的属性。这里,名为buttonColor的新属性就是一例。其被定义为color类型,并赋值为lightblue。buttonColor被进一步用在条件操作符中,以决定按钮的填充色。除使用冒号“:”外,属性的赋值也可以用“=”进行。自定义的属性使得外部元素也能够访问内部组件。在QML中,基本类型包括int、string、real等,以及一个variant类型。

通过在onEntered和onExited上添加相应代码,我们能够在鼠标移动进、出按钮时改变其颜色。

buttonClick()信号在Button.qml文件中被声明,以“取代”onClicked信号。所有的信号的处理器都被自动创建,其名称为on加上信号名。这里,onButtonClick是buttonClick的处理器。这里,onClicked处理器会调用onButtonClick处理器,以输出一段文本。onButtonClick则使得外部对象能够访问Button的鼠标区域。例如,某些元素可能定义了多个MouseArea,而使用buttonClick信号则能够更好的区分不同MouseArea发出的信号。

现在,我们已经拥有了足够的知识来使用QML创建元素,并处理基本的鼠标事件。我们也在Rectangle内创建了一个Text标签,自定义了其属性,并实现了对相关信号的处理。在元素内创建元素的概念会在这个文本编辑器中重复出现。

除非能够执行一定的功能,否则按钮是没用的。在下一节中,我们将创建一个包含了若干个按钮的菜单。

你的浏览器不支持图片显示

创建菜单页

到目前为止,我们已经讨论了如何在单个UML文件中创建元素,并设定要执行的动作。现在,我们将讨论如何引入和重用其他文件中定义的QML元素。

菜单能够显示多个菜单项,每个菜单项都能够执行一个特定的动作。在QML中,我们能用不同的方法创建菜单。首先,我们将创建创建一个包含多个按钮的菜单,点击这些按钮将触发不同的动作。菜单的源代码在FileMenu.qml文件中。

import Qt 4.7 // 导入Qt QML模块
import "folderName" //导入该目录的内容
import "script.js" as Script // 导入一个名为script.js的JavaScript文件,并将其命名为Script

上面用到的语法展示了如何使用“import”关键字。这在使用不在同一目录下的JavaScript或者QML文件来说是必须的。由于Button.qml文件和FileMenu.qml文件位于同一目录,我们并不需要导入Button.qml就能够直接使用它。我们能够通过声明“Button {}”直接创建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()
 }
}

我们定义了三个Button元素。他们都在Row元素内被定义,Row元素能够自动将其子元素放置在同一行中。Button的定义则位于同一目录下的Button.qml文件中,在前一节中已经讨论和使用过。我们可以在这些新创建的按钮中添加新的属性,从而覆盖在Button.qml中定义的那些属性。点击名为exitButton的按钮能够关闭当前关闭当前窗口。而在Button.qml中定义的onButtonClick也同样会被调用。

你的浏览器无法显示图片

Row元素声明在Rectangle的内部,即创建了一个矩形容器来存放按钮行。这个额外的矩形提供了一个间接的方式来管理菜单内的按钮行。

编辑菜单的声明和这里提到的方法类似,用到的按钮包含以下标签:Copy、Paste和Select All。

你的浏览器不支持图片显示

通过使用导入和自定义先前定义的组件,我们能够通过组合这些菜单页来创建菜单栏。

实现菜单栏

我们的文本编辑程序需要能够在菜单栏上显示菜单。菜单栏需要在用户选择需要显示的菜单后显示其中的菜单项。菜单的交换意味着菜单需要比简单在一行中显示更多的结构。QML使用模型和视图结构来处理数据。

使用数据模型和视图

QML能够使用不同的视图来展现数据模型。我们的菜单栏将以列表的形式展现菜单,并将菜单名在一列中显示。菜单的列表被定义在VisualItemModel中。VisualItemModel元素包含了若干已经拥有视图的项,例如Rectangle元素和导入的其他UI元素。其他如ListModel模型则需要一个delegate来显示其中的数据。

我们在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元素能够通过delegate来展现模型的数据。而delegate能够通过声明模型项来在Row元素或格子中展现数据。这里的menuListModel已经具有了可视化项,因此我们不再需要声明delegate。

ListView{
 id: menuListView

// 锚被定义来反应窗口的锚
 anchors.fill:parent
 anchors.bottom: parent.bottom
 width:parent.width
 height: parent.height

// 该模型包含了数据
 model: menuListModel

// 控制菜单交换时的移动
 snapMode: ListView.SnapOneItem
 orientation: ListView.Horizontal
 boundsBehavior: Flickable.StopAtBounds
 flickDeceleration: 5000
 highlightFollowsCurrentItem: true
 highlightMoveDuration:240
 highlightRangeMode: ListView.StrictlyEnforceRange
}

此外,ListView继承自Flickable,使得其能够相应鼠标的拖拽和其他姿势。上面这段代码的最后一部分设置了Flickable的属性,以便创建所需的效果。特别的,highlightMoveDuration属性改变了按键转换。更高的highlightMoveDuration值意味着更慢的菜单转换。

ListView通过索引维护模型项,并能够通过索引访问模型中的每个可视项。改变currentIndex能够有效的改变ListView中的高亮项。比如这里菜单项的头部就是如此。在同一行中有两个按钮,在点击后都能够当先显示的菜单。点击fileButton能够将当前菜单设置为文件菜单,而FileMenu的索引为0,因为其是在menuListModel中第一个定义的。类似的,点击editButton也能够将当前菜单改变为EditMenu。

labelList矩形的z值为1,意味着它显示在菜单栏之前。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
 }
 }
}

我们可以通过点击菜单名来访问相应的菜单项。菜单的交换也是直观和有动感的。

你的浏览器不支持图片显示

创建文本编辑器

声明TextArea

如果没有可编辑的文本域,我们的程序就不能被称为文本编辑器了。而QML就恰好提供了一个TextEdit元素来进行多行的文本编辑。TextEdit和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区域之内。如果文本过长,则能够提供滚动条的功能。函数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

// 屏幕被分为MenuBar和TextArea两部分。MenuBar占据1/3的空间
 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组件都能够很容易的被创建。

你的浏览器不支持图片显示

装饰文本编辑器

实现绘图接口

我们的文本编辑器看上去非常简单,所以我们需要装饰一下。通过使用QML,我们能够给文本编辑器添加转换和动画效果。考虑到菜单栏就占据了1/3的空间,所以有必要使他仅仅在我们需要时才出现。

我们需要添加一个绘图接口,在被点击时显示或收回菜单栏。在我们的实现中,我们有一个矩形来响应鼠标点击事件。和程序一样,这个绘图接口有“打开”和“关闭”两个状态。该绘图项是一个高度不大的矩形。其内部有一个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元素表示。一系列的状态能够被一一列出,并绑定到状态属性上。在我们的程序中,有两个被称为DRAWER_CLOSED和DRAWER_OPEN的状态。配置通过PropertyChanges元素被声明。在DRAWER_OPEN状态上,有四个项能够接受属性的变化。第一个目标,menuBar,会将其y属性改为0。类似的,textArea会在DRAWER_OPEN状态时降低到一个新的位置。而textArea、绘图器及其图标都会改变属性以实现当前状态。

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属性,定义在何时被触发。我们也可以用*使其在任何状态转换发生时被触发。

在转换过程中,我们可以将动画和属性改变相绑定。我们能够用NumberAnimation元素将menuBar从y:0到y:-partition的位置改变添加动画效果。我们声明目标的属性会以一个特定的曲线在一段时间内改变。这个曲线(easing curve)负责控制动画的速度,以及在状态转化间的行为。我们在这里选择的曲线是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元素。转换只在状态变化的过程中有效,并且Behavior能够为属性的变化设置动画。在这个文本编辑器中,该箭头使用了NumberAnimation来为其rotation属性设置动画。

In TextEditor.qml:

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

回想那些有状态和动画的组件,我们能够改进其外观。在Button.qml中,我们能够定义按钮被点击时的颜色和scale属性,还能通过ColorAnimation来为颜色改变添加动画,也能用NumberAnimation为数字属性的改变添加动画效果。下面的"on 属性名"语法在为单个属性添加动画时非常有用。

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

此外,我们能够通过添加例如gradients和透明度等颜色效果来改进QML组件的外观。声明一个Gradient元素能够覆盖元素的颜色属性。你可以在gradient中用GradientStop元素来声明颜色。gradient取值为0.0到1.0之间。

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

这里的gradient被用到菜单栏上来显示渐变的深度。第一个颜色被0.0位置定义,最后一个则在1.0处。

接下来做什么

我们已经完成了对这个简单的文本编辑器的用户界面的构建。现在,我们可以用普通的Qt和C+来实现程序的逻辑了。QML能够很好的被用作构建原型的工具,将程序的逻辑和UI设计相分离。

你的浏览器不支持显示图片

h2. 用Qt C扩展QML

现在,我们已经拥有了文本编辑器的界面,接下来就要用C来实现其功能。通过QML和C的混合使用,我们能够用Qt开发程序的逻辑。我们可以在C程序中通过Qt的Declarative类来创建QML上下文,并在Graphics Scene中显示QML元素。此外,我们也能将C代码编译成插件,以便在qmlviewer中被调用。这里,我们会将开发后的程序编译成插件。这样,我们就能直接(用qmlviewer)加载QML文件,而不必运行一个可执行文件了。

h3. 将C类暴露给QML

我们会用Qt和C实现文件的加载和保存。通过注册,我们可以在QML中使用C类和函数。然后将C代码编译称Qt的插件,以便QML文件能够加载。

在我们的程序中,我们需要创建以下项:

  1. Directory类已负责处理目录相关的操作
  2. File类是QObject,展现目录里的文件列表
  3. 插件类以便将C类注册到QML上下文
  4. Qt工程文件,以便编译插件
  5. 一个qmldir文件,告诉qmlviewer何处加载插件

h3. 创建Qt plugin

我们需要在Qt工程文件中添加如下代码以编译插件,包括必要的源文件、头文件,以及Qt模块等。所有的C代码和工程文件都存放在filedialog目录下。

在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

特别的,我们添加了Qt的declarative模块,将工程设置为插件类型,并把编译好的插件放到上层目录的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()函数将我们的File和Directory两个类注册到QML上下文中。注册用的函数qmlRegisterType需要相应的类名作为其模板,一个主版本号、副版本号,以及QML中展示的类名作为参数。

最后,我们需要用Q_EXPORT_PLUGIN2宏来导出插件。注意,我们在头文件的类声明中也添加了Q_OBJECT宏,需要运行qmake来自动创建相应的元对象代码。

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

我们能够使用C,通过Qt的元对象系统来创建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的元对象系统来注册其为文件操作而需要的属性,并以插件形式导出到QML中作为Directory元素。这里用Q_PROPERTY宏定义的每个属性都被导出称一个QML属性。

Q_PROPERTY宏声明了属性,使其能够通过Qt的元对象系统被读写。例如QString类型的filename属性,可以通过filename()函数进行读取,并通过setFilename()函数进行写操作。此外,还有一个名为filenameChanged()的信号与其绑定,会在该属性被改变时触发。读写的函数都在头文件中被定义为public函数。

Similarly, we have the other properties declared according to their uses. The filesCount property indicates the number of files in a directory. The filename property is set to the currently selected file's name and the loaded/saved file content is stored in fileContent property.

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

The files list property is a list of all the filtered files in a directory. The Directory class is implemented to filter out invalid text files; only files with a .txt extension are valid. Further, QLists can be used in QML files by declaring them as a QDeclarativeListProperty in C. The templated object needs to inherit from a QObject, therefore, the File class must also inherit from QObject. In the Directory class, the list of File objects is stored in a QList called m_fileList.

class File : public QObject{

Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

};

The properties can then be used in QML as part of the Directory element's properties. Note that we do not have to create an identifier id property in our C''+ code.

Directory{

id: directory
filesCount
filename
fileContent
files
files[0].name

}

Because QML uses Javascript's syntax and structure, we can iterate through the list of files and retrieve its properties. To retrieve the first file's name property, we can call files[0].name.

Regular C++ functions are also accessible from QML. The file loading and saving functions are implemented in C++ and declared using the Q_INVOKABLE macro. Alternatively, we can declare the functions as a slot and the functions will be accessible from QML.

In Directory.h:

Q_INVOKABLE void saveFile(); Q_INVOKABLE void loadFile();

The Directory class also has to notify other objects whenever the directory contents change. This feature is performed using a signal. As previously mentioned, QML signals have a corresponding handler with their names prepended with on. The signal is called directoryChanged and it is emitted whenever there is a directory refresh. The refresh simply reloads the directory contents and updates the list of valid files in the directory. QML items can then be notified by attaching an action to the onDirectoryChanged signal handler.

The list properties need to be explored further. This is because list properties use callbacks to access and modify the list contents. The list property is of type QDeclarativeListProperty<File>. Whenever the list is accessed, the accessor function needs to return a QDeclarativeListProperty<File>. The template type, File, needs to be a QObject derivative. Further, to create the QDeclarativeListProperty, the list's accessor and modifiers need to be passed to the consructor as function pointers. The list, a QList in our case, also needs to be a list of File pointers.

The constructor of QDeclarativeListProperty constructor and the Directory implementation:

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 );

The constructor passes pointers to functions that will append the list, count the list, retrieve the item using an index, and empty the list. Only the append function is mandatory. Note that the function pointers must match the definition of 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)

To simplify our file dialog, the Directory class filters out invalid text files, which are files that do not have a .txt extension. If a file name doesn't have the .txt extension, then it won't be seen in our file dialog. Also, the implementation makes sure that saved files have a .txt extension in the file name. Directory uses QTextStream to read the file and to output the file contents to a file.

With our Directory element, we can retrieve the files as a list, know how many text files is in the application directory, get the file's name and content as a string, and be notified whenever there are changes in the directory contents.

To build the plugin, run qmake on the cppPlugins.pro project file, then run make to build and transfer the plugin to the plugins directory.

=== 在QML中导入plugin ===

The qmlviewer tool imports files that are in the same directory as the application. We can also create a qmldir file containing the locations of QML files we wish to import. The qmldir file can also store locations of plugins and other resources.

In qmldir:

Button ./Button.qml

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

plugin FileDialog plugins

The plugin we just created is called FileDialog, as indicated by the TARGET field in the project file. The compiled plugin is in the plugins directory.

=== 将文件对话框集成到文件菜单中 ===

Our FileMenu needs to display the FileDialog element, containing a list of the text files in a directory thus allowing the user to select the file by clicking on the list. We also need to assign the save, load, and new buttons to their respective actions. The FileMenu contains an editable text input to allow the user to type a file name using the keyboard.

The Directory element is used in the FileMenu.qml file and it notifies the FileDialog element that the directory refreshed its contents. This notification is performed in the signal handler, onDirectoryChanged.

In FileMenu.qml:

Directory{

id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()

}

Keeping with the simplicity of our application, the file dialog will always be visible and will not display invalid text files, which do not have a .txt extension to their filenames.

In FileDialog.qml:

signal notifyRefresh() onNotifyRefresh: dirView.model = directory.files

The FileDialog element will display the contents of a directory by reading its list property called files. The files are used as the model of a GridView element, which displays data items in a grid according to a delegate. The delegate handles the appearance of the model and our file dialog will simply create a grid with text centered in the middle. Clicking on the file name will result in the appearance of a rectangle to highlight the file name. The FileDialog is notified whenever the notifyRefresh signal is emitted, reloading the files in the directory.

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

}

Our FileMenu can now connect to their respective actions. The saveButton will transfer the text from the TextEdit onto the directory's fileContent property, then copy its file name from the editable text input. Finally, the button calls the saveFile() function, saving the file. The sloadButton has a similar execution. Also, the New action will empty the contents of the TextEdit.

Further, the EditMenu buttons are connected to the TextEdit functions to copy, paste, and select all the text in the text editor.

你的浏览器无法显示图片

完成的文本编辑器

你的浏览器不支持图片显示