Qt 常用控件 - 2

发布于:2025-07-26 ⋅ 阅读:(18) ⋅ 点赞:(0)

Qt 常用控件 - 1 https://blog.csdn.net/Small_entreprene/article/details/149574348?fromshare=blogdetail&sharetype=blogdetail&sharerId=149574348&sharerefer=PC&sharesource=Small_entreprene&sharefrom=from_link

Geometry(几何)

位置和尺寸实际上是四个属性的统称:

  • x:横坐标

  • y:纵坐标

  • width:宽度

  • height:高度

但在实际开发中,我们通常不会直接使用这些属性,而是通过一系列封装好的方法来获取或修改。对于 Qt 的坐标系,需要注意它是一个“左手坐标系”,其中坐标系的原点是当前元素的父元素的左上角。

API 说明
  • geometry():获取控件的位置和尺寸,返回结果是一个 QRect(矩形),包含 x、y、width 和 height 属性,其中 x 和 y 是左上角的坐标。

  • setGeometry(QRect)setGeometry(int x, int y, int width, int height):设置控件的位置和尺寸,可以直接设置一个 QRect,也可以分四个属性单独设置。move只是修改位置,即x,y

Qt 中针对一些几何上的概念也进行了封装:

QPoint:表示一个点;QRect:表示一个矩形;这些属于是小对象。里面的属性非常少,占用控件比较小。C++使用上述对象,通常就会按照值得方式来进行参数得传递!未必要使用指针或引用,又不大!拷贝开销也不大!

代码示例:控制按钮的位置
  1. 在界面中拖入五个按钮,其 objectName 分别为 pushButton_targetpushButton_uppushButton_downpushButton_leftpushButton_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,其 objectName 分别为 pushButton_acceptpushButton_rejectlabel,控件中文本如下图所示。

  2. widget.cpp 中添加 slot 函数:

    void Widget::on_pushButton_accept_clicked()
    {
        ui->label->setText("女神快来嘴一个! mua~~");
    }
    
    void Widget::on_pushButton_reject_pressed()
    {
        // 获取窗口的宽度和高度
        int width = this->geometry().width();
        int height = this->geometry().height();
        // 重新生成按钮的位置
        int x = rand() % width;
        int y = rand() % height;
        // 设置新的位置
        ui->pushButton_reject->move(x, y);
    }

运行程序后,当点击“残忍拒绝”时,按钮会跑开。上述代码使用的是 pressed(鼠标按下事件)。如果使用 mouseMoveEvent,效果会更强烈,只要鼠标移动过来,按钮就跑开。不过对应的代码会更复杂(需要自定义类继承自 QPushButton,重写 mouseMoveEvent 方法),此处暂不展开。

Window Frame (窗口框架)的影响

如果 widget 作为一个窗口(带有标题栏、最小化、最大化、关闭按钮),那么在计算尺寸和坐标时有两种算法:包含 window frame 和不包含 window frame。

其中 x()y()frameGeometry()pos()move() 都是按照包含 window frame 的方式来计算的;而 geometry()width()height()rect()size() 则是按照不包含 window frame 的方式来计算的。当然,如果一个 widget 不是作为窗口,上述两类方式得到的结果是一致的。

相关 API
API 说明
x() 获取横坐标,计算时包含 window frame
y() 获取纵坐标,计算时包含 window frame
pos() 返回 QPoint 对象,其中包含 x()y()setX()setY() 等方法,计算时包含 window frame
frameSize() 返回 QSize 对象,其中包含 width()height()setWidth()setHeight() 等方法,计算时包含 window frame
frameGeometry() 返回 QRect 对象,QRect 相当于 QPoint 和 QSize 的结合体,可以获取 x、y、width、size,计算时包含 window frame 对象
width() 获取宽度,计算时不包含 window frame
height() 获取高度,计算时不包含 window frame
size() 返回 QSize 对象,其中包含 width()height()setWidth()setHeight() 等方法,计算时不包含 window frame
rect() 返回 QRect 对象,QRect 相当于 QPoint 和 QSize 的结合体,可以获取并设置 x、y、width、size,计算时不包含 window frame 对象
geometry() 返回 QRect 对象,QRect 相当于 QPoint 和 QSize 的结合体,可以获取 x、y、width、size,计算时不包含 window frame 对象
setGeometry() 直接设置窗口的位置和尺寸,可以设置 x、y、width、height 或者 QRect 对象,计算时不包含 window frame 对象

在 Qt 中,关于位置尺寸 ,提供了很多的 API。

  • 有的 API 的位置信息是以 Widget 本体左上角为原点的(不考虑 Window frame)
  • 有的 API 的位置信息是以 Window frame 左上角为原点的

认真观察上面的表格,其实这里的 API 中有 frameGeometrygeometry 两个就足够完成所有需求了。为什么要提供这么多功能重复的 API 呢?这涉及到 Qt API 的设计理念:尽量符合人的直觉。例如,Qt 的 QVector 中,尾插元素操作有以下方法:push_backappend+=<<,这些方法的效果都是等价的,即使不翻阅文档,单纯凭借直觉也能把代码写对。

代码示例:感受 geometry 和 frameGeometry 的区别

在界面上放置一个按钮。

在按钮的 slot 函数中编写代码:

void Widget::on_pushButton_clicked()
{
    QRect rect1 = this->geometry();
    QRect rect2 = this->frameGeometry();
    qDebug() << rect1;
    qDebug() << rect2;
}

 执行程序后,可以看到在构造函数中,打印出的 geometryframeGeometry 是相同的;

QRect(0,0 800x600)
QRect(0,0 800x600)

我们是在构造函数中打印日志的,Window Frame 就是构造后添加到对象树的过程中所涉及到的一个关键环节!! 此时还看不出双方的差异,就需要加到更迟一点的环节!--- 第二步

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // //此处直接针对Widget对象,使用geometry和framegeometry
    // QRect rect1 = this->geometry();
    // QRect rect2 = this->frameGeometry();

    // qDebug()<<rect1;
    // qDebug()<<rect2;
    
    QPushButton* button  = new QPushButton(this);
    button->setText("按钮");
    button->move(100,100);
    
    connect(button,&QPushButton::clicked,this,&Widget::handle);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handle()
{
    //此处直接针对Widget对象,使用geometry和framegeometry
    QRect rect1 = this->geometry();
    QRect rect2 = this->frameGeometry();
    
    qDebug()<<rect1;
    qDebug()<<rect2;
}

我们创建按钮,点击按钮时,打印的 geometryframeGeometry 则存在差异。--- 第三步

注意

在构造方法中,Widget 刚刚创建出来,还没有加入到对象树中,此时也不具备 Window frame。在按钮的 slot 函数中,由于用户点击的时候,对象树已经构造好了,此时 Widget 已经具备了 Window frame,因此在位置和尺寸上均出现了差异。如果把上述代码修改成打印 pushButtongeometryframeGeometry,结果就是完全相同的,因为 pushButton 并非是一个窗口。

windowTitle(窗口标题)

API 说明
  • windowTitle():获取控件的窗口标题。

  • setWindowTitle(const QString& title):设置控件的窗口标题。

注意:上述设置操作针对不同的 widget 可能会有不同的行为。当前 windowTitle 属性,是从属于 QWidget的,QWidget 是一个广泛的概念,一个按钮,一个输入框就是一个 QWidget 了,如果是顶层 widget(独立窗口),这个操作才会有效;如果是子 widget,这个操作没有任何效果。

代码示例:设置窗口标题 修改 widget.cpp
1 Widget::Widget(QWidget *parent)
2 : QWidget(parent)
3 , ui(new Ui::Widget)
4 {
5 ui->setupUi(this);
6
7 // 设置窗口标题
8 this->setWindowTitle("这是窗口标题");
9 }

执行效果:窗口标题被设置为“这是窗口标题”。

我们可以继续创建一个按钮控件:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->setWindowTitle("这是窗口标题");
    
    QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    button->setWindowTitle("通过按钮设置窗口标题");
}

很明显,按钮对窗口标题的设置是没有影响的!

不过没有报错?其实这个设定是不太科学的!这一点,我们更希望当写不不太科学的代码的时候,能够给一些报错提示!

windowIcon(窗口图标)

我们运行一个程序,在窗口和任务栏中就会有相关的图标:不同的程序图标不同:

API 说明
  • windowIcon():获取控件的窗口图标,返回 QIcon 对象。

  • setWindowIcon(const QIcon& icon):设置控件的窗口图标。

注意:这两个 API 同 windowTitle,上述操作仅针对顶层 widget 有效。

代码示例:设置窗口图标
  1. 先在 D 盘中放一个图片,名字为 rose.png
     

  2. 修改 widget.cpp

#include <QIcon>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置图标
    // "D:\\for_test\\rose.png" 
    QIcon icon("D:/for_test/rose.png");//路径不要带中文
    //C++11引入了 raw string 解决该问题 --- 字符串里不包含任何转义字符(所有的字符都不会转义)
    //r("D:\for_test\rose.png")
    
    this->setWindowIcon(icon);
}

  1. 注意:Windows 下路径的分隔符可以使用 / 也可以使用 \,但是如果在字符串中使用 \,需要写作转义字符的形式 \\。因此更推荐使用 /。另外,C++11引入了 raw string 解决该问题 --- 字符串里不包含任何转义字符(所有的字符都不会转义)--- 用法:

    r("D:\for_test\rose.png")
  2. 运行程序,可以看到窗口图标已经成为上述图片,同时程序在任务栏中的图标也发生了改变。

  3. Qt 将各种涉及到的相关概念,都封装成了类,QIcon 就表示一个图标。

  4. 我们之前推荐使用堆去创建对象,主要因为要通过 Qt 对象树来释放对象,确保当前控件的生命周期是足够的!QIcon 自身是比较小的对象,创建出来之后,就是要设置到某一个 QWidget 里面,QIcon 对象本身释放不释放,不影响图标最终的显示 -- 可以看成就是给某一个函数调用传参数的参数作用。而且 QIcon 也不支持对象树机制!

实际开发中的注意事项
  • 一般不会在代码中通过绝对路径引入图片,因为无法保证程序发布后,用户的电脑上也有同样的路径。--- 说不定有的没有D盘 --- 不要写死了

  • 如果使用相对路径,则需要确保代码中的相对路径写法和图片实际所在的路径匹配(比如代码中写作 "./image/rose.jpg",就需要在当前工作目录中创建 image 目录,并把 rose.png 放进去)。

  • 绝对路径:以盘符(Windows)或者以 /(Linux)开头的路径。

  • 相对路径:以 .(表示当前路径)或者以 ..(表示当前路径上级路径)开头的路径。其中 . 经常也会省略。相对路径的前提是需要明确“当前工作目录”。

  • 对于 Qt 程序来说,当前工作目录可能是变化的。比如通过 Qt Creator 运行的程序,当前工作目录是项目的构建目录;直接双击 exe 运行,工作目录则是 exe 所在目录。

  • 所谓构建目录,是和 Qt 项目并列的,专门用来放生成的临时文件和最终 exe 的目录。

代码示例:获取当前的工作目录
  1. 在界面上创建一个比较大的 label,确保能把路径显示完整。objectName 使用默认的 label 即可。
  2. 修改 widget.cpp
    #include <QDir>
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
        // 获取到当前工作目录
        QString currentDir = QDir::currentPath();
        // 设置工作目录到 label 中
        ui->label->setText(currentDir);
    }
  3. 直接在 Qt Creator 中执行程序,可以看到当前工作目录是项目的构建目录。

  4. 进入上述构建目录,把里面的 exe 拷贝到其他目录中(比如 D: 中)。再次执行程序,可以看到当前工作目录已经发生改变。

  5. 要想直接能双击 exe 运行,需要先把 Qt 的路径添加到 path 环境变量中,否则会提示找不到动态库。这一点在最开始搭建开发环境的时候已经操作过,此处不再赘述。
  6. 注意,上述构建目录是随时可删除的。比如点击菜单栏中的“构建”->“清理项目”,就会把这个目录中的内容清空掉。因此如果把图片文件放到构建目录中,可能在不小心删除后就丢失了。我们还是希望能够把图片和源代码放在一起,并且使我们的程序无论拷贝到任何位置中都能正确使用图片。
Qt 的 qrc 资源管理机制

通过 qrc 机制,可以重根本上解决两个问题:

1. 确保图片所在的路径在目标用户机器上存在。

2. 确保图片不会被用户搞没了。

方式:给 Qt 项目中引入一个额外的 xml 文件(后缀使用 .qrc 表示),在这个 xml 中,把要使用的图片资源给导入进来,并且在 xml 中进行记录!

Qt 在编译项目的时候,就会根据 qrc 中的描述的图片信息,找到图片内容,并且提取出图片的二进制数据,把这些二进制数据转化成 C++ 代码,最终编译到 exe 中!

缺点就是 qrc 无法导入太大的资源文件,比如搞几个 GB 的视频文件,qrc 就无能力了!

  • Qt 使用 qrc 机制帮我们自动完成了上述工作,更方便地来管理项目依赖的静态资源。

  • qrc 文件:是一种 XML 格式的资源配置文件,它用 XML 记录硬盘上的文件和对应的随意指定的资源名称。应用程序通过资源名称来访问这些资源。

  • 在 Qt 开发中,可以通过将资源文件添加到项目中来方便地访问和管理这些资源。这些资源文件可以位于 qrc 文件所在目录的同级或其子目录下。

  • 在构建程序的过程中,Qt 会把资源文件的二进制数据转成 cpp 代码,编译到 exe 中。从而使依赖的资源变得“路径无关”。

  • 这种资源管理机制并非 Qt 独有,很多开发框架都有类似的机制。例如 Android 的 Resources 和 AssetManager 也是类似的效果。

代码示例:通过 qrc 管理图片作为图标

  1. 右键项目,创建一个 Qt Resource File(qrc 文件),文件名随意起(不要带中文),此处叫做 resource.qrc

  2. 在 qrc 编辑器中,添加前缀。此处我们前缀设置成 / 即可。所谓的前缀,可以理解成“虚拟目录”(没有在我们的电脑中真实存在,是 Qt 抽象出来的)。这个前缀决定了后续我们如何在代码中访问资源。

  3. 在资源编辑器中,点击 add Files 添加资源文件。此处我们需要添加的是 rose.png注意:添加的文件必须是在 qrc 文件的同级目录,或者同级目录的子目录中。因此需要把之前 D 盘中的 rose.png 复制到上述目录中。添加完毕后,可以在资源编辑器中看到添加好的文件。

  4. 在代码中使用 rose.png

#include <QIcon>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置图标
    
    QIcon icon(":/rose.png");

    this->setWindowIcon(icon);
}

注意上述路径的访问规则:

  • 使用 : 作为开头,表示从 qrc 中读取资源。

  • / 是上面配置的前缀。

  • rose.jpg 是资源的名称。

  • 需要确保代码中编写的路径和添加到 qrc 中资源的路径匹配。否则资源无法被访问(同时也不会有报错提示)。

运行程序,可以看到图标已经能正确设置。

接下来,可以进入到项目的构建目录,可以看到目录中多了一个 qrc_resource.cpp 文件。直接打开这个文件,可以看到类似如下代码:

/****************************************************************************
** Resource object code
**
** Created by: The Resource Compiler for Qt version 6.9.0
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#ifdef _MSC_VER
// disable informational message "function ... selected for automatic inline expansion"
#pragma warning (disable: 4711)
#endif

static const unsigned char qt_resource_data[] = {
  // rose.png
  0x0,0x13,0x78,0xfb,
  0x89,
  0x50,0x4e,0x47,0xd,0xa,0x1a,0xa,0x0,0x0,0x0,0xd,0x49,0x48,0x44,0x52,0x0,
  0x0,0x5,0x30,0x0,0x0,0x5,0x30,0x8,0x2,0x0,0x0,0x0,0xfb,0xf,0xed,0x35,
  0x0,0x0,0x0,0x5b,0x65,0x58,0x49,0x66,0x4d,0x4d,0x0,0x2a,0x0,0x0,0x0,0x8,
  0x0,0x1,0x1,0x3b,0x0,0x2,0x0,0x0,0x0,0x41,0x0,0x0,0x0,0x1a,0x0,0x0,
  0x0,0x0,0x61,0x32,0x34,0x63,0x37,0x63,0x35,0x63,0x62,0x36,0x30,0x62,0x62,0x37,
  0x66,0x36,0x30,0x39,0x32,0x61,0x62,0x35,0x62,0x61,0x36,0x37,0x37,0x66,0x34,0x61,
  0x30,0x31,0x66,0x33,0x66,0x36,0x63,0x33,0x34,0x38,0x62,0x36,0x39,0x62,0x66,0x31,
  0x62,0x35,0x31,0x65,0x35,0x38,0x65,0x63,0x63,0x31,0x37,0x66,0x39,0x61,0x61,0x32,
  0x33,0x33,0x0,0x1b,0x21,0xc5,0xce,0x0,0x0,0x20,0x0,0x49,0x44,0x41,0x54,0x78,
  0x9c,0x94,0xbd,0x4b,0x96,0x24,0x3b,0xe,0x2c,0x66,0x6,0x8f,0xea,0xf7,0x56,0xa7,
  0xad,0x68,0xac,0xa5,0x68,0xa0,0xad,0x49,0xab,0xd0,0xe0,0xdd,0x4a,0x87,0x69,0x80,
  0xf,0x41,0xba,0x47,0xde,0x96,0x9f,0xee,0xba,0x91,0x11,0x74,0x12,0xc4,0x9f,0x4,
  0x8,0xf2,0xff,0xfa,0x7f,0xfe,0x5f,0x0,0x4e,0x48,0xfa,0xf1,0x5b,0x12,0x79,0x49,
  // .............
};
#ifdef QT_NAMESPACE
#  define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name
#  define QT_RCC_MANGLE_NAMESPACE0(x) x
#  define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b
#  define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)
#  define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \
        QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))
#else
#   define QT_RCC_PREPEND_NAMESPACE(name) name
#   define QT_RCC_MANGLE_NAMESPACE(name) name
#endif

#if defined(QT_INLINE_NAMESPACE)
inline namespace QT_NAMESPACE {
#elif defined(QT_NAMESPACE)
namespace QT_NAMESPACE {
#endif

bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);

#ifdef QT_NAMESPACE
}
#endif

int QT_RCC_MANGLE_NAMESPACE(qInitResources_resource)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources_resource)()
{
    int version = 3;
    QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData)
        (version, qt_resource_struct, qt_resource_name, qt_resource_data);
    return 1;
}

int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_resource)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_resource)()
{
    int version = 3;
    QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData)
       (version, qt_resource_struct, qt_resource_name, qt_resource_data);
    return 1;
}

#ifdef __clang__
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wexit-time-destructors"
#endif

namespace {
   struct initializer {
       initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources_resource)(); }
       ~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_resource)(); }
   } dummy;
}

#ifdef __clang__
#   pragma clang diagnostic pop
#endif

上述代码其实就是通过 unsigned char 数组,把 rose.png 中的每个字节都记录下来。这些代码会被编译到 exe 中。后续无论 exe 被复制到哪个目录下,都确保能够访问到该图片资源。

qrc 资源管理方案的优缺点

  • 优点:确保了图片、字体、声音等资源能够真正做到“目录无关”,无论如何都不会出现资源丢失的情况。

  • 缺点:不适合管理体积大的资源。如果资源比较大(比如是几个 MB 的文件),或者资源特别多,生成的最终的 exe 体积就会比较大,程序运行消耗的内存也会增大,程序编译的时间也会显著增加。




下一篇更精彩😝😝😝😝😝😝

下一篇更精彩😝😝😝😝😝😝

下一篇更精彩😝😝😝😝😝😝


网站公告

今日签到

点亮在社区的每一天
去签到