首页 Qt 学习之路 2 Qt 学习之路 2(81):元素布局

Qt 学习之路 2(81):元素布局

28 2.7K

上一章我们介绍了 QML 中用于定位的几种元素,被称为定位器。除了定位器,QML 还提供了另外一种用于布局的机制。我们将这种机制成为锚点(anchor)。锚点允许我们灵活地设置两个元素的相对位置。它使两个元素之间形成一种类似于锚的关系,也就是两个元素之间形成一个固定点。锚点的行为类似于一种链接,它要比单纯地计算坐标改变更强。由于锚点描述的是相对位置,所以在使用锚点时,我们必须指定两个元素,声明其中一个元素相对于另外一个元素。锚点是Item元素的基本属性之一,因而适用于所有 QML 可视元素。

一个元素有 6 个主要的锚点的定位线,如下图所示:
QML 锚点
这 6 个定位线分别是:topbottomleftrighthorizontalCenterverticalCenter。对于Text元素,还有一个baseline锚点。每一个锚点定位线都可以结合一个偏移的数值。其中,topbottomleftright称为外边框;horizontalCenterverticalCenterbaseline称为偏移量。

下面,我们使用例子来说明这些锚点的使用。首先,我们需要重新定义一下上一章使用过的BlueRectangle组件:

import QtQuick 2.0

Rectangle {
    width: 48
    height: 48
    color: "blue"
    border.color: Qt.lighter(color)

    MouseArea {
        anchors.fill: parent
        drag.target: parent
    }
}

简单来说,我们在BlueRectangle最后增加了一个MouseArea组件。前面的章节中,我们简单使用了这个组件。顾名思义,这是一个用于处理鼠标事件的组件。之前我们使用了它处理鼠标点击事件。这里,我们使用了其拖动事件。anchors.fill: parent一行的含义马上就会解释;drag.target: parent则说明拖动目标是parent。我们的拖动对象是MouseArea的父组件,也就是BlueRectangle组件。

接下来看第一个例子:

QML anchors.fill

代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100
        BlueRectangle {
            width: 12
            anchors.fill: parent
            anchors.margins: 8
        }
    }
}

在这个例子中,我们使用anchors.fill设置内部蓝色矩形的锚点为填充(fill),填充的目的对象是parent;填充边距是 8px。注意,尽管我们设置了蓝色矩形宽度为 12px,但是因为锚点的优先级要高于宽度属性设置,所以蓝色矩形的实际宽度是 100px - 8px - 8px = 84px。

第二个例子:

QML anchors.left

代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100
        BlueRectangle {
            width: 48
            y: 8
            anchors.left: parent.left
            anchors.leftMargin: 8
        }
    }
}

这次,我们使用anchors.left设置内部蓝色矩形的锚点为父组件的左边线(parent.left);左边距是 8px。另外,我们可以试着拖动蓝色矩形,看它的移动方式。在我们拖动时,蓝色矩形只能沿着距离父组件左边 8px 的位置上下移动,这是由于我们设置了锚点的缘故。正如我们前面提到过的,锚点要比单纯地计算坐标改变的效果更强,更优先。

第三个例子:
QML anchors.left parent.right
代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100
        BlueRectangle {
            width: 48
            anchors.left: parent.right
        }
    }
}

这里,我们修改代码为anchors.left: parent.right,也就是将组件锚点的左边线设置为父组件的右边线。效果即如上图所示。当我们拖动组件时,依然只能上下移动。

下一个例子:
QML anchors.horizontalCenter

代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100

        BlueRectangle {
            id: blue1
            width: 48; height: 24
            y: 8
            anchors.horizontalCenter: parent.horizontalCenter
        }
        BlueRectangle {
            id: blue2
            width: 72; height: 24
            anchors.top: blue1.bottom
            anchors.topMargin: 4
            anchors.horizontalCenter: blue1.horizontalCenter
        }
    }
}

这算是一个稍微复杂的例子。这里有两个蓝色矩形:blue1blue2blue1的锚点水平中心线设置为父组件的水平中心;blue2的锚点上边线相对于blue1的底部,其中边距为 4px,另外,我们还增加了一个水平中线为blue1的水平中线。这样,blue1相对于父组件,blue2相对于blue1,这样便决定了三者之间的相对关系。当我们拖动蓝色矩形时可以发现,blue1blue2的相对位置始终不变,因为我们已经明确指定了这种相对位置,而二者可以像一个整体似的同时上下移动(因为我们没有指定其中任何一个的上下边距与父组件的关系)。

另外一个例子:
QML anchors.centerIn
代码如下所示:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100

        BlueRectangle {
            width: 48
            anchors.centerIn: parent
        }
    }
}

与第一个例子类似,我们使用的是anchors.centerIn: parent将蓝色矩形的中心固定在父组件的中心。由于我们已经指明是中心,所以也不能拖动这个蓝色矩形。

最后一个例子:
QML anchors.horizontalCenter verticalCenter

代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    width: 220
    height: 220
    color: "black"

    GreenRectangle {
        x: 10
        y: 10
        width: 100
        height: 100

        BlueRectangle {
            width: 48
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.horizontalCenterOffset: -12
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}

上一个例子中,anchors.centerIn: parent可以看作等价于anchors.horizontalCenter: parent.horizontalCenteranchors.verticalCenter: parent.verticalCenter。而这里,我们设置了anchors.horizontalCenterOffset为 -12,也就是向左偏移 12px。当然,我们也可以在anchors.centerIn: parent的基础上增加anchors.horizontalCenterOffset的值,二者是等价的。由于我们在这里指定的相对位置已经很明确,拖动也是无效的。

至此,我们简单介绍了 QML 中定位器和锚点的概念。看起来这些元素和机制都很简单,但是,通过有机地结合,足以灵活应对更复杂的场景。我们所要做的就是不断熟悉、深化对这些定位布局技术的理解。

28 评论

qt starter 2014年2月21日 - 18:02

你好,如果已经数序c和c++编程,还需要学习qml吗
官方是发布了一个新的编程语音qml,提供给了大家用qt quick来编写app的方式
但是我并没有找到相关文章说明为什么要用qt quick。
在能用qt c++后,有必要再研究qt quick吗?
刚接触qt没多久,有些困惑。希望博主能解说下为什么用quick。thk!

回复
豆子 2014年2月21日 - 23:00

QML 是独立于 C++ 的一种新的语言,Qt Quick 是建立在 QML 语言之上的类库。QML 相对 C++ 最大的特点是动态性和解释型。它不需要编译即可运行,声明式编程开发起来也特别快速。所以很多人把它用于原型开发。另外,Qt Quick 内置了丰富的动画效果,十分适合于手机等移动平台的开发(你会发现 iOS 或 Android 平台含有大量动画)。

回复
qt starter 2014年2月22日 - 10:53

谢谢
个人感觉qt quick和cocos2d-x都是侧重在移动应用领域,只是实现方式有些区别,一个用qml语言,一个用c++(cocos2d-x也可以基于qt c++开发)。不知博主是否有了解对比过二者差别。最近有想试试移动应用和嵌入式应用,但框架选择还一直定不下,也缺乏精力同时深入学习,所以想先了解了再下手。

回复
豆子 2014年2月22日 - 15:41

Qt Quick 和 cocos2d-x 还是有区别的。前者是一套应用程序通用框架,后者是 2D 游戏框架。也就是说,Qt Quick 提供的是一些应用程序所需要的组件,比如按钮、菜单之类;cocos2d-x 更多为游戏作了优化。

回复
corey 2014年2月23日 - 00:05

要是能快点把qt quick2更新完就好了
: )

回复
豆子 2014年2月25日 - 11:34

尽量尽量,感谢支持!

回复
卢扬 2014年3月5日 - 13:44

很好!支持!

回复
jsxyhyj 2014年3月7日 - 23:16

请问qml中怎么实现tableWidget的功能啊?

回复
豆子 2014年3月10日 - 10:18

目前 QML 中没有类似组件,这也是现在版本的 QML 不适合开发大型应用的原因之一:组件太少。如果确定需要,只能自己将 QTableWidget 封装成一个 QML 组件。

回复
welliam 2014年6月5日 - 11:22

最近在一个项目在考虑用qml,豆子对于5.3版本有看过么?官方有没有丰富组件呢?

回复
豆子 2014年6月5日 - 16:09

没有,5.3 没有增加新的组件。

回复
rookie 2014年5月7日 - 23:04

您好
想請問關於QML下ComboBox的顯示問題
ListModel {id: author1} //資料會由其他地方append進來
TableView {
model: author1
TableViewColumn{
role: "attribute"
title: "attribute"
width: 180
delegate:Rectangle {
ComboBox {
id:combo
model:author1
textRole: "attribute"
anchors.fill: parent
}
}
}
假設ListModel 中有三筆資料分別為aa,bb,cc,目前執行結果是在tableview中有顯示
3個combobox且下拉選項為aa,bb,cc無誤,但三個combobox在外觀上都是顯示aa.
該如何設定讓他變成三個combobox分別顯示aa,bb,cc呢?

回复
豆子 2014年5月8日 - 09:03

貌似需要在 onCompleted 信号处理中设置每个 combo box 的 currentIndex 之类的属性。因为默认显示的是第一个数据项。

回复
rookie 2014年5月8日 - 09:51

那請問該如何個別設定每個combo box啊?
Component.onCompleted: {
var i
for(i=0;i<combo.count;i++)
combo.currentIndex=i
}
這樣寫只會讓每個combo box都顯示最後一個選項....

回复
豆子 2014年5月8日 - 14:45

你应该根据 model 的数据来判断这个 combo box 应该使用哪个初始值。可以使用 delegate 的 onCompleted 事件吧,在函数中使用 TableView.view.model.xxx 读取该代理的数据值,更具这个数据判断默认值。

回复
rookie 2014年5月8日 - 15:37

豆哥,不好意思不太懂您的意思耶,能否請您針對上面的程式給個簡單的範例呢?
原本的想法是不知道能不能"個別"操控combobox來設定currentindex
意思大概如下
for(int i=0;i<3;i++)
combo[i].currentindex=i-1
在生成3個combobox後再去一一做調整這樣
所以我才使用Component.onCompleted:
或是一定要使用其他方式才能做到我想像的那樣呢?

rookie 2014年5月8日 - 15:46

更正combo[i].currentindex=i-1
-1是打錯的!

豆子 2014年5月9日 - 23:31

不知道下面的代码是不是你想要的:

import QtQuick 2.1
import QtQuick.Controls 1.1

ApplicationWindow {
ListModel {
id: author
ListElement { attribute: "A" }
ListElement { attribute: "B" }
ListElement { attribute: "C" }
}

TableView {
id: tableView
model: author
anchors.fill: parent

TableViewColumn {
role: "attribute"
title: "attribute"
width: 60
delegate: Rectangle {
ComboBox {
id: combo
model: [ "A", "B", "C" ]
anchors.fill: parent
onCountChanged: {
combo.currentIndex = combo.find(styleData.value);
}
}
}
}
}
}

rookie 2014年5月11日 - 17:47

豆哥太感謝你了! 正是我要的!
抱歉現在才回復,因為之前豆哥回復都有寄信通知我的,
沒想到這次都沒收到信,自己來這才看到的!
另外方便請問您關於styleData.value觀念和意思麼?
之前自己從不知道還能這樣用find method呢

豆子 2014年5月12日 - 21:31

有关 styleData 可以看下 TableViewColumn 的文档,里面有详细的介绍。 styleData 可以认为是 QML 赋值给每一个 delegate 的有关这个 delegate 的描述信息,包括其显示的值、索引、背景色之类,方便自定义 delegate

rookie 2014年5月11日 - 21:00

額外一提
發現能直接用currentIndex:styleData.row
如此也能達到同樣功能

豆子 2014年5月12日 - 21:28

使用 styleData.row 也是可以的,不过这样的话就是数据按照行的顺序赋予默认值。如果你的数据顺序是 A, C, B,按照行号赋值可能就是错误的。毕竟你的需求是按照 model 的数据赋值,应该用 find 先找到 index 再赋值更合理

rookie 2014年5月14日 - 12:56

豆哥您好! 想再請教您些問題,
同一個TableView下的兩個TableViewColumn該如何互相操控呢?向您上面給的例子model: [ "A", "B", "C" ],如果我在ListModel中修改ListElement { id:"01" ; attribute: “A” }並且新增一個ID的Column,id:"02"對應到attribute B,以此類推。ID Column在一開始的顯示上使用 Text{ text:styleData.value }是OK的,有正確對應到。
但希望做到變動combobox選項時ID Column也會自動顯示對應的ID,我這邊試過用在combobox下使用onCurrentIndexChange試圖修改ID Column但失敗了。

回复
徐程 2015年3月12日 - 04:52

你好,不好意思打扰了,偶尔看到你的网站不知道能不能问个问题,我想在combobox里显示文件名所以我做了:
Item{
ListView{
id: listCombox
x: 123
y: 152
width: 110
height: 30
model: FolderListModel{
id: folderModel
folder:"/config"
nameFilters: ["*.yml"]
}
delegate: ComboBox {
model:folderModel
}
}
}
但是我最终都无法显示。。。

回复
豆子 2015年3月12日 - 10:18

你的代码将 ComboBox 作为 ListView 的代理,不知道是不是你需要的?而且二者的 model 是同一个

回复
徐程 2015年3月12日 - 21:26

不好意思昨天疏忽了一件事,其实我是想做:
Item{
ListView{
id: listCombox
x: 124
y: 150
width: 110
height: 30
model: FolderListModel{
id: folderModel
folder:"./config"
nameFilters: ["*.yml"]
}
delegate: ComboBox {
id:combobox
model:listCombox
}
}
}
对,我想让combobox显示folderlistmodel筛选后的文件,但是最后我得到个错误:Unable to assign QQuickListView to QString
而且我得到了两个combobox,但是我想要的是在一个combox里获得所有的文件名。。。

回复
fan 2016年3月30日 - 14:45

anchors.horizontalCenter: parent.horizontalCenter 是水平中轴,但是例4出来的效果是垂直中轴啊,,,有点搞不懂

回复
豆子 2016年3月30日 - 23:19

anchors.horizontalCenter: parent.horizontalCenter 意思是 anchors 的水平中心对准父组件的水平中心,因此就是这个效果,在一定程度上并不是“垂直中轴”的概念。

回复

回复 豆子 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2