StupidBeauty
Read times:5899Posted at: - no title specified

Qt5.4文档翻译:Qt Quick编程入门,Getting Started Programming with Qt Quick

内容目录

QML是用来构建用户界面的

定义一个按钮(Button)和一个菜单(Menu)

基本组件——一个按钮(Button)

创建一个菜单页面

实现一个菜单栏

使用数据模型和视图

构建一个文本编辑器

声明一个TextArea

将这个文本编辑器的各个组件组合起来

美化这个文本编辑器

实现一个抽屉界面

下一步干嘛?

使用Qt C++来扩展QML

将C++类暴露给QML

构建一个Qt插件

将该类注册到QML中

在C++类中创建QML 属性

在QML中导入一个插件

将文件对话框整合到文件菜单中

最终的文本编辑器应用程序

运行这个文本编辑器

欢迎来到 QML 的世界,它是一门声明式用户界面语言。在这篇入门指南中,我们会使用来创建一个简单的文本编辑器程序。在读完这篇指南之后,妳应当能够使用QML 和Qt C++来开发应用程序了。

QML是用来构建用户界面的

我们将要开发的一款应用程序,是一个简单的文本编辑器, 它能够载入、保存及进行一些文本编辑操作。 这篇指南由两部分组成。第一部分 ,使用声明 式语言QML 来设计应用程序的布局和行为。第二部分 ,使用Qt C++ 来实现文件的载入和保存。利用 Qt 的元对象系统 ,可将C++函数暴露为可被 QML对象类型 使用的属性。利用QML 和Qt C++,可高效地将界面逻辑与应用程序逻辑分离开。

完整 的源代码位于 examples/quick/tutorials/gettingStartedQml 目录中。如果 妳想观摩一下最终程序的样子,则, 可以直接跳到 运行文本编辑 小节。

本教程中的C++部分,假设读者拥有基本的对于Qt的编译过程的知识。

教程章节:

  1. 1. 定义 一个按钮( Button )和一个菜单( Menu

  2. 2. 实现 一个菜单栏( Menu Bar

  3. 3. 构建 一个文本编辑器( Text Editor

  4. 4. 美化 该文本编辑器( Text Editor

  5. 5. 使用 Qt C++ 来扩展 QML

关于QML 的信息,例如语法和特性,可参考 QML参考手册

定义一个按钮(Button)和一个菜单(Menu

基本组件——一个按钮(Button

我们首先来设计一个按钮。从功能上说,一个按钮包含着一个鼠标点击区域和一个文本标签。当用户按下该按钮时,按钮会作出一些动作。

QML 中,基本的可视化条目就是 Rectangle 类型。 Rectangle 这个 QML对象类型 ,拥有一些 QML属性 ,可用来控制它的外观和位置。

import QtQuick 2.3

Rectangle {

id: simpleButton

color: "grey"

width: 150; height: 75

Text {

id: buttonLabel

anchors.centerIn: parent

text: "button label"

}

}

第一行语句, import QtQuick 2.3 ,使得 qmlscene 工具载入 我们日后要使用的那些 QML类型 。每个QML 文件 中都必须有这么一行。注意 ,import 语句中会包含所引入的Qt 模块的版本号。

此处 的这个简单的矩形有一个唯一的标识符, simpleButton 即为 id 属性。 Rectangle 对象 的属性是这样与值绑定的:列表中的每一项表示一个属性,首先是属性的名字,然后是一个冒号,然后是值。在这个示例代码中,颜色值 grey 被绑定到Rectangle 的 color 属性。类似 地,我们绑定了该Rectangle 的 width height

Text 类型 是一个不可编辑的文本区域。我们将这个对象命名为 buttonLabel 。为了设置该 Text 区域的字符串内容,我们将某个值绑定到 text 属性。该文本标签被包含于 Rectangle 中,为了让它居中对齐,我们将该Text 对象的 anchors 属性赋值为其亲代对象,而其亲代对象的名字是 simpleButton 锚点也可以绑定到其它条目的锚点,这样,布局就会简单狠多。

我们将这个代码保存为 SimpleButton.qml ,然后运行 qmlscene ,以这个文件作为参数,就会显示一个灰色的矩形框,其中有一个文本标签。

要实现按钮的点击功能的话,需要使用 QML 的事件处理功能。 QML 的事件处理功能狠类似于 Qt 的信号和信号槽 机制 。会发射出信号,然后就会调用到与之相连接的信号槽。

Rectangle {

id: simpleButton

...

MouseArea {

id: buttonMouseArea

// Anchor all sides of the mouse area to the rectangle's anchors

anchors.fill: parent

// onClicked handles valid mouse button clicks

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

}

}

我们在simpleButton 中包含了一个 MouseArea 对象。 MouseArea 对象 ,表示的是一些交互区域,在该区域中,鼠标的移动会被探测到。对于我们示例中的按钮,我们将整个 MouseArea 都与它的亲代对象对齐,也就是 simpleButton anchors.fill 这种语法,其意思是,访问一个名为 anchors 的属性组中的一个名为 fill 的属性。 QML使用 的是 基于锚点的布局 ,这样,各个条目可以以其它条目作为布局参考点,于是能够创建出非常健壮的布局。

MouseArea 拥有 狠多信号处理器,当鼠标在指定的 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 type name 语法 来声明自定义的属性。在此处的代码中,声明了一个类型为 color 的属性 buttonColor ,并且将它的值绑定为 "lightblue" 。日后,会在一个条件操作符中用上 buttonColor ,来确定该按钮的填充颜色。注意:属性的赋值,可以使用 = 等号来进行,也可以使用 : 冒号表示的值绑定来进行。自定义的属性,使得Rectangle 作用域之外的那些代码能够访问到它的内部元素。有一些基本的 QML类型 ,例如 int string real ,还有一个叫做 variant 的类型。

onEntered onExited 两个信号槽处理函数绑定到颜色上之后,每当鼠标悬停到按钮上方的时候,按钮的边框颜色会变成黄色,而当鼠标离开之后,其边框颜色会恢复。

Button.qml 中声明了一个 buttonClick() 信号,具体做法就是,在 signal 关键字之后跟上信号名字。对于所有的信号,都会自动创建对应的处理器函数,其名字以 on 开头。因此 onButtonClick 就是 buttonClick 的处理器函数。然后, onButtonClick 就被赋值给某个动作。在我们的按钮示例中, onClicked 这个鼠标按钮处理器函数会简单地调用 onButtonClick ,导致输出一个文本字符串。 onButtonClick 使得外部对象 可以轻易地访问到 Button 的鼠标区域。例如,界面元素里可能会声明多个 MouseArea ,而 buttonClick 信号 可以更好地区分出多个不同的 MouseArea 信号处理器函数。

现在 ,我们知道了,怎么在QML 中处理基本的鼠标事件。 我们在一个 Rectangle 中创建了一个 Text 标签,自定义了它的属性,并且实现了对于鼠标动作做出响应的行为。在文本编辑器应用程序中,我们会频繁地使用这种在QML 对象中创建其它QML 对象的技术。

这个按钮,只有在用作一个组件来做出某个动作时,才会有用。在下一小节,我们会创建一个菜单,其中包含着多个这样的按钮。

创建一个菜单页面

到目前为止,我们学习了,如何在单个的QML 文件中创建对象,以及给对象赋予行为。在本小节中,我们会学习,如何导入QML 类型,以及,如何复用之前创建的组件来构建其它组件。

菜单 ,会显示某个列表中的内容,其中,每个列表项都能够做出某种动作。在 QML 中,可采用多种手段来创建菜单。首先,我们会创建一个包含着多个按钮的菜单,其中每个按钮会做出不同的动作。菜单的代码是位于 FileMenu.qml 中。

import QtQuick 2.3             // 导入 Qt QML模块

import "folderName"            // 导入某个文件夹中的内容

import "script.js" as Script   // 导入某个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()

}

}

FileMenu.qml 中,我们声明了三个 Button 对象。它们被声明在一个 Row 类型当中,该类型是一个定位器,会将它的子代元素排列到一个竖向的行中去。 Button 本身 的声明是位于 Button.qml 中的, 它与我们上一小节中使用的是一样的。可在新创建的按钮中声明新的属性绑定,它们会覆盖掉在 Button.qml 中设置的属性。名为 exitButton 的按钮,当它被点击时,会关闭窗口并且退出程序。注意, Button.qml 中的信号处理器函数 onButtonClick 会被调用,而 exitButton 中的 onButtonClick 处理器函数也会被调用。

Row 声明位于 一个 Rectangle 中,这样,就为这一行按钮创建了一个矩形容器。额外的这个矩形,提供了一种非直接地将这一行按钮组织到某个菜单中去的手段。

编辑菜单 的声明是类似的。该菜单中有几个按钮,其文字内容分别是: Copy Paste Select All

现在,我们学会了如何导入及对之前创建的组件进行自定义,那么,我们可以将这些菜单页面组合起来创建一个菜单栏了,菜单栏中包含着用来选择菜单的按钮,并且,学习一下我们如何使用QML 来组织数据。

实现一个菜单栏

在我们的文本编辑器程序中,需要显示一个菜单栏。菜单栏能够在不同的菜单之间切换,并且,用户可以选择要显示哪个菜单。菜单的切换,就意味着,要对菜单进行更有效的组织,而不是仅仅将它们显示在一行中。QML使用模型和视图来组织数据并且显示结构化的数据。

使用数据模型和视图

QML 中有不同的 数据视图 ,用来显示 数据模型 。我们的菜单栏,会将各个菜单显示在一个列表中,并且会有一个菜单头,它显示一行菜单名字。菜单列表会声明在一个 ObjectModel 中。 ObjectModel 类型,包含的是一些本身已经可被显示的条目,例如 Rectangle 对象。其它 的模型类型,例如 ListModel 类型,需要有一个代理来显示它们的数据。

我们在 menuListModel 中声明两个可视化条目,分别是 FileMenu EditMenu 。我们对这两个菜单进行自定义,然后将它们显示在一个 ListView 中。对应的QML 定义位于 MenuBar.qml 文件 中,而在 EditMenu.qml 中有一个简单的编辑菜单的定义。

ObjectModel {

id: menuListModel

FileMenu {

width: menuListView.width

height: menuBar.height

color: fileColor

}

EditMenu {

color: editColor

width: menuListView.width

height: menuBar.height

}

}

ListView 类型 会在一个代理的帮助下显示一个模型。代理 可将模型条目显示在一个 Row 对象中或者一个网格中。我们的 menuListModel 已经拥有可见的条目了,因此,我们不需要声明一个代理。

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 通过 一个索引( index )来维护模型中的条目,该模型中的每个可视化条目都可以通过 index 访问到,其在索引中的顺序与声明的顺序相同。改变 currentIndex ,就会改变 ListView 中的高亮条目。我们的菜单栏的头部就展示了这种效果。 这一行中有两个按钮,当它们被点击时都会改变当前菜单。 fileButton 被点击时,会将当前菜单切换成文件菜单, index 会被设置成 0 ,因为 FileMenu menuListModel 中是第一个声明的。类似 地, editButton 被点击时,会将当前菜单切换成 EditMenu

labelList 矩形 z 值为 1 ,使得它显示在菜单栏的前面。 z 值较高的元素,会显示在 z 值较低的元素的前面。默认的值是。 es. The default  z  value is  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)

}

这个编辑器,被设置了字体的颜色( color )属性,并且被设置了 wrapMode 属性,以支持自动换行。此 TextEdit 区域是位于一个可滑动的元素之中,当文本光标离开可视区域的范围时,会自动将文本内容滚动。 ensureVisible() 函数会检查光标矩形是否位于可视区域边界之外,并且相应地移动文本区域。 QML使用Javascript 的语法来编写它的脚本,并且,前面已经提到过, Javascript文件 可被导入以在QML 文件中使用。

function ensureVisible(r) {

if (contentX >= r.x)

contentX = r.x;

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

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

if (contentY >= r.y)

contentY = r.y;

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

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

}

将这个文本编辑器的各个组件组合起来

现在,我们可以使用QML 来创建文本编辑器的布局了。这个文本编辑器有两个组件:我们之前创建的 菜单栏;以及现在所说的文本区域。QML允许我们复用组件,这样会使得我们的代码更简单,具体做法就是,导入组件并且在必要的时候进行自定义。我们的文本编辑器,将窗口分割成两个部分:屏幕中1/3的部分用来显示菜单栏;另外2/3的部分用来显示文本区域。菜单栏会显示在任何其它对象的前面。

Rectangle {

id: screen

width: 1000

height: 1000

// 屏幕 被划分成 MenuBar TextArea

// 屏幕 上1/3的区域被分配给 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"

width: parent.width

height: partition * 2

}

}

通过导入 可复用的组件,使得我们的 TextEditor 代码看起来简单得多。然后,我们可以对主程序进行自定义,并且不需要担心那些已经拥有确定行为的属性。通过这种手段,可以轻松地创建程序布局和用户界面组件。

美化这个文本编辑器

实现一个抽屉界面

我们的文本编辑器看起来狠简单,所以需要做一下美化。使用QML,可以声明一些过渡效果,让我们的文本编辑器拥有动画效果。我们的菜单栏占用了屏幕上1/3的区域,因此,有必要让它只在需要用到的时候出现。

我们可以添加一个抽屉界面,它会在被点击时收起或展开菜单栏。具体地,在我们的实现当中,我们创建了一个小小的矩形,它会被鼠标点击事件做出响应。抽屉 drawer ,和该程序,有两个状态:“抽屉已打开”状态和“抽屉已关闭”状态。 drawer 元素 是一个狭长矮小的矩形。其中嵌套了一个 Image 对象,它包含了一个箭头图标,并且在抽屉中居中。每当用户点击它的鼠标区域时,抽屉就会通过 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 。元素 的配置选项是使用 PropertyChanges 对象来声明的。在 DRAWER_OPEN 状态中,有4个元素会接收到属性变更。第一个目标, menuBar 会将 y 属性改变成 0 。类似 地,当状态为 DRAWER_OPEN 的时候, textArea 会向下移动到一个新的位置。 textArea 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.OutExpo ,它会在动画的结尾降低运动速度。欲知更多信息,则参考 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 呢,可为通用的属性变更设置动画过程。 在我们这里的文本编辑器中,箭头对象会在 rotation 属性发生变更时使用一个 NumberAnimation 来以动画形式展现该变更。

TextEditor.qml 中:

Behavior {

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

}

现在 ,学习了状态和动画的知识,我们再回过头去看那些组件的代码,我们可以改善那些组件的外观。在 Button.qml 中,我们可以在按钮被点击时添加 color scale 属性的变更。颜色类型是使用 ColorAnimation 来处理动画的,而数值是使用 NumberAnimation 来处理动画的。 所展示的 on propertyName 语法,在针对单个属性做动画处理时,狠有用。

Button.qml 中:

...

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

Behavior on color { ColorAnimation{ duration: 55 } }

scale: buttonMouseArea.pressed ? 1.1 : 1.0

Behavior on scale { NumberAnimation{ duration: 55 } }

另外 ,我们可以向QML 组件添加颜色效果(例如渐变)和透明效果来改善它们的外观。声明 Gradient 对象 的话,会覆盖掉 color 属性。妳可以使用 GradientStop 类型来在渐变中声明一个颜色。渐变是使用一个范围值来定位的,其取值范围是 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" }

}

菜单 栏利用了渐变来显示一个以渐变来模拟的深度效果。第一个颜色位于 0.0 ,最后一个颜色位于 1.0

下一步干嘛?

我们已经实现了一个狠简单的文本编辑器的界面。然后呢,用户界面已经完工了,那么,我们可以使用常规的Qt 和C++来实现程序的逻辑了。QML用来做原型工具是狠好的,可以将程序逻辑和用户界面的设计分离开来。

使用Qt C++来扩展QML

既然 我们已经设计好了文本编辑器的布局,那么,我们可以开始使用C++来实现该文本编辑器的功能了。将QML 与C++配套使用的话,使得我们可以使用Qt 来实现程序逻辑。 我们可以在C++程序中使用 Qt的那些Quick类 创建一个QML 上下文,并且使用 QQuickView 来显示那些QML 类型的对象。或者,我们可以将 C++代码导出 到一个扩展插件中,然后,让它作为一个新的 已标识的模块 ,以让QML 可以访问到。在使用 qmlscene 启动QML 文件的时候,只需要确保该模块位于QML 引擎用来寻找模块的其中某一个 导入路径 中就可以了。对于我们的程序,应当使用第二种实现方式。这样的话,我们就可以直接使用 qmlscene 载入该 QML 文件,而不用运行一个可执行程序。

将C++类暴露给QML

我们会使用Qt 和C++来实现文件载入和保存功能。在QML 中注册C++类和函数,即可利用它们。它们还需要被编译为一个Qt插件,并且暴露为一个QML 模块。

对于我们的应用程序,我们需要创建以下条目:

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

  2. 2. File 类,它本身是一个 QObject ,用来模拟某个目录中的文件列表

  3. 3.一个插件类,用来将以上那些类注册到QML 上下文中

  4. 4. 一个Qt项目文件,用来编译该插件

  5. 5. 一个 用于定义模块的qmldir文件 ,它定义了那些要通过QML 模块暴露出来的标识符(用于导入的 URI)及内容(在这个例子中,就是我们的插件)

注意: 自从Qt 5.1 开始 Qt Quick Dialogs 模块提供 了一个文件对话框组件,可用来从本地文件系统中选择文件。在本教程中,出于演示的目的,我们自己写了个文件对话框。

构建一个Qt插件

要想构造一个插件,我们需要在Qt 项目文件中设置以下属性。首先,要把必要的源代码、头文件和 Qt模块添加 到我们的项目文件中。所有的 C++代码 和项目文件都位于 filedialog 目录中。

filedialog.pro 中:

TEMPLATE = lib

CONFIG += qt plugin

QT += qml

DESTDIR +=  ../imports/FileDialog

OBJECTS_DIR = tmp

MOC_DIR = tmp

TARGET = filedialogplugin

HEADERS += \

directory.h \

file.h \

dialogPlugin.h

SOURCES += \

directory.cpp \

file.cpp \

dialogPlugin.cpp

特别地,我们将该项目与 qml 模块链接,并且使用 lib 模板来将它配置为一个插件( plugin )。我们应当将编译出来的插件放置到亲代目录的 imports/FileDialog 目录中。

将该类注册到QML中

dialogPlugin.h 中:

#include <QtQml/QQmlExtensionPlugin>

class DialogPlugin : public QQmlExtensionPlugin

{

Q_OBJECT

Q_PLUGIN_METADATA(IID "org.qt-project.QmlExtensionPlugin.FileDialog")

public:

// registerTypes 是继承自 QQmlExtensionPlugin

void registerTypes(const char *uri);

};

我们需要使用 Q_PLUGIN_METADATA 宏来导出该插件。注意,在 dialogPlugin.h 文件中,在类的顶部使用了 Q_OBJECT 宏。另外,我们需要针对该项目文件运行 qmake ,以生成必要的元对象代码。

我们的插件类 DialogPlugin ,是 QQmlExtensionPlugin 的一个子类。我们需要实现继承的函数 registerTypes()

DialogPlugin.cpp 中:

#include "dialogPlugin.h"

#include "directory.h"

#include "file.h"

#include <QtQml>

void DialogPlugin::registerTypes(const char *uri)

{

// 将Directory 类注册到QML 中,类型为"Directory",版本号为 1.0

// @uri FileDialog

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

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

}

registerTypes() 函数 会将我们的File 和Directory 类注册到QML 中。这个函数需要以下参数:以类名作为模板参数;主版本号;次版本号;以及我们的类在QML中的名字。 // @uri <module identifier> 这个注释,使得 Qt Creator 在编辑那些导入了此模块的QML 文件时,知道所注册的类型的存在性。

在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 ( QQmlListProperty <File> files READ files CONSTANT)

...

Directory 类使用 Qt 的元对象系统来注册那些在完成文件处理功能时所需要的属性。 Directory 类,被导出为一个插件,并且,可在QML 中以 Directory 类型的形式使用。每一个使用 Q_PROPERTY ()宏列出的属性,都是一个QML 属性。

Q_PROPERTY 会向Qt的元对象系统声明一个属性,以及对应的读取和写入函数。例如, filename 属性 ,类型为 QString ,可使用 filename() 函数来读取,使用 setFilename() 函数来写入。另外, 还有一个与filename 属性关联的信号,名为 filenameChanged() ,当该属性发生变化时会被发射。读取和写入函数是在头文件中声明为 public 访问类型的。

类似 地,我们声明了其它属性。 filesCount 属性表示 的是某个目录中的文件个数。 filename属性 会反映出当前选中的文件的名字,而载入/保存的文件内容,是储存在 fileContent 属性中的。

Q_PROPERTY ( QQmlListProperty <File> files READ files CONSTANT)

files 这个列出属性,是某个目录中被过滤出来的所有文件的列表。 Directory 类的实现中,会过滤掉无效的文本文件;只有那些名字以 .txt 结尾的文件是有效的。另外, QList 可在QML 文件中使用,只需在C++中将它们声明为 QQmlListProperty 即可。模板 中的类需要继承自 QObject ,因此, File 类必须继承 QObject 。在 Directory 类中,由 File 对象组成的那个列表是储存在一个名为 m_fileList QList 中。

class File : public QObject {

Q_OBJECT

Q_PROPERTY( QString name READ name WRITE setName NOTIFY nameChanged)

...

};

然后 ,这些属性就可以在QML 中作为 Directory 对象属性的一部分来使用。注意,我们不需要在C++代码中创建一个标识 id 属性。

Directory {

id: directory

filesCount

filename

fileContent

files

files[0].name

}

由于QML使用Javascript 的语法和结构,因此,我们可以在文件列表中遍历并且获取它们的属性。要获取第一个文件的名字属性,我们可以调用 files[0].name

常规 C++函数 也可在QML 中访问到。文件的载入和保存函数,是使用 C++实现 的,并且是使用 Q_INVOKABLE 宏来声明的。或者,我们也可以将这些函数声明为槽( slot ),那样的话也可以在QML 中访问到它们。

directory.h 中:

Q_INVOKABLE void saveFile();

Q_INVOKABLE void loadFile();

Directory 类还需要在目录的内容发生变化时告知其它对象。这个特性是利用信号( signal )来实现的。之前已经说过,对于 QML信号 ,会有一个对应的处理器函数,其名字为信号名字前面加上 on 。该信号名为 directoryChanged ,每当发生目录刷新时,就会被发射。刷新动作中,只是简单地重新载入目录的内容,并且更新目录中有效文件的列表。然后,那些 QML元素 ,只需要向 onDirectoryChanged 这个信号处理器附加一个动作,即可获知目录内容的变化。

列表 (list) 属性需要 多讲一讲。因为,列表属性是使用回调函数来访问及修改列表中的内容的。列表属性的类型是 QQmlListProperty<File> 。每当列表被访问时,访问函数就需要返回一个 QQmlListProperty<File> 。其中的模板类, File ,需要继承自 QObject 。另外,为了创建该 QQmlListProperty ,访问 器和修改器需要以函数指针的形式传入构造函数。此处 的列表,在我们的示例中,是一个 QList ,也需要声明成由 File 指针组成的列表。

QQmlListProperty 的构造函数是如下声明的:

QQmlListProperty ( QObject *object, void *data, AppendFunction append,

CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0);

它需要一系列的函数指针,这些函数分别做以下事情:向列表中追加元素;返回列表中元素个数;使用下标来获取指定的元素;清空列表。只有 append 函数 是必需的。注意,各个函数指针 都必须匹配 AppendFunction CountFunction AtFunction ClearFunction 的定义。

Directory 类像这样构造一个 QQmlListProperty 实例:

QQmlListProperty <File>(this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr);

其中的参数分别是指向以下函数的指针:

void appendFiles( QQmlListProperty <File> *property, File *file);

File* fileAt( QQmlListProperty <File> *property, int index);

int filesSize( QQmlListProperty <File> *property);

void clearFilesPtr( QQmlListProperty <File> *property);

为了简化我们的文件对话框, Directory 类将无效的文本文件过滤掉了,无效 的文件即是那些不带 .txt 后缀名的文件。如果某个文件的文件名不带 .txt 后缀,则,在我们的文件对话框中就看不到它。并且 ,代码里会确保,保存出去的文件,其文件名必定带有一个 .txt 后缀。 Directory 使用 QTextStream 来读取及输出文件内容。

利用 Directory 对象 ,我们可以做到:获取 到文件列表,以得知该应用程序的目录中有多少个文本文件;获取选中的文件的名字,以及其内容;当目录的内容发生变化时得到通知。

要构建该插件,则针对 filedialog.pro 项目文件运行 qmake 命令,然后运行 make 命令 来构建它并且将该插件传送到 plugins 目录中。

在QML中导入一个插件

qmlscene 工具能够导入那些 与应用程序处于同一个目录的文件。我们还可以创建一个 qmldir 文件,其中包含着我们想要导入的内容所在的位置。在本示例中,只有该插件需要导入,但是,我们也可以在 qmldir 中定义其它资源(QML类型、JavaScript文件)。

qmldir 文件 的内容:

module FileDialog

plugin filedialogplugin

我们刚刚创建的模块名为 FileDialog ,它提供了一个名为 filedialogplugin 的插件,该名字即与项目文件中的 TARGET 字段相匹配。因为 我们没有指定该插件的路径,所以, QML预期 会在与 qmldir 文件相同的目录下找到该插件。

现在,可以在QML 中导入由我们的插件注册的那些QML 类型:

import FileDialog 1.0

Directory {

id: directory

}

...

将文件对话框整合到文件菜单中

我们的 FileMenu 需要显示 FileDialog 对象,其中包含 着某个目录下的文本文件列表,使得用户可以通过点击列表来选择特定文件。 我们还需要将保存、载入和新建按钮与对应的动作关联起来。 FileMenu 中还包含着一个可编辑的文本输入区域,允许用户使用键盘 来输入一个文件名。

Directory 对象 是在 FileMenu.qml 文件中使用的,它向 FileDialog 对象告知,目录对象已经刷新了它的内容。 这个通知是利用信号处理器,即 onDirectoryChanged 来实现的。

FileMenu.qml 中:

Directory {

id: directory

filename: textInput.text

onDirectoryChanged: fileDialog.notifyRefresh()

}

为了让我们的程序尽可能地简单,我们会让文件对话框保持一直可见,并且不会显示出无效的文本文件,也就是说其文件名不带 .txt 后缀名的文件。

FileDialog.qml 中:

signal notifyRefresh()

onNotifyRefresh: dirView.model = directory.files

FileDialog 对象 会读取某个目录的名为 files 的列表属性,以显示其内容。那些文件 会作为某个 GridView 对象的模型,该对象会根据某个代理的数据来以一个网格显示数据条目。 该代理会管理该模型的外观,而我们的文件对话框会简单的创建一个网格,其中,文字会显示在每个条目的中间。 点击文件名,就会导致出现 一个矩形框,将该文件名高亮显示。每当 notifyRefresh 信号 被发射时, FileDialog 就会得到通知,于是重新载入目录中的文件列表。

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 按钮对应 的动作,会清空 TextEdit 的内容。

还有, EditMenu 中的那些按钮,会连接到 TextEdit 的对应功能:复制、粘贴及选中文本编辑器中的所有文字。

最终的文本编辑器应用程序

现在,这个应用程序已经成为一个简单的文本编辑器了,它能够接受文字内容输入,并且将内容保存到文件中。它还可以载入一个文件,并且进行文字操作。

运行这个文本编辑器

在运行这个文本编辑器之前,需要构建出那个文件对话框的 C++插件 要构建它,则,进入 filedialog 目录,然后,运行 qmake ,最后使用 make 来编译它。

使用 qmlscene 命令 来运行该文本编辑器,并且传入imports 目录作为其中一个参数,这样, QML引擎 就知道该到哪里去寻找那个导入了我们的文件对话框插件的模块:

qmlscene -I ./imports texteditor.qml

完整 的源代码,位于 examples/quick/tutorials/gettingStartedQml 目录中。

http://img4.duitang.com/uploads/item/201407/18/20140718230344_3xyye.jpeg

把良心拿出来晒一晒。

Your opinions

Your name:Email:Website url:Opinion content:
- no title specified

HxLauncher: Launch Android applications by voice commands