博客24## 一、Components(组件)
Component 是由 Qt 框架或开发者封装好的、只暴露了必要接口的 QML 类型,可以重复利用。一个 QML 组件就像一个黑盒子,它通过属性、信号、函数和外部世界交互。
一个 Component 即可以定义在独立的 qml 文件中,也可以嵌入到其它的 qml 文档中来定义。通常我们可以根据这个原则来选择将一个 Component 定义在哪里:如果一个 Component 比较小且只在某个 qml 文档中使用或者一个 Component 从逻辑上看从属于某个 qml 文档,那就可以采用嵌入的方式来定义该 Component 。你也可以与 C++ 的嵌套类对比来理解。
嵌入式定义组件
示例 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;
}
}
}
如你所见,要在一个 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 ,这里先按下不表。
二、使用 Loader
2.1 Loader 的详细介绍
Loader 用来动态加载 QML 组件。Loader 可以使用其 source 属性加载一个 qml 文档,也可以通过其 sourceComponent 属性加载一个 Component 对象。当你需要延迟一些对象直到真正需要才创建它们时, Loader 非常有用。
当 Loader 的 source 或 sourceComponent 属性发生变化时,它之前加载的 Component 会自动销毁,新对象会被加载。将 source 设置为一个空字符串或将 sourceComponent 设置为 undefined ,将会销毁当前加载的对象,相关的资源也会被释放,而 Loader 对象则变成一个空对象。
Loader 的 item 属性指向它加载的组件的顶层 item ,比如 Loader 加载了我们的颜色选择组件,其 item 属性就指向颜色选择组件的 Rectangle 对象。对于 Loader 加载的 item ,它暴露出来的接口,如属性、信号等,都可以通过 Loader 的 item 属性来访问。所以我们才可以这么使用:
Loader{
id: redLoader;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
sourceComponent: colorComponent;
onLoaded:{
item.color = "red";
}
}
上面的代码在 Loader 对象使用 sourceComponent 属性来加载 id 为 colorComponent 的组件对象,然后在 onLoaded 信号处理器中使用 item 属性来设置颜色选择组件的颜色。对于信号的访问,我们则可以使用 Connections 对象,如下面的 qml 代码所示:
Connections {
target: redLoader.item;
onColorPicked:{
coloredText.color = clr;
}
}
我们创建的 Connections 对象,其 target 指向 redLoader.item ,即指向颜色选择组件的顶层 item — Rectangle ,所以可以直接响应它的 colorPicked 信号。
虽然 Loader 本身是 Item 的派生类,但没有加载 Component 的 Loader 对象是不可见的,没什么实际的意义。而一旦你加载了一个 Component , Loader 的大小、位置等属性却可以影响它所加载的 Component 。
如果你没有显式指定 Loader 的大小,那么 Loader 会将自己的尺寸调整为与它加载的可见 item 的尺寸一致;如果 Loader 的大小通过 width 、 height 或 锚布局显式设置了,那么它加载的可见 item 的尺寸会被调整以便适应 Loader 的大小。不管是哪种情况, Loader 和它所加载的 item 具有相同的尺寸,这确保你使用锚来布局 Loader 就等同于布局它加载的 item 。
2.2 从文件加载组件
之前介绍 Loader 时,我们以嵌入式定义的 Component 为例子说明 Loader 的各种特性和用法,现在我们来看如何从文件加载组件。
对于定义在一个独立文件中的 Component ,同样可以使用 Loader 来加载,只要指定 Loader 的 source 属性即可。现在再来修改下我们的例子,使用 Loader 来加载 ColorPicker 组件。
示例代码如下:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 320;
height: 240;
color: "#EEEEEE";
Text {
id: coloredText;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.top: parent.top;
anchors.topMargin: 4;
text: "Hello World!";
font.pixelSize: 32;
}
Loader{
id: redLoader;
width: 80;
height: 60;
focus: true;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
source: "ColorPicker.qml";
KeyNavigation.right: blueLoader;
KeyNavigation.tab: blueLoader;
onLoaded:{
item.color = "red";
item.focus = true;
}
onFocusChanged:{
item.focus = focus;
}
}
Loader{
id: blueLoader;
focus: true;
anchors.left: redLoader.right;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
source: "ColorPicker.qml";
KeyNavigation.left: redLoader;
KeyNavigation.tab: redLoader;
onLoaded:{
item.color = "blue";
}
onFocusChanged:{
item.focus = focus;
}
}
Connections {
target: redLoader.item;
onColorPicked:{
coloredText.color = clr;
if(!redLoader.focus){
redLoader.focus = true;
blueLoader.focus = false;
}
}
}
Connections {
target: blueLoader.item;
onColorPicked:{
coloredText.color = clr;
if(!blueLoader.focus){
blueLoader.focus = true;
redLoader.focus = false;
}
}
}
}
代码有几处改动:
一处是将 sourceComponent 修改为 source ,其值为 "ColorPicker.qml" 。
一处是两个 Connections 对象,在 onColorPicked 信号处理器中,设置了 Loader 的焦点属性,因为只有 Loader 有焦点,它加载的 item 才会有焦点,如果你鼠标点击某个颜色选择组件而加载它的 Loader 没有焦点,那么虽然颜色可以改变,但是焦点框出不来。
使用 Loader 加载定义在 qml 文档中的组件,比直接使用组件名构造对象要繁琐得多,但如果你的应用会根据特定的情景来决定某些界面元素是否显示,这种方式也许可以满足你。
参考:
《Qt Quick核心编程》第7章