前言
鉴于之前学习Android的经验,我把Qt学习分以下步骤进行:
- UI
- 信号与槽
- 国际化
- 多线程
- 绘图
- 文件操作
- 数据库
- 网络编程
- …
熟悉了UI、信号与槽应该就能写出个人模狗样的东西了。
Qt常用控件及分类
建议直接到 Qt Designer 中熟悉熟悉,控件不多,也不用打代码。
感兴趣的就熟悉以下控件属性等,不感兴趣就学着托拉拽出一个计算器…
如果你还是想先听别人介绍一下,那么直接看这里 疯仔嵌入式- qt creator入门之(一)
如何获取Qt Designer中创建的控件对象
Qt Designer 中创建的控件,我们应该如何取得呢?
例如在mainwindow.ui
中生成了一个lable标签,查看其属性可发现
那么在mainwindow.cpp
中,我们可在构造函数中通过ui->ObjectName(类似控件ID)
取得控件。
需要注意的是取得控件的操作必须写在ui->setupUi(this)
之后。
//mainwindow.h
#include <QLabel>
//.....
class MainWindow : public QMainWindow
{
///.....
private:
Ui::MainWindow *ui;
QLabel *m_label;
};
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_label = ui->label;
}
纯手动添加控件
mainwindow.h
中定义好所用控件变量;
注意控件定义为 控件类型 *控件变量名
。
在mainwindow.cpp
文件中,在ui->setupUi(this)
之后创建控件;
注意创建控件变量的方法为 控件变量名 = new 控件类型
。
若有需要可在此设置控件属性。
最后记得要将控件添加至布局中,目前仅发现只有 布局 具有addWidget()
方法。
//mainwindow.h
#include <QLabel>
//.....
class MainWindow : public QMainWindow
{
///.....
private:
Ui::MainWindow *ui;
QLabel *m_label;
};
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//新建一个Label
m_label = ui->label;
//设置属性
m_label->setText("Hello");
//添加到布局,verticalLayout为Qt Designer中添加的一个布局
ui->verticalLayout->addWidget(m_label);
}
Qt Designer 是如何起作用的
当你熟悉了Qt Designer
,及手动添加控件后,不止你内心是否也会生成这样的困惑:我们知道 Qt 是 C++
为主的,而我们用 Qt Designer
生成的 *.ui
,在编辑模式下,可以发现它是类似 html
的标签式代码。那么我们的 *.ui
是如何起作用的呢?
实际上,用 Qt Designer
设计的 *.ui
文件可以通过 uic工具
转换为*.h
文件(在编译时也会自动生成这样一个 ui_*.h
文件)
我就发现了我的 mainwindow.cpp
存在以下内容:
#include "ui_mainwindow.h"
注意: ui_xxx.h
是根据 Qt Designer
自动生成的,由于再次编译会重新生成,对其进行手动修改并无意义。因此,其一般也不会出现在我们的工程目录中。
ui_xxx.h
一般包含以下内容:
-
定义了一个类
ui_xxxx
,用于封装可视化设计的界面。 -
自动生成了界面各组件成员变量的定义。在
public
部分为界面的每个组件定义一个指针变量,类名就是我们在Qt Designer
界面中设置的ObjectName
。注意:窗体的类名一般不建议改动;实例名需在窗体代码中定义。
-
定义了
setupUi()
函数(圈起来,重点),相信对该函数一定很眼熟,它在mainwindow.cpp
中会被调用。前面也说到,想在C++
代码中获取得U控件
,必须在此处之后。 -
setupUi()
还调用了retranslateUi(Widget)
,用来设置界面各组件的文字内容属性,如标签的文字、按键的文字、窗体的标题等。 -
setupUi()
还调用了一行看起来比较特别的代码(简直就是颜色不一样的烟火)QMetaObject::connectSlotsByName(MainWindow);
该函数官方说明如下:
简而言之就是:搜索所用的子类,并链接相应的信号与槽。
关于源码阅读,我没有找到可以在 Qt Creator
中直接跳转的方法,最后无奈选择 Woboq的在线C/C++浏览器 ,无需下载,即开即用,用完就跑。
QMetaObject::connectSlotsByName
源码中有以下内容:
void QMetaObject::connectSlotsByName(QObject *o)
{
if (!o)
return;
const QMetaObject *mo = o->metaObject();
Q_ASSERT(mo);
const QObjectList list = // list of all objects to look for matching signals including...
o->findChildren<QObject *>(QString()) // all children of 'o'...
<< o; // and the object 'o' itself
// for each method/slot of o ...
for (int i = 0; i < mo->methodCount(); ++i) {
const QByteArray slotSignature = mo->method(i).methodSignature();
const char *slot = slotSignature.constData();
Q_ASSERT(slot);
// ...that starts with "on_", ...
if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
continue;
// ...we check each object in our list, ...
bool foundIt = false;
for(int j = 0; j < list.count(); ++j) {
const QObject *co = list.at(j);
const QByteArray coName = co->objectName().toLatin1();
// ...discarding those whose objectName is not fitting the pattern "on_<objectName>_...", ...
if (coName.isEmpty() || qstrncmp(slot + 3, coName.constData(), coName.size()) || slot[coName.size()+3] != '_')
continue;
const char *signal = slot + coName.size() + 4; // the 'signal' part of the slot name
// ...for the presence of a matching signal "on_<objectName>_<signal>".
const QMetaObject *smeta;
int sigIndex = co->d_func()->signalIndex(signal, &smeta);
if (sigIndex < 0) {
// if no exactly fitting signal (name + complete parameter type list) could be found
// look for just any signal with the correct name and at least the slot's parameter list.
// Note: if more than one of thoses signals exist, the one that gets connected is
// chosen 'at random' (order of declaration in source file)
QList<QByteArray> compatibleSignals;
const QMetaObject *smo = co->metaObject();
int sigLen = qstrlen(signal) - 1; // ignore the trailing ')'
for (int k = QMetaObjectPrivate::absoluteSignalCount(smo)-1; k >= 0; --k) {
const QMetaMethod method = QMetaObjectPrivate::signal(smo, k);
if (!qstrncmp(method.methodSignature().constData(), signal, sigLen)) {
smeta = method.enclosingMetaObject();
sigIndex = k;
compatibleSignals.prepend(method.methodSignature());
}
}
if (compatibleSignals.size() > 1)
qWarning() << "QMetaObject::connectSlotsByName: Connecting slot" << slot
<< "with the first of the following compatible signals:" << compatibleSignals;
}
if (sigIndex < 0)
continue;
// we connect it...
if (Connection(QMetaObjectPrivate::connect(co, sigIndex, smeta, o, i))) {
foundIt = true;
// ...and stop looking for further objects with the same name.
// Note: the Designer will make sure each object name is unique in the above
// 'list' but other code may create two child objects with the same name. In
// this case one is chosen 'at random'.
break;
}
}
if (foundIt) {
// we found our slot, now skip all overloads
while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
++i;
} else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
// check if the slot has the following signature: "on_..._...(..."
int iParen = slotSignature.indexOf('(');
int iLastUnderscore = slotSignature.lastIndexOf('_', iParen-1);
if (iLastUnderscore > 3)
qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
}
}
}
核心逻辑是遍历子对象,搜索子对象所引发的信号,通过on_控件名(ID)_信号名 做匹配,匹配上了就使用connect( )连接。
小结一下:setUi( )
实现了窗体上组件的创建、属性设置、信号与槽的关联。
- 最后是一个命名空间
Ui
。
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
可以发现里面有一个 MainWindow
类继承自 Ui_MainWindow
。
而我们 mainwindow.h
中也有一个MainWindow
类,只是在 mainwindow.h
中,为声明。
这样我们就可以在MainWindow
类里用 ui 指针
访问可视化设计的界面组件。