您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Qt Quick 组件与对象动态创建详解(一)
 
作者 WPJY的博客,火龙果软件    发布于 2014-08-11
  3743  次浏览      15
 

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 以便响应回车和空格两个按键。

你可以使用 Item 或其派生类作为组件的根 item 。 ColorPicker 组件使用 Rectangle 作为根 Item 。现在让我们看看如实在其它文件中使用新定义的 ColorPicker 组件。我修改了上节的示例,新的 qml 文件被我命名为 component_file.qml ,内容如下:

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

function setTextColor(clr){
coloredText.color = clr;
}

ColorPicker {
id: redColor;
color: "red";
focus: true;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
KeyNavigation.right: blueColor;
KeyNavigation.tab: blueColor;
onColorPicked:{
coloredText.color = clr;
}
}

ColorPicker {
id: blueColor;
color: "blue";
anchors.left: redColor.right;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
KeyNavigation.left: redColor;
KeyNavigation.right: pinkColor;
KeyNavigation.tab: pinkColor;
}

ColorPicker {
id: pinkColor;
color: "pink";
anchors.left: blueColor.right;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
KeyNavigation.left: blueColor;
KeyNavigation.tab: redColor;
}

Component.onCompleted:{
blueColor.colorPicked.connect(setTextColor);
pinkColor.colorPicked.connect(setTextColor);
}
}

可以看到, component_file.qml 使用 ColorPicker 组件的方式与使用 Rectangle 、 Button 、 Text 等标准 Qt Quick 组件完全一致:可以给组件指定唯一的 id ,可以使用锚布局,可以使用 KeyNavigation 附加属性……总之,自定义的组件和 Qt Quick 组件并无本质不同。不过需要注意的是,组件实例的 id 和组成组件的顶层 item 的 id 是各自独立的,以上面的例子来看, redColor 和 colorPicker 是两个不同的 id ,前者指代组件对象(虽然组件的定义没有使用 Component ),后者指代 ColorPicker 的 Rectangle 对象。

上面的代码还演示两种使用 qml 自定义信号的方式, redColor 使用信号处理器, greeColor 和 pinkColor 则使用了 signal 对象的 connect() 方法连接到 setTextColor() 方法上。

我把 ColorPicker.qml 和 component_file.qml 放在同一个文件下面,否则可能会报错。图 1 是运行 "qmlscene component_file.qml" 命令 的效果:

图 1 在文件中定义组件并使用

对于定义在单独文件中的 Component ,除了可以像刚刚介绍的那样使用,也可以使用 Loader 来动态加载,根据需要再创建对象。下面我们就来看 Loader 究竟是何方妖怪。

使用 Loader

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 。

我们改变一下颜色选择器示例的代码,两个 Loader 对象,一个设置尺寸一个不设置,看看是什么效果。新的 qml 文档我们命名为 loader_test.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;
width: 80; // [1]
height: 60;// [2]
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;
}
}
}

注意上面的代码中的注释,方括号标准的 2 处修改,设置了红色 Loader 的尺寸,效果如图 1 所示:

图 2 Loader 尺寸

如果 Loader 加载的 item 想处理按键事件,那么必须将 Loader 对象的 focus 属性置 true 。又因为 Loader 本身也是一个焦点敏感的对象,所以如果它加载的 item 处理了按键事件,应当将事件的 accepted 属性置 true ,以免已经被吃掉的事件再传递给 Loader 。我们来修改下 loader_test.qml ,加入对焦点的处理,当一个颜色组件拥有焦点时,绘制一个边框,此时如果你按下回车或空格键,会触发其 colorPicked 信号。同时我们也处理左右键,在不同的颜色选择组件之间切换焦点。将新代码命名为 loader_focus.qml ,内容如下:

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

Component {
id: colorComponent;
Rectangle {
id: colorPicker;
width: 50;
height: 30;
signal colorPicked(color clr);
property Item loader;
border.width: focus ? 2 : 0;
border.color: focus ? "#90D750" : "#808080";
MouseArea {
anchors.fill: parent
onClicked: {
colorPicker.colorPicked(colorPicker.color);
loader.focus = true;
}
}
Keys.onReturnPressed: {
colorPicker.colorPicked(colorPicker.color);
event.accepted = true;
}
Keys.onSpacePressed: {
colorPicker.colorPicked(colorPicker.color);
event.accepted = true;
}
}
}

Loader{
id: redLoader;
width: 80;
height: 60;
focus: true;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
sourceComponent: colorComponent;
KeyNavigation.right: blueLoader;
KeyNavigation.tab: blueLoader;

onLoaded:{
item.color = "red";
item.focus = true;
item.loader = redLoader;
}
onFocusChanged:{
item.focus = focus;
}
}

Loader{
id: blueLoader;
anchors.left: redLoader.right;
anchors.leftMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
sourceComponent: colorComponent;
KeyNavigation.left: redLoader;
KeyNavigation.tab: redLoader;

onLoaded:{
item.color = "blue";
item.loader = blueLoader;
}
onFocusChanged:{
item.focus = focus;
}
}
Connections {
target: redLoader.item;
onColorPicked:{
coloredText.color = clr;
}
}

Connections {
target: blueLoader.item;
onColorPicked:{
coloredText.color = clr;
}
}
}

首先我让颜色选择组件处理按键事件(如忘记请参看《Qt Quick事件处理之鼠标、键盘、定时器》),收到回车和空格键时发出 colorPicked 信号。我还给颜色选择组件定义了一个名为 loader 的属性,以便鼠标点击颜色选择组件时可以改变 Loader 对象的焦点属性。我们在 Loader 的 onLoaded 信号处理器中给颜色选择组件的 loader 属性赋值。

颜色选择组件根据焦点状态决定是否绘制边框,当有焦点时绘制宽度为 2 的边框。

对于 Loader ,我设置了 KeyNavigation 附加属性,指定左右键和 tab 键如何切换焦点,而当焦点变化时,同步改变颜色选择组件的焦点。最后我设置 redLoader 拥有初始焦点。

图 3 loader与按键、焦点

你可以使用 qmlscene loader_focus.qml 命令运行看看效果,鼠标点击某个颜色选择组件,会触发焦点切换和边框变化,左右键、 tab 键也会触发焦点变化,而当一个颜色选择组件拥有焦点时,回车、空格键都可以触发 "Hello World!" 改变颜色。

从文件加载组件

之前介绍 Loader 时,我们以嵌入式定义的 Component 为例子说明 Loader 的各种特性和用法,现在我们来看如何从文件加载组件。

对于定义在一个独立文件中的 Component ,同样可以使用 Loader 来加载,只要指定 Loader 的 source 属性即可。现在再来修改下我们的例子,使用 Loader 来加载 ColorPicker 组件。

新的 qml 文档我命名为 loader_component_file.qml ,内容如下:

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 文档中的组件,比直接使用组件名构造对象要繁琐得多,但如果你的应用会根据特定的情景来决定某些界面元素是否显示,这种方式也许可以满足你。

   
3743 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...