在《Qt Quick 事件处理之信号与槽》一文中介绍自定义信号时,举了一个简单的例子,定义了一个颜色选择组件,当用户在组建内点击鼠标时,该组件会发出一个携带颜色值的信号,当时我使用 Connections 对象连接到组件的 colorPicked 信号,改变文本的颜色。 当时用到的 Component 、 Loader 两个特性,一直没来得及介绍,可能很多人都还在雾里看花呢。这次呢,我们就来仔仔细细地把他们讲清楚。
版权所有 foruok ,转载请注明出处:http://blog.csdn.net/foruok 。
Components(组件)
Component 是由 Qt 框架或开发者封装好的、只暴露了必要接口的 QML 类型,可以重复利用。一个 QML 组件就像一个黑盒子,它通过属性、信号、函数和外部世界交互。
一个 Component 即可以定义在独立的 qml 文件中,也可以嵌入到其它的 qml 文档中来定义。通常我们可以根据这个原则来选择将一个 Component 定义在哪里:如果一个 Component 比较小且只在某个 qml 文档中使用或者一个 Component 从逻辑上看从属于某个 qml 文档,那就可以采用嵌入的方式来定义该 Component 。你也可以与 C++ 的嵌套类对比来理解。
嵌入式定义组件
《Qt Quick 事件处理之信号与槽》一文中使用到 Component 的示例 QML 代码如下:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 320;
height: 240;
color: "#C0C0C0";
Text {
id: coloredText;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.top: parent.top;
anchors.topMargin: 4;
text: "Hello World!";
font.pixelSize: 32;
}
Component {
id: colorComponent;
Rectangle {
id: colorPicker;
width: 50;
height: 30;
signal colorPicked(color clr);
MouseArea {
anchors.fill: parent
onPressed: colorPicker.colorPicked(colorPicker.color);
}
}
}
Loader{
id: redLoader;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
sourceComponent: colorComponent;
onLoaded:{
item.color = "red";
}
}
Loader{
id: blueLoader;
anchors.left: redLoader.right;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
sourceComponent: colorComponent;
onLoaded:{
item.color = "blue";
}
}
Connections {
target: redLoader.item;
onColorPicked:{
coloredText.color = clr;
}
}
Connections {
target: blueLoader.item;
onColorPicked:{
coloredText.color = clr;
}
}
}
其中,颜色选择组件的定义代码如下:
Component {
id: colorComponent;
Rectangle {
id: colorPicker;
width: 50;
height: 30;
signal colorPicked(color clr);
MouseArea {
anchors.fill: parent
onPressed: colorPicker.colorPicked(colorPicker.color);
}
}
}
如你所见,要在一个 QML 文档中嵌入 Component 的定义,需要使用 Component 对象。
定义一个 Component 与定义一个 QML 文档类似, Component 只能包含一个顶层 item ,而且在这个 item 之外不能定义任何数据,除了 id 。比如上面的代码中,顶层 item 是 Rectangle 对象,在 Rectangle 之外我定义了 id 属性,其值为 colorComponent 。而顶层 item 之内,则可以包含更多的子元素来协同工作,最终形成一个具有特定功能的组件。
Component 通常用来给一个 view 提供图形化组件,比如 ListView::delegate 属性就需要一个 Component 来指定如何显示列表的每一个项,又比如 ButtonStyle::background 属性也需要一个 Component 来指定如何绘制 Button 的背景。
Component 不是 Item 的派生类,而是从 QQmlComponent 继承而来,虽然它通过自己的顶层 item 为其它的 view 提供可视化组件,但它本身是不可见元素。你可以这么理解:你定义的组件是一个新的类型,它必须被实例化以后才可能显示。而要实例化一个嵌入在 qml 文档中定义的组件,则可以通过 Loader 。后面我们详细讲述 Loader ,这里先按下不表,我们要来看如何在一个文件中定义组件了。
在单独文件中定义组件
很多时候我们把一个 Component 单独定义在一个 qml 文档中,比如 Qt Quick 提供的 BusyIndicator 控件,其实就是在 BusyIndicator.qml 中定义的一个组件。下面是 BusyIndicator.qml 文件的内容:
Control {
id: indicator
property bool running: true
Accessible.role: Accessible.Indicator
Accessible.name: "busy"
style: Qt.createComponent(Settings.style + "/BusyIndicatorStyle.qml", indicator)
}
我在《 Qt Quick 简单教程》一文中的显示网络图片实例中,使用了 BusyIndicator 来提示用户图片正在加载中需要等候,你可以跳转到那篇文章学习 BusyIndicator 的用法。
BusyIndicator 组件的代码非常简单,只是给 Control 元素(Qt Quick 定义的私有元素,用作其它控件的基类,如 ComboBox 、 BusyIndicator 等)增加了一个属性、设置了几个属性的值,仅此而已。
不知你是否注意到了, BusyIndicator.qml 文件中的顶层 item 是 Control ,而我们使用时却是以 BusyIndicator 为组件名(类名)。这是我们定义 Component 时要遵守的一个约定:组件名字必须和 qml 文件名一致。好嘛,和 Java 一样啦,类名就是文件名。还有一点,组件名字的第一个字母必须是大写。对于在文件中定义一个组件,就这么简单了,再没有其它的特殊要求。 Qt Quick 提供的多数基本元素和特性,你都可以在定义组件时使用。
一旦你在文件中定义了一个组件,就可以像使用标准 Qt Quick 元素一样使用你的组件。比如我们给颜色选择组件起个名字叫 ColorPicker ,对应的 qml 文件为 ColorPicker.qml ,那么你就可以在其它 QML 文档中使用 ColorPicker {...} 来定义 ColorPicker 的实例。
好啦,现在让我们来看看在单独文件中定义的 ColorPicker 组件:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
id: colorPicker;
width: 50;
height: 30;
signal colorPicked(color clr);
function configureBorder(){
colorPicker.border.width = colorPicker.focus ? 2 : 0;
colorPicker.border.color = colorPicker.focus ? "#90D750" : "#808080";
}
MouseArea {
anchors.fill: parent
onClicked: {
colorPicker.colorPicked(colorPicker.color);
mouse.accepted = true;
colorPicker.focus = true;
}
}
Keys.onReturnPressed: {
colorPicker.colorPicked(colorPicker.color);
event.accepted = true;
}
Keys.onSpacePressed: {
colorPicker.colorPicked(colorPicker.color);
event.accepted = true;
}
onFocusChanged: {
configureBorder();
}
Component.onCompleted: {
configureBorder();
}
}
请注意上面的代码,它和嵌入式定义有明显不同: Component 对象不见咧!对,就是酱紫滴:在单独文件内定义组件,不需要 Component 对象,只有在其它 QML 文档中嵌入式定义组件时才需要 Component 对象。另外,为了能够让多个 ColorPicker 组件可以正常的显示焦点框,我还使用了 onClicked 信号处理器,新增了 onFocusChanged 信号处理器,在它们的实现中调用 configureBorder() 函数来重新设置边框的宽度和颜色,新增 onReturnPressed 和 onSpacePressed 以便响应