Qt入门 - UI布局及控件

前言

鉴于之前学习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 指针 访问可视化设计的界面组件。

猜你喜欢

转载自blog.csdn.net/weixin_40774605/article/details/105149204