@TOC
📝导言:控件概述
Widget 是Qt中的核⼼概念.英⽂原义是"⼩部件",我们此处也把它翻译为"控件".
- 按钮,列表视图,树形视图,单⾏输⼊框,多⾏输⼊框,滚动条,下拉框等,都可以称为"控件".
- Qt 作为⼀个成熟的GUI开发框架,内置了⼤量的常⽤控件.这⼀点在QtDesigner中就可以看到端倪.
- 并且Qt也提供了"⾃定义控件"的能⼒,可以让程序猿在现有控件不能满⾜需求的时候,对现有控件做出扩展,或者⼿搓出新的控件.
综上,学习Qt,其中⼀个很重要的任务就是熟悉并掌握Qt内置的常⽤控件. 这些控件对于我们快速开发出符合需求的界⾯,是⾄关重要的.
一、QWidget核⼼属性
使⽤QWidget
类表⽰"控件".像按钮,视图,输⼊框,滚动条等具体的控件类,都是继承⾃QWidget
.
QWidget
中就包含了Qt整个控件体系中,通⽤的部分
在QtDesigner
中,随便拖⼀个控件过来,选中该控件,即可在右下⽅看到QWidget
中的属性
这些属性既可以通过QtDesigner会直接修改,也可以通过代码的⽅式修改.
这些属性的具体含义,在QtAssistant中均有详细介绍.
在QtAssistant中搜索QWidget,即可找到对应的⽂档说明.(或者在QtCreator代码中,选中QWidget, 按F1也可).
二、enabled
2.1 API使用
API | 说明 |
isEnabled() | 获取到控件的可用状态. |
setEnabled | 设置控件是否可使用. |
所谓"禁⽤"指的是该控件不能接收任何⽤⼾的输⼊事件,并且外观上往往是灰⾊的.如果⼀个widget被禁⽤,则该widget的⼦元素也被禁⽤.
代码⽰例:使⽤代码创建⼀个禁⽤状态的按钮
widget.cpp:
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{QPushButton* btn = new QPushButton("这是一个被禁用的按钮", this);btn->setEnabled(false);ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}
运⾏程序,可以看到按钮处于灰⾊状态,⽆法被点击
2.2 代码⽰例
:通过按钮2切换按钮1的禁⽤状态.
- 使⽤QtDesigner拖两个按钮到Widget中.两个按钮的
objectName
分别为pushButton
和pushButton_2
, 转到槽⽣成两个按钮的slot函数
- 使用isEnabled获取当前按钮的可用状态.
- 使用setEnabled修改按钮的可用状态.此处是直接针对原来的可用状态进行取反后设置.
运⾏程序,可以看到,初始情况下,上⾯的按钮是可⽤状态.点击下⽅按钮,即可使上⽅按钮被禁⽤;再次点击下⽅按钮,上⽅按钮就会解除禁⽤.(禁⽤状态的按钮为灰⾊,且不可点击).
void Widget::on_pushButton_clicked()
{qDebug()<<"按下按钮";
}void Widget::on_pushButton_2_clicked()
{bool flag = this->ui->pushButton->isEnabled();this->ui->pushButton->setEnabled(!flag);
}
Qobject
的objectName
属性介绍:Qobject 是QWidget的父类.里面最主要的属性就是objectName .在一个Qt程序中,objectName
相当于对象的身份标识,彼此之间不能重复.在使用Qt Designer时,尤其是界面上存在多个widget 的时候,可以通过objectName
获取到指定的widget
对象.Qt Designer生成的ui
文件,本身是xml
格式的.qmake
会把这个xml
文件转换成C++的.h
文件(这个文件生成在build
目录中),构成一个ui_widget类.每个widget
的objectName
最终就会成为ui_widget
类的属性名字.最终这个类的实例,就是(Ui : : widget *ui
,因此就可以通过形如ui->pushButton或者ui->pushButton_2这样的代码获取到界面上的widget对象了.在QtDesigner中创建按钮的时候,可以设置按钮的初始状态是"可⽤"还是"禁⽤".如果把enabled这⼀列的对钩去掉,则按钮的初始状态就是"禁⽤"状态.
三、 geometry
3.1 API使用
位置和尺⼨.其实是四个属性的统称:
x
横坐标y
纵坐标width
宽度height
高度但是实际开发中,我们并不会直接使⽤这⼏个属性,⽽是通过⼀系列封装的⽅法来获取/修改 对于Qt的坐标系,不要忘记是⼀个"左⼿坐标系".其中坐标系的原点是当前元素的⽗元素的左上⻆. API说明geometry()
获取到控件的位置和尺⼨.返回结果是⼀个QRect
,包含了x,y,width,height.其中x,y是左上⻆的坐标.
API | 说明 |
| 获取到控件的位置和尺寸。返回结果是一个 |
| 设置控件的位置和尺寸。可以直接设置一个 |
|
2.2 简单示例
代码⽰例:控制按钮的位置
- 在界⾯中拖五个按钮.
五个按钮的objectName
分别为
pushButton_target pushButton_down pushButton_up pushButton_left
pushButton_right
2) 在widget.cpp中编写四个按钮的slot函数
void Widget::on_PushButton_up_clicked()
{QRect rect = ui->PushButton_target->geometry();rect.setY(rect.y() - 5);ui->PushButton_target->setGeometry(rect);
}void Widget::on_PushButton_down_clicked()
{QRect rect = ui->PushButton_target->geometry();rect.setY(rect.y() + 5);ui->PushButton_target->setGeometry(rect);
}void Widget::on_PushButton_left_clicked()
{QRect rect = ui->PushButton_target->geometry();rect.setX(rect.x() - 5);ui->PushButton_target->setGeometry(rect);
}void Widget::on_PushButton_right_clicked()
{QRect rect = ui->PushButton_target->geometry();rect.setX(rect.x() + 5);ui->PushButton_target->setGeometry(rect);
}
运⾏程序,可以看到,按下下⽅的四个按钮,就会控制target的左上⻆的位置.对应的按钮整个尺⼨也会发⽣改变.
上述代码中我们是直接设置的QRect中的x,y.实际上QRect内部是存储了左上和右下两个点的坐标,再通过这两个点的坐标差值计算⻓宽.单纯修改左上坐标就会引起整个矩形的⻓宽发⽣改变.
如果想让整个按钮都移动,可以改成下列代码:
void Widget::on_PushButton_up_clicked()
{QRect rect = ui->PushButton_target->geometry();ui->PushButton_target->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height());
}void Widget::on_PushButton_down_clicked()
{QRect rect = ui->PushButton_target->geometry();ui->PushButton_target->setGeometry(rect.x(), rect.y() + 5, rect.width(), rect.height());
}void Widget::on_PushButton_left_clicked()
{QRect rect = ui->PushButton_target->geometry();ui->PushButton_target->setGeometry(rect.x() - 5, rect.y(), rect.width(), rect.height());
}void Widget::on_PushButton_right_clicked()
{QRect rect = ui->PushButton_target->geometry();ui->PushButton_target->setGeometry(rect.x() + 5, rect.y(), rect.width(), rect.height());
}
上述代码使用move方法也是可以的.
代码⽰例: 点我抢红包1)往界面上拖拽两个按钮和一个Label.Label
的objectName
为pushButton_accept
和pushButton_reject
, label的objectName为label控件中文本如下图所示.
2) 在widget.cpp中添加slot函数
void Widget::on_pushButton_not_grabbing_clicked()
{ui->label->setText("恭喜抢到红包!");
}void Widget::on_pushButton_rob_clicked()
{//获取窗口的宽度和高度int windowwidgth = this->geometry().width();int windowheight = this->geometry().height();//重新生成位置int x = rand() % windowwidgth;int y = rand() % windowheight;//移动按钮ui->pushButton_rob->move(x, y);
}
运⾏程序,可以看到,当“我抢”点击时,按钮就跑了.
更狠一点,代码使⽤的是
pressed
,⿏标按下未松开,按钮就跑了
void Widget::on_pushButton_rob_pressed()
{//获取窗口的宽度和高度int windowwidgth = this->geometry().width();int windowheight = this->geometry().height();//重新生成位置int x = rand() % windowwidgth;int y = rand() % windowheight;//移动按钮ui->pushButton_rob->move(x, y);
}
四、windowframe
4.1 API使用
如果widget作为⼀个窗⼝(带有标题栏,最⼩化,最⼤化,关闭按钮),那么在计算尺⼨和坐标的时候就有两种算法.包含windowframe和不包含windowframe
.其中x(),y(), frameGeometry(), pos(), move()
都是按照包含windowframe的⽅式来计算的.其中geometry(),width(), height(), rect(), size()
则是按照不包含windowframe的⽅式来计算的.当然,如果⼀个不是作为窗⼝的widget
,上述两类⽅式得到的结果是⼀致的
核心概念
pos()
/x()
/y()
:窗口整体(含标题栏、边框)的左上角在屏幕坐标系的位置,x()
是水平坐标,y()
是垂直坐标,pos()
返回(x, y)
点。geometry()
:指窗口客户区(Client Area
,即标题栏下方可绘制内容的区域 )的几何信息,geometry().x()
/geometry().y()
是客户区左上角相对窗口整体的偏移(因标题栏存在,通常y
有正值 ),geometry().width()
/geometry().height()
是客户区的宽高。width()
/height()
:等价于geometry().width()
/geometry().height()
,直接获取客户区宽高。frameGeometry()
:窗口整体(含标题栏、边框)的几何信息,frameGeometry().width()
/frameGeometry().height()
是窗口整体的宽高,包含边框、标题栏的尺寸。
API | 说明 |
| 获取横坐标 计算时包含 window frame |
| 获取纵坐标 计算时包含 window frame |
| 返回 计算时包含 window frame |
| 返回 计算时包含 window frame |
| 返回 计算时包含 window frame 对象 |
| 获取宽度 计算时不包含 window frame |
| 获取高度 计算时不包含 window frame |
| 返回 计算时不包含 window frame |
| 返回 计算时不包含 window frame 对象 |
| 返回 计算时不包含 window frame 对象 |
| 直接设置窗口的位置和尺寸。可以设置 计算时不包含 window frame 对象 |
4.2 简单示例
代码⽰例:感受geometry和frameGeometry的区别.1)在界⾯上放置⼀个按钮.
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QRect rect1 = this->geometry();QRect rect2 = this->frameGeometry();qDebug() << rect1;qDebug() << rect2;}
- 在按钮的slot函数中,编写代码
为啥结果一下?在 Widget 构造函数中,窗口还未完成最终的绘制和布局计算。此时 Qt 尚未为窗口添加边框和标题栏(这些属于窗口管理器的装饰),因此 frameGeometry()(窗口整体,含边框)和 geometry()(客户区)暂时指向同一个区域。
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{QRect rect1 = this->geometry();QRect rect2 = this->frameGeometry();qDebug() << rect1;qDebug() << rect2;
}
对于标准窗口,标题栏通常只在垂直方向增加高度(如下方示例中的 630-600=30 就是标题栏高度),而水平方向的边框宽度可能被 Qt 计入客户区宽度计算,或在特定样式下被优化为与客户区宽度一致。
geometry: QRect(454,156 800x600) // 客户区:宽800,高600
frameGeometry: QRect(454,126 800x630) // 整体:宽800(和客户区相同),高630(比客户区多30,即标题栏高度)
宽度方向无差异,说明水平方向没有额外的边框宽度,或边框宽度被包含在客户区宽度计算中。
在构造⽅法中,Widget刚刚创建出来,还没有加⼊到对象树中.此时也就不具备Window frame.
在按钮的slot函数中,由于⽤⼾点击的时候,对象树已经构造好了,此时Widget已经具备了Windowframe,因此在位置和尺⼨上均出现了差异.
如果把上述代码修改成打印pushButton的geometry和frameGeometry,结果就是完全相同的.因为pushButton并⾮是⼀个窗⼝.
五、windowTitle
5.1常用API:
API | 说明 |
| 获取到控件的窗口标题. |
| 设置控件的窗口标题. |
注意!上述设置操作针对不同的widget可能会有不同的⾏为.如果是顶层widget(独⽴窗⼝),这个操作才会有效.如果是⼦widget,这个操作⽆任何效果.
5.2 代码⽰例
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//设置窗口标题this->setWindowTitle("这是标题");
}
六、 windowIcon
6.1API使用
API | 说明 |
| 获取到控件的窗口图标. 返回 |
| 设置控件的窗口图标. |
同
windowTitle
,上述操作仅针对顶层widget
有效.
注意: Windows下路径的分隔符可以使用
/
也可以使用
\
.但是如果在字符串中使用
\
需要写作转义字符的形式
\\
.因此我们还是更推荐使用
/
.
绝对路径:以盘符(windows)或者以
/
(Linux)开头的路径.相对路径:以.
(表示当前路径)或者以..(表示当前路径上级路径)开头的路径.其中.经常也会省略.相对路径的前提是需要明确"当前工作目录".
对于Qt程序来说,当前⼯作⽬录可能是变化的.⽐如通过QtCreator
运⾏的程序,当前⼯作⽬录是项⽬的构建⽬录;直接双击exe
运⾏,⼯作⽬录则是exe
所在⽬录.
6.2 代码⽰例
代码⽰例:获取当前的⼯作⽬录
- 在界⾯上创建⼀个⽐较⼤的
label
,确保能把路径显⽰完整.objectName使⽤默认的label
即可. - 修改widget.cpp使⽤
QDir::currentPath()
即可获取到当前⼯作⽬录
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//设置窗口标题this->setWindowTitle("这是哈基米");//创建图标对象//路径不能带中文,不能使用反斜杠\,因为\会跟后面转译, 如果要使用的是\\才可以QIcon icon("C:/Users/Acer/Desktop/image/hachimitsu.jpg");//设置图标this->setWindowIcon(icon);//获取到当前的工作目录QString currentDir = QDir::currentPath();//设置工作目录到label中ui->label->setText(currentDir);
}
- 直接在
QtCreator
中执⾏程序,可以看到当前⼯作⽬录是项⽬的构建⽬录. - 进⼊上述构建⽬录,把⾥⾯的exe拷⻉到其他⽬录中,⽐如D:中.再次执⾏程序,可以看到当前⼯作⽬录已经发⽣改变.
要想直接能双击exe运⾏,需要先把Qt的路径添加到path环境变量中,否则会提⽰找不到动态库.注意,上述构建⽬录,是随时可删除的.⽐如点击菜单栏中的"构建"->"清理项⽬",就会把这个⽬录中的内容清空掉.因此如果我们把图⽚⽂件放到构建⽬录中,可能在不⼩⼼删除后就丢失了.我们还是希望能够把图⽚和 源代码放到⼀起,并且使我们的程序⽆论拷⻉到任何位置中都能正确使⽤图⽚.
七、Qt 使⽤qrc机制
⽤qrc
机制帮我们⾃动完成了上述⼯作,更⽅便的来管理项⽬依赖的静态资源
qrc
⽂件是⼀种XML
格式的资源配置⽂件,它⽤XML
记录硬盘上的⽂件和对应的随意指定的资源名称. 应⽤程序通过资源名称来访问这些资源.- 在
Qt
开发中,可以通过将资源⽂件添加到项⽬中来⽅便地访问和管理这些资源.这些资源⽂件可以位于qrc
⽂件所在⽬录的同级或其⼦⽬录下. - 在构建程序的过程中,
Qt
会把资源⽂件的⼆进制数据转成cpp
代码,编译到exe
中.从⽽使依赖的资源变得"路径⽆关". - 这种资源管理机制并⾮Qt独有,很多开发框架都有类似的机制.例如Android的Resources和AssetManager也是类似的效果.
代码⽰例:通过qrc管理图⽚作为图标
- 右键项⽬,创建⼀个
QtResourceFile
(qrc
⽂件),⽂件名随意起(不要带中⽂),此处叫做resource.qrc
2)在qrc编辑器中,添加前缀.
此处我们前缀设置成/
即可.
所谓的前缀,可以理解成"⽬录".这个前缀决定了后续我们如何在代码中访问资源.3) 在资源编辑器中,点击add Files
添加资源⽂件.此处我们需要添加的是hachimitsu.jpg
添加完毕后,可以在资源编辑器中看到添加好的⽂件
4) 在代码中使⽤hachimitsu.jpg
编辑widget.cpp
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//访问 到`hachimitsu.jpg`资源QIcon icon(":/hachimitsu.jpg");//设置图标this->setWindowIcon(icon);
}
注意上述路径的访问规则.
- 使⽤
:
作为开头,表⽰从qrc
中读取资源. /
是上⾯配置的前缀hachimitsu.jpg
是资源的名称需要确保代码中编写的路径和添加到qrc中资源的路径匹配.否则资源⽆法被访问(同时也不会有报错提⽰).
接下来,我们可以进⼊到项⽬的构建⽬录,可以看到,⽬录中多了⼀个接打开这个⽂件,可以看到类似如下代码:
上述代码其实就是通过unsignedchar
数组,把hachimitsu.jpg
中的每个字节都记录下来.这些代码会被编译到exe
中.后续⽆论exe
被复制到哪个⽬录下,都确保能够访问到该图⽚资源.
上述qrc这⼀套资源管理⽅案,优点和缺点都很明显.优点:确保了图⽚,字体,声⾳等资源能够真正做到"⽬录⽆关",⽆论如何都不会出现资源丢失的情况.缺点:不适合管理体积⼤的资源.如果资源⽐较⼤(⽐如是⼏个MB的⽂件),或者资源特别多,⽣成的最终的exe体积就会⽐较⼤,程序运⾏消耗的内存也会增⼤,程序编译的时间也会显著增加.