Qt 传统窗口调整技术

Qt 传统窗口调整技术

QWidget 中与控件/窗口大小相关

大小设置相关函数

函数名 参数 作用 是否允许用户调整大小
setFixedSize(int w, int h) / setFixedSize(const QSize &) 宽度、高度 设置固定大小 ❌ 不允许
resize(int w, int h) / resize(const QSize &) 宽度、高度 设置当前大小 ✅ 允许
setMinimumSize(int w, int h) / setMinimumSize(const QSize &) 最小宽度、高度 设置最小尺寸限制 ✅ 允许
setMaximumSize(int w, int h) / setMaximumSize(const QSize &) 最大宽度、高度 设置最大尺寸限制 ✅ 允许
setGeometry(int x, int y, int w, int h) 坐标和宽高 设置位置和大小 ✅ 允许
adjustSize() 根据内容自动调整控件大小(调用 resize(sizeHint()) ✅ 允许

大小获取函数

函数名 返回值类型 作用
size() QSize 获取当前控件大小
width() / height() int 获取当前宽度或高度
minimumSize() / maximumSize() QSize 获取最小/最大尺寸
geometry() QRect 获取控件的矩形区域(位置+尺寸)

推荐尺寸(用于布局管理器)

函数名 返回值类型 作用 备注
sizeHint() QSize 返回推荐的大小 可重写
minimumSizeHint() QSize 返回推荐的最小大小 默认为 (0,0),可重写

手动计算调整控件分布

widget.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>472</width>
    <height>473</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>0</width>
    <height>0</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>Widget</string>
  </property>
  <widget class="QSlider" name="horizontalSlider">
   <property name="geometry">
    <rect>
     <x>160</x>
     <y>420</y>
     <width>231</width>
     <height>21</height>
    </rect>
   </property>
   <property name="orientation">
    <enum>Qt::Orientation::Horizontal</enum>
   </property>
  </widget>
  <widget class="QLabel" name="labelShow">
   <property name="geometry">
    <rect>
     <x>40</x>
     <y>10</y>
     <width>400</width>
     <height>300</height>
    </rect>
   </property>
   <property name="text">
    <string/>
   </property>
  </widget>
  <widget class="QPushButton" name="btnOpenPic">
   <property name="geometry">
    <rect>
     <x>10</x>
     <y>380</y>
     <width>75</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>打开图片</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btnOpenMov">
   <property name="geometry">
    <rect>
     <x>80</x>
     <y>380</y>
     <width>102</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>打开动态图</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btnStart">
   <property name="geometry">
    <rect>
     <x>200</x>
     <y>380</y>
     <width>91</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>播放</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btnStop">
   <property name="geometry">
    <rect>
     <x>330</x>
     <y>380</y>
     <width>91</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>停止</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QImageReader> // 用于读取图像(错误类型也在此)
#include <QMovie>       // 用于播放动态图(如 GIF)
#include <QPixmap>      // 用于显示静态图像
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

public:
    explicit Widget(QWidget* parent = nullptr);
    ~Widget();

public slots:
    // 动图播放错误的槽函数,连接 QMovie::error 信号
    void recvPlayError(QImageReader::ImageReaderError error);

    // 帧变化的槽函数,用于更新进度条,连接 QMovie::frameChanged 信号
    void recvFrameNumber(int frameNumber);

private slots:
    // UI 按钮点击的槽函数(自动连接)
    void on_btnOpenPic_clicked(); // 打开静态图按钮
    void on_btnOpenMov_clicked(); // 打开动态图按钮
    void on_btnStart_clicked();   // 播放按钮
    void on_btnStop_clicked();    // 停止按钮

private:
    Ui::Widget* ui; // 指向 UI 界面类的指针,自动生成并管理所有控件

    // 用于显示静态图的指针(建议用智能指针管理)
    QPixmap* pixmap = nullptr;

    // 用于播放动态图的指针(如 gif/mng)
    QMovie* movie = nullptr;

    // 标志位:当前是否为动态图(true 表示是 QMovie)
    bool isMovie = false;

    // 标志位:动态图是否正在播放
    bool isPlaying = false;

    // 清除上一个图像或动画的辅助函数,释放资源并重置状态
    void clearOldShow();

    // QWidget interface
protected:
    void resizeEvent(QResizeEvent* event) override;
};

#endif // WIDGET_H
  • virtual void resizeEvent(QResizeEvent* event);QWidget 中的一个虚函数,用于处理 窗口或控件大小变化时的事件

  • 该事件对象提供了控件新旧尺寸的信息:

函数 作用
event->size() 返回新的大小 (QSize)
event->oldSize() 返回旧的大小 (QSize)
  • 注意事项

  • 重写时不要忘记调用 QWidget::resizeEvent(event);,除非你完全取代默认行为

  • 如果控件用布局管理器(如 QVBoxLayoutQGridLayout),一般不需要手动处理大小变化。

  • C++11 开始,加入 override 有个好处:如果写错函数签名、参数、拼写等,没有 override 就不会报错,导致函数不会被调用。

widget.cpp

#include "widget.h"
#include "./ui_widget.h"
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QResizeEvent>
#include <QScrollArea>

// 构造函数:初始化 UI 并用 QScrollArea 包装 label 实现滚动支持
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);

    // 获取 labelShow 的原始几何尺寸
    // 表示该控件相对于其父控件的几何信息(位置 + 大小)
    QRect rcLabel = ui->labelShow->geometry();

    // 创建一个滚动区域并将 labelShow 设置为其子控件
    QScrollArea* scrollArea = new QScrollArea(this);
    // 把 labelShow 这个 QLabel 设置为 scrollArea(滚动区域)的子控件。
    // scrollArea 会自动显示 labelShow 的内容;
    // 如果 labelShow 太大(超出 scrollArea 大小),scrollArea 会提供滚动条;
    // scrollArea 会接管 labelShow 的父对象和显示逻辑。
    scrollArea->setWidget(ui->labelShow);
    // 将 scrollArea 的大小和位置设置为 labelShow 原本在窗口中的位置和大小
    scrollArea->setGeometry(rcLabel);

    // 输出当前支持的图片与动画格式
    qDebug() << QImageReader::supportedImageFormats();
    qDebug() << QMovie::supportedFormats();

    // 设置主界面窗体最小尺寸
    this->setMinimumSize(350, 350);
}

// 析构函数:释放资源
Widget::~Widget() {
    clearOldShow(); // 清除旧显示内容(手动释放 pixmap 或 movie)
    delete ui;
}

// 清除旧的图像/动画内容,释放内存
void Widget::clearOldShow() {
    ui->labelShow->clear(); // 清空显示

    // 释放静态图资源
    if (pixmap != nullptr) {
        delete pixmap;
        pixmap = nullptr;
    }

    // 释放动态图资源
    if (movie != nullptr) {
        if (isPlaying) movie->stop(); // 正在播放先停止
        delete movie;
        movie = nullptr;
    }

    isMovie = false;
    isPlaying = false;
}

// 点击“打开图片”按钮后的响应槽函数
void Widget::on_btnOpenPic_clicked() {
    QString fileName;

    // 弹出文件选择对话框
    fileName = QFileDialog::getOpenFileName(this, tr("打开静态图"), "", "Pictures (*.bmp *.jpg *.jpeg *.png *.xpm);;All files(*)");
    if (fileName.isEmpty()) return;

    clearOldShow(); // 释放上一个资源
    qDebug() << fileName;

    pixmap = new QPixmap();
    if (pixmap->load(fileName)) {
        ui->labelShow->setPixmap(*pixmap);          // 设置图像
        ui->labelShow->setGeometry(pixmap->rect()); // 自动调整 label 大小
        isMovie = false;
        isPlaying = false;
    } else {
        delete pixmap;
        pixmap = nullptr;
        QMessageBox::critical(this, tr("打开失败"), tr("打开图片失败,文件名为:\r\n%1").arg(fileName));
    }
}

// 点击“打开动画”按钮后的响应槽函数
void Widget::on_btnOpenMov_clicked() {
    QString fileName;

    // 弹出文件选择对话框
    fileName = QFileDialog::getOpenFileName(this, tr("打开动态图"), "", "Movies (*.gif *.mng);;All files(*)");
    if (fileName.isEmpty()) return;

    clearOldShow(); // 清除旧资源
    qDebug() << fileName;

    movie = new QMovie(fileName);
    if (!movie->isValid()) {
        QMessageBox::critical(this, tr("动态图格式不可用"), tr("动态图格式不支持或读取出错,文件名为:\r\n%1").arg(fileName));
        delete movie;
        movie = nullptr;
        return;
    }

    // 获取总帧数(有些 gif 不支持,返回 -1)
    int count = movie->frameCount();
    qDebug() << tr("总帧数:%1").arg(count);

    // 设置滑动条最大值(回退默认值 100 以支持未知帧数)
    ui->horizontalSlider->setMaximum(count > 0 ? count : 100);

    // 设置动画显示到 label 中
    ui->labelShow->setMovie(movie);
    isMovie = true;
    isPlaying = false;

    // 使用新信号槽语法连接错误处理与帧更新
    connect(movie, &QMovie::error, this, &Widget::recvPlayError);
    connect(movie, &QMovie::frameChanged, this, &Widget::recvFrameNumber);

    // 跳转到第一帧并设置 label 大小
    if (movie->jumpToFrame(0)) {
        ui->labelShow->setGeometry(movie->frameRect());
    }
}

// “播放”按钮点击槽函数
void Widget::on_btnStart_clicked() {
    if (!isMovie || isPlaying) return; // 非动画或已经在播放
    isPlaying = true;
    movie->start();
    qDebug() << tr("循环计数:%1").arg(movie->loopCount());
}

// “暂停”按钮点击槽函数
void Widget::on_btnStop_clicked() {
    if (!isMovie || !isPlaying) return;
    isPlaying = false;
    movie->stop();
}

// 动画播放出错时的处理槽函数
void Widget::recvPlayError(QImageReader::ImageReaderError error) {
    qDebug() << tr("读取动态图错误的代码:%1").arg(error);
    QMessageBox::critical(this, tr("播放出错"), tr("播放动态图出错,文件名为:\r\n%1").arg(movie->fileName()));
    isPlaying = false;
}

// 每一帧更新时,设置滑动条位置
void Widget::recvFrameNumber(int frameNumber) {
    ui->horizontalSlider->setValue(frameNumber);
}

// 重写
void Widget::resizeEvent(QResizeEvent* event) {
    int W = event->size().width();
    int H = event->size().height();

    // 先计算第二行四个按钮的左上角坐标,按钮尺寸固定为 75*23
    // 第一个按钮
    int x1 = 10;                    // 左边距 10
    int y1 = H - 10 - 21 - 10 - 23; // 10 都是间隔,21 是水平滑动条高度,23 是按钮高度
    // 第四个按钮
    int x4 = W - 10 - 75; // 10 是右边距,75 是按钮宽度
    int y4 = y1;          // 与第一个按钮同一水平线
    // 计算四个按钮的三个间隙总大小
    int nTriGap = W - 10 - 10 - 75 * 4;
    // 计算单个间隙
    int nGap = nTriGap / 3;
    // 计算第二个按钮坐标
    int x2 = x1 + 75 + nGap;
    int y2 = y1;
    // 计算第三个按钮左边
    int x3 = x4 - 75 - nGap;
    int y3 = y1;

    // 设置四个按钮的矩形
    ui->btnOpenPic->setGeometry(x1, y1, 75, 23);
    ui->btnOpenMov->setGeometry(x2, y2, 75, 23);
    ui->btnStart->setGeometry(x3, y3, 75, 23);
    ui->btnStop->setGeometry(x4, y4, 75, 23);

    // 计算第三行水平滑动条的坐标和尺寸
    int xSlider = x2;
    int ySlider = H - 10 - 21;
    int wSlider = W - x2 - 10;
    int hSlider = 21;
    // 设置水平滑动条的矩形
    ui->horizontalSlider->setGeometry(xSlider, ySlider, wSlider, hSlider);

    // 计算包裹标签的滚动区域占用的矩形
    int xLabel = 10;
    int yLabel = 10;
    int wLabel = W - 10 - 10;
    int hLabel = H - 10 - 21 - 10 - 23 - 10 - 10;
    // 设置包裹标签的滚动区域矩形
    QScrollArea* pSA = this->findChild<QScrollArea*>(); // 查找子对象
    // 如果 pSA 不为 nullptr 才能设置矩形
    if (pSA != nullptr) pSA->setGeometry(xLabel, yLabel, wLabel, hLabel);
}

计算文本显示宽度

QWidget::adjustSize()

QWidget::adjustSize() 会根据控件内容(比如子控件的大小、布局、字体等)自动调整当前控件的大小,以“刚好能包住内容”为目标。

QLabel* label = new QLabel(this);
label->setText("动态内容");
label->adjustSize();  // 让 QLabel 自动变大以显示完整内容

adjustSize() 实际上是调用 sizeHint() 获取推荐尺寸,然后调用 resize(sizeHint())

void QWidget::adjustSize() {
    resize(sizeHint());
}

如果你重写了 sizeHint(),它就会使用你提供的尺寸。

QFontMetrics

QFontMetrics 是 Qt 中用于 测量字体相关尺寸信息 的类,主要功能是根据某个字体 QFont计算文本的大小、行高、对齐、间距等信息。它对布局、自定义绘制控件、精确控制文本位置等非常重要。

常用构造方式
QFont font("Microsoft YaHei", 12);
QFontMetrics metrics(font);

也可以在 widget 的 paintEvent 中直接使用当前字体:

QFontMetrics metrics(this->font());
常用函数速查表
函数名 返回类型 说明
width(const QString &text)(Qt5)horizontalAdvance(const QString &text)(Qt6) int 获取字符串的水平宽度(像素)
height() int 获取总高度(行高)
ascent() int 获取字体基线以上的高度
descent() int 获取字体基线以下的高度
leading() int 获取行间距(行与行之间的额外空间)
boundingRect(const QString &text) QRect 返回包围文字的矩形区域
elidedText(text, mode, width) QString 超出宽度时加省略号(...)的文本

示例:计算字符串宽度并设置 QLabel 宽度

QLabel* label = new QLabel("Hello Qt");
QFontMetrics metrics(label->font());
int width = metrics.horizontalAdvance("Hello Qt");
label->setFixedWidth(width);  // 宽度正好包住文字
QFontMetrics vs QFontMetricsF
  • QFontMetrics 返回的是整数,精度较低,常用于普通控件;

  • QFontMetricsF 返回的是浮点数,适用于高精度绘制(比如自定义 OpenGL 渲染控件)。

QFontMetrics::size()

QSize QFontMetrics::size(int flags, const QString &text) const;

返回给定 text 在指定绘图标志(flags)下所需的尺寸(QSize),适用于多行文本和复杂格式的测量

这比 horizontalAdvance() 更通用(后者只测一行宽度),支持多行、对齐方式等更多排版控制

参数 含义说明
flags Qt::TextFlag 类型的标志位,例如对齐方式、是否换行等
text 要计算的文本内容

常用 flags 值有:

  • Qt::TextSingleLine:单行显示(默认不换行)
  • Qt::TextWordWrap:自动换行
  • Qt::AlignLeft, Qt::AlignCenter, Qt::AlignRight:对齐方式
示例代码
QFont font("Microsoft YaHei", 12);
QFontMetrics metrics(font);

QString text = "This is a\nmulti-line text.";
QSize size = metrics.size(Qt::TextWordWrap, text);

qDebug() << "宽度:" << size.width() << "高度:" << size.height();

示例

widget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>391</width>
    <height>267</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Widget</string>
  </property>
  <widget class="QLabel" name="label_1">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>30</y>
     <width>53</width>
     <height>24</height>
    </rect>
   </property>
   <property name="text">
    <string>按钮文本</string>
   </property>
  </widget>
  <widget class="QLabel" name="label_2">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>80</y>
     <width>53</width>
     <height>24</height>
    </rect>
   </property>
   <property name="text">
    <string>动态按钮</string>
   </property>
  </widget>
  <widget class="QLabel" name="label_3">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>130</y>
     <width>53</width>
     <height>24</height>
    </rect>
   </property>
   <property name="text">
    <string>固定按钮</string>
   </property>
  </widget>
  <widget class="QLineEdit" name="lineEdit">
   <property name="geometry">
    <rect>
     <x>100</x>
     <y>30</y>
     <width>113</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QPushButton" name="btnDynamic">
   <property name="geometry">
    <rect>
     <x>110</x>
     <y>80</y>
     <width>75</width>
     <height>24</height>
    </rect>
   </property>
   <property name="text">
    <string>动态</string>
   </property>
  </widget>
  <widget class="QPushButton" name="btnFixed">
   <property name="geometry">
    <rect>
     <x>120</x>
     <y>130</y>
     <width>75</width>
     <height>24</height>
    </rect>
   </property>
   <property name="text">
    <string>固定</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

  public:
    Widget(QWidget* parent = nullptr);
    ~Widget();

  private slots:
    void on_lineEdit_textEdited(const QString& arg1);

  private:
    Ui::Widget* ui;

    // QWidget interface
  protected:
    void resizeEvent(QResizeEvent* event) override;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QResizeEvent>

Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
    // 设置窗口的最小尺寸,确保内容不会被挤压得太小
    // 计算最小宽度:10(左边距) + 54(标签宽度) + 10(间隙) + 75(第三行按钮宽度) + 10(右边距)
    // 计算最小高度:24(单行控件高度) * 3(行数) + 10(上下间隙) * 4(行间及边距数量)
    this->setMinimumSize(10 + 54 + 10 + 75 + 10, 24 * 3 + 10 * 4);
}

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

void Widget::on_lineEdit_textEdited(const QString& arg1) {
    QFontMetrics fm = ui->btnDynamic->fontMetrics();

    // 计算文本的宽度(像素)
    int nTextWidth = fm.horizontalAdvance(arg1);
    // 获取动态按钮当前大小
    QSize szButtonDynamic = ui->btnDynamic->size();
    // 按文本宽度调整按钮宽度,增加10像素边距留白
    szButtonDynamic.setWidth(nTextWidth + 10);
    ui->btnDynamic->resize(szButtonDynamic);
    // 设置按钮文本和提示内容
    ui->btnDynamic->setText(arg1);
    ui->btnDynamic->setToolTip(arg1);

    // 固定按钮文本处理:如果文本宽度小于等于65,直接显示
    if (nTextWidth <= 65) {
        ui->btnFixed->setText(arg1);
    } else {
        // 否则截断文本并加省略号,确保按钮宽度不超过65像素
        QString strPart;
        QString strDot = "...";
        int nStrLen = arg1.length();
        int nNewTextWidth = 0;
        for (int i = 0; i < nStrLen; i++) {
            strPart += arg1[i];
            nNewTextWidth = fm.horizontalAdvance(strPart + strDot);
            if (nNewTextWidth >= 65) break;
        }
        ui->btnFixed->setText(strPart + strDot);
    }
    ui->btnFixed->setToolTip(arg1);
}

void Widget::resizeEvent(QResizeEvent* event) {
    // 获取窗口当前宽度和高度
    int W = event->size().width();
    int H = event->size().height();

    // 第一行控件布局
    // 标签1 固定大小54x24,垂直居中在窗口的1/4高度处
    int xLabel1 = 10;         // 左边距10像素
    int yLabel1 = H / 4 - 12; // 标签垂直居中,24高度减半为12
    ui->label_1->move(xLabel1, yLabel1);

    // 单行编辑控件布局
    int xLineEdit = xLabel1 + 54 + 10; // 紧邻标签右侧,间隙10像素
    int yLineEdit = yLabel1;
    int wLineEdit = W - xLineEdit - 10; // 编辑框宽度占剩余宽度,右边距10像素
    int hLineEdit = 24;                 // 高度固定24像素
    ui->lineEdit->setGeometry(xLineEdit, yLineEdit, wLineEdit, hLineEdit);

    // 第二行控件布局
    // 标签2 固定大小54x24,垂直居中在窗口的2/4高度处
    int xLabel2 = 10;
    int yLabel2 = 2 * H / 4 - 12;
    ui->label_2->move(xLabel2, yLabel2);
    // 动态按钮紧邻标签右侧,大小由槽函数动态调整
    int xButtonDynamic = xLabel2 + 54 + 10;
    int yButtonDynamic = yLabel2;
    ui->btnDynamic->move(xButtonDynamic, yButtonDynamic);

    // 第三行控件布局
    // 标签3 固定大小54x24,垂直居中在窗口的3/4高度处
    int xLabel3 = 10;
    int yLabel3 = 3 * H / 4 - 12;
    ui->label_3->move(xLabel3, yLabel3);
    // 固定按钮紧邻标签右侧,大小固定
    int xButtonFixed = xLabel3 + 54 + 10;
    int yButtonFixed = yLabel3;
    ui->btnFixed->move(xButtonFixed, yButtonFixed);
}
posted @ 2025-06-08 16:49  _Sylvan  阅读(161)  评论(0)    收藏  举报