常用控件与布局管理
这一章讲 Qt 界面开发的"积木":QWidget、QMainWindow、QDialog 三大窗口类,以及 QHBoxLayout/QVBoxLayout/QGridLayout 三大布局。学完就能拼出大部分界面。
一、QWidget:所有控件的基类
1.1 QWidget 是什么
QWidget 是所有界面元素的基类,窗口、按钮、输入框都是它的子类。它既可以作为独立窗口,也可以嵌入到其他控件中。
1.2 常用属性与方法
#include <QWidget>
#include <QDebug>
/**
* @brief 演示 QWidget 常用方法
*/
void demoWidget()
{
QWidget w;
// 窗口标题
w.setWindowTitle("我的窗口");
// 大小
w.resize(400, 300); // 设置大小
w.setMinimumSize(200, 150); // 最小尺寸
w.setMaximumSize(800, 600); // 最大尺寸
w.setFixedSize(400, 300); // 固定大小,不可拉伸
// 位置
w.move(100, 100); // 移动到屏幕坐标 (100, 100)
// 显示与隐藏
w.show(); // 显示(非模态)
// w.hide(); // 隐藏
// w.close(); // 关闭
// 状态查询
qDebug() << "是否可见:" << w.isVisible();
qDebug() << "是否启用:" << w.isEnabled();
qDebug() << "大小:" << w.size();
qDebug() << "位置:" << w.pos();
// 启用/禁用
w.setEnabled(false); // 禁用(变灰,不响应输入)
w.setEnabled(true);
// 设置提示
w.setToolTip("这是一个提示"); // 鼠标悬停显示
}
1.3 窗口标志(WindowFlags)
#include <QWidget>
/**
* @brief 演示窗口标志
*/
void demoWindowFlags()
{
// 普通窗口(默认)
QWidget normal;
normal.setWindowTitle("普通窗口");
normal.show();
// 无边框窗口(自定义标题栏时用)
QWidget borderless;
borderless.setWindowFlags(Qt::FramelessWindowHint);
borderless.show();
// 窗口置顶
QWidget alwaysOnTop;
alwaysOnTop.setWindowFlags(Qt::WindowStaysOnTopHint);
alwaysOnTop.show();
// 对话框风格(带问号按钮)
QWidget dialogStyle;
dialogStyle.setWindowFlags(Qt::Dialog);
dialogStyle.show();
// 组合使用:无边框 + 置顶
QWidget combined;
combined.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
combined.show();
}
二、QMainWindow:主窗口框架
2.1 QMainWindow 的结构
QMainWindow 是带菜单栏、工具栏、状态栏、停靠窗口的主窗口框架,适合做应用程序主界面。
┌─────────────────────────────────────┐
│ 菜单栏 (MenuBar) │
├─────────────────────────────────────┤
│ 工具栏 (ToolBar) │
├──────────┬──────────────────────────┤
│ 停靠窗口 │ 中心部件 (CentralWidget) │
│ (Dock) │ │
├──────────┴──────────────────────────┤
│ 状态栏 (StatusBar) │
└─────────────────────────────────────┘
2.2 完整示例:带菜单工具栏的主窗口
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QTextEdit;
class QLabel;
/**
* @brief 主窗口,演示 QMainWindow 的各个区域
*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
private slots:
void onNewFile(); ///< 新建文件
void onOpenFile(); ///< 打开文件
void onSaveFile(); ///< 保存文件
void onAbout(); ///< 关于对话框
private:
void createMenuBar(); ///< 创建菜单栏
void createToolBar(); ///< 创建工具栏
void createStatusBar(); ///< 创建状态栏
QTextEdit *m_textEdit; ///< 中心文本编辑区
QLabel *m_statusLabel; ///< 状态栏标签
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include <QTextEdit>
#include <QLabel>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QAction>
#include <QFileDialog>
#include <QMessageBox>
#include <QFile>
#include <QTextStream>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("我的记事本");
resize(800, 600);
// 1. 设置中心部件(必须)
m_textEdit = new QTextEdit(this);
setCentralWidget(m_textEdit);
// 2. 创建菜单、工具栏、状态栏
createMenuBar();
createToolBar();
createStatusBar();
}
void MainWindow::createMenuBar()
{
// 文件菜单
QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
QAction *newAct = new QAction("新建(&N)", this);
newAct->setShortcut(QKeySequence::New); // Ctrl+N
connect(newAct, &QAction::triggered, this, &MainWindow::onNewFile);
fileMenu->addAction(newAct);
QAction *openAct = new QAction("打开(&O)…", this);
openAct->setShortcut(QKeySequence::Open); // Ctrl+O
connect(openAct, &QAction::triggered, this, &MainWindow::onOpenFile);
fileMenu->addAction(openAct);
QAction *saveAct = new QAction("保存(&S)", this);
saveAct->setShortcut(QKeySequence::Save); // Ctrl+S
connect(saveAct, &QAction::triggered, this, &MainWindow::onSaveFile);
fileMenu->addAction(saveAct);
fileMenu->addSeparator(); // 分隔线
QAction *exitAct = new QAction("退出(&X)", this);
exitAct->setShortcut(QKeySequence::Quit); // Ctrl+Q
connect(exitAct, &QAction::triggered, this, &QWidget::close);
fileMenu->addAction(exitAct);
// 帮助菜单
QMenu *helpMenu = menuBar()->addMenu("帮助(&H)");
QAction *aboutAct = new QAction("关于(&A)", this);
connect(aboutAct, &QAction::triggered, this, &MainWindow::onAbout);
helpMenu->addAction(aboutAct);
}
void MainWindow::createToolBar()
{
QToolBar *toolBar = addToolBar("主工具栏");
toolBar->setMovable(false); // 禁止拖动
// 工具栏可以复用菜单的 QAction
QAction *newAct = new QAction("新建", this);
connect(newAct, &QAction::triggered, this, &MainWindow::onNewFile);
toolBar->addAction(newAct);
QAction *openAct = new QAction("打开", this);
connect(openAct, &QAction::triggered, this, &MainWindow::onOpenFile);
toolBar->addAction(openAct);
QAction *saveAct = new QAction("保存", this);
connect(saveAct, &QAction::triggered, this, &MainWindow::onSaveFile);
toolBar->addAction(saveAct);
}
void MainWindow::createStatusBar()
{
m_statusLabel = new QLabel("就绪", this);
statusBar()->addWidget(m_statusLabel);
// 监听文本变化,更新状态栏字数
connect(m_textEdit, &QTextEdit::textChanged, this, [this]() {
int count = m_textEdit->toPlainText().length();
m_statusLabel->setText(QString("字符数: %1").arg(count));
});
}
void MainWindow::onNewFile()
{
m_textEdit->clear();
m_statusLabel->setText("已新建文件");
}
void MainWindow::onOpenFile()
{
QString fileName = QFileDialog::getOpenFileName(
this, "打开文件", "", "文本文件 (*.txt);;所有文件 (*.*)");
if (fileName.isEmpty()) return;
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
m_textEdit->setPlainText(in.readAll());
file.close();
statusBar()->showMessage("已打开: " + fileName, 3000);
}
}
void MainWindow::onSaveFile()
{
QString fileName = QFileDialog::getSaveFileName(
this, "保存文件", "untitled.txt", "文本文件 (*.txt)");
if (fileName.isEmpty()) return;
QFile file(fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << m_textEdit->toPlainText();
file.close();
statusBar()->showMessage("已保存: " + fileName, 3000);
}
}
void MainWindow::onAbout()
{
QMessageBox::about(this, "关于", "我的记事本 v1.0\\n基于 Qt 6 开发");
}
2.3 QAction:菜单和工具栏的统一抽象
QAction 是 Qt 的一个重要设计:一个动作可以同时出现在菜单和工具栏中,共享文本、图标、快捷键、信号。
// 最佳实践:创建一次 QAction,菜单和工具栏都用它
QAction *saveAct = new QAction(QIcon(":/icons/save.png"), "保存", this);
saveAct->setShortcut(QKeySequence::Save);
saveAct->setStatusTip("保存当前文件");
connect(saveAct, &QAction::triggered, this, &MainWindow::onSaveFile);
fileMenu->addAction(saveAct); // 加到菜单
toolBar->addAction(saveAct); // 加到工具栏(同一个 action)
三、QDialog:对话框
3.1 模态与非模态
#include <QDialog>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
/**
* @brief 自定义对话框
*/
class MyDialog : public QDialog
{
Q_OBJECT
public:
explicit MyDialog(QWidget *parent = nullptr)
: QDialog(parent)
{
setWindowTitle("自定义对话框");
auto *layout = new QVBoxLayout(this);
layout->addWidget(new QLabel("这是一个对话框", this));
auto *okBtn = new QPushButton("确定", this);
auto *cancelBtn = new QPushButton("取消", this);
layout->addWidget(okBtn);
layout->addWidget(cancelBtn);
// 点击确定时,对话框返回 QDialog::Accepted
connect(okBtn, &QPushButton::clicked, this, &QDialog::accept);
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
}
};
// 使用对话框
void demoDialog()
{
MyDialog dlg;
// 模态显示:阻塞主窗口,直到对话框关闭
if (dlg.exec() == QDialog::Accepted) {
qDebug() << "用户点击了确定";
} else {
qDebug() << "用户点击了取消";
}
// 非模态显示:不阻塞,和主窗口同时操作
// MyDialog *dlg2 = new MyDialog(this);
// dlg2->show();
// dlg2->setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动释放
}
3.2 标准对话框(开箱即用)
Qt 提供了一系列常用标准对话框,不用自己写。
#include <QMessageBox>
#include <QInputDialog>
#include <QFileDialog>
#include <QColorDialog>
#include <QFontDialog>
#include <QDebug>
/**
* @brief 演示各种标准对话框
*/
void demoStandardDialogs(QWidget *parent)
{
// 1. 消息框
QMessageBox::information(parent, "提示", "操作成功");
QMessageBox::warning(parent, "警告", "文件可能已损坏");
QMessageBox::critical(parent, "错误", "无法连接服务器");
// 确认对话框(有返回值)
auto reply = QMessageBox::question(
parent, "确认", "确定要删除吗?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
qDebug() << "用户确认删除";
}
// 2. 输入对话框
bool ok;
QString text = QInputDialog::getText(
parent, "输入", "请输入姓名:", QLineEdit::Normal, "", &ok);
if (ok && !text.isEmpty()) {
qDebug() << "输入的姓名:" << text;
}
int value = QInputDialog::getInt(parent, "输入", "请输入数量:", 1, 1, 100, 1, &ok);
// 3. 文件对话框
QString fileName = QFileDialog::getOpenFileName(
parent, "打开文件", "", "图片 (*.png *.jpg);;所有文件 (*)");
QString saveName = QFileDialog::getSaveFileName(
parent, "保存文件", "output.txt", "文本 (*.txt)");
QString dir = QFileDialog::getExistingDirectory(parent, "选择目录");
// 4. 颜色对话框
QColor color = QColorDialog::getColor(Qt::white, parent, "选择颜色");
if (color.isValid()) {
qDebug() << "选中颜色:" << color.name();
}
// 5. 字体对话框
bool ok2;
QFont font = QFontDialog::getFont(&ok2, QFont("Arial", 12), parent);
if (ok2) {
qDebug() << "选中字体:" << font.family() << font.pointSize();
}
}
四、布局管理(Layout)
布局管理器自动管理控件的位置和大小,窗口缩放时控件自动适配。永远不要手动写 setGeometry 定位控件,用布局。
4.1 三大基础布局
| QHBoxLayout | 水平排列 | 按钮行、工具栏 |
| QVBoxLayout | 垂直排列 | 表单、列表 |
| QGridLayout | 网格排列 | 计算器键盘、表格布局 |
4.2 QHBoxLayout(水平布局)
#include <QWidget>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
/**
* @brief 演示水平布局
*/
QWidget *createHBoxDemo()
{
QWidget *widget = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(widget);
layout->addWidget(new QPushButton("按钮1", widget));
layout->addWidget(new QPushButton("按钮2", widget));
layout->addWidget(new QLabel("标签", widget));
layout->addWidget(new QPushButton("按钮3", widget));
layout->addStretch(); // 添加弹性空间,把前面的控件挤到左边
return widget;
}
4.3 QVBoxLayout(垂直布局)
#include <QWidget>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QPushButton>
/**
* @brief 演示垂直布局(登录表单)
*/
QWidget *createLoginForm()
{
QWidget *widget = new QWidget;
QVBoxLayout *layout = new QVBoxLayout(widget);
layout->addWidget(new QLineEdit("用户名", widget));
layout->addWidget(new QLineEdit("密码", widget));
layout->addStretch(); // 弹性空间
QPushButton *loginBtn = new QPushButton("登录", widget);
layout->addWidget(loginBtn);
// 设置间距和边距
layout->setSpacing(10); // 控件间距
layout->setContentsMargins(20, 20, 20, 20); // 左、上、右、下边距
return widget;
}
4.4 QGridLayout(网格布局)
#include <QWidget>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
/**
* @brief 演示网格布局(计算器键盘)
*/
QWidget *createCalculatorKeypad()
{
QWidget *widget = new QWidget;
QGridLayout *layout = new QGridLayout(widget);
// 显示栏(跨4列)
QLineEdit *display = new QLineEdit("0", widget);
display->setAlignment(Qt::AlignRight);
display->setReadOnly(true);
layout->addWidget(display, 0, 0, 1, 4); // 行0, 列0, 占1行4列
// 按钮网格
QStringList buttons = {
"C", "±", "%", "÷",
"7", "8", "9", "×",
"4", "5", "6", "-",
"1", "2", "3", "+",
"0", ".", "="
};
int row = 1, col = 0;
for (const QString &text : buttons) {
QPushButton *btn = new QPushButton(text, widget);
if (text == "0") {
layout->addWidget(btn, row, col, 1, 2); // 0 占2列
col += 2;
} else {
layout->addWidget(btn, row, col);
++col;
}
if (col >= 4) { col = 0; ++row; }
}
layout->setSpacing(5);
return widget;
}
4.5 布局嵌套(复杂界面)
实际界面通常是布局嵌套布局。
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
/**
* @brief 演示布局嵌套(用户信息表单)
*/
QWidget *createComplexForm()
{
QWidget *widget = new QWidget;
QVBoxLayout *mainLayout = new QVBoxLayout(widget); // 最外层垂直
// 第一部分:基本信息(网格布局)
QGridLayout *infoLayout = new QGridLayout;
infoLayout->addWidget(new QLabel("姓名:", widget), 0, 0);
infoLayout->addWidget(new QLineEdit(widget), 0, 1);
infoLayout->addWidget(new QLabel("邮箱:", widget), 1, 0);
infoLayout->addWidget(new QLineEdit(widget), 1, 1);
infoLayout->addWidget(new QLabel("电话:", widget), 2, 0);
infoLayout->addWidget(new QLineEdit(widget), 2, 1);
mainLayout->addLayout(infoLayout); // 把布局加入布局
// 第二部分:备注(文本框)
mainLayout->addWidget(new QLabel("备注:", widget));
mainLayout->addWidget(new QTextEdit(widget));
// 第三部分:按钮行(水平布局,右对齐)
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addStretch(); // 弹性空间把按钮挤到右边
btnLayout->addWidget(new QPushButton("取消", widget));
btnLayout->addWidget(new QPushButton("确定", widget));
mainLayout->addLayout(btnLayout);
mainLayout->setSpacing(10);
mainLayout->setContentsMargins(20, 20, 20, 20);
return widget;
}
4.6 伸缩因子(Stretch Factor)
QHBoxLayout *layout = new QHBoxLayout(widget);
QPushButton *btn1 = new QPushButton("短", widget);
QPushButton *btn2 = new QPushButton("长按钮", widget);
// 第二个参数是 stretch,值越大占的空间越多
layout->addWidget(btn1, 1); // 占 1 份
layout->addWidget(btn2, 2); // 占 2 份(宽度是 btn1 的两倍)
// 也可以用 setStretch
// layout->setStretch(0, 1);
// layout->setStretch(1, 2);
4.7 控件大小策略(SizePolicy)
QPushButton *btn = new QPushButton("按钮", widget);
// 水平方向:固定大小;垂直方向:可扩展
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
// 常用策略:
// QSizePolicy::Fixed 固定大小(取 sizeHint)
// QSizePolicy::Minimum 至少 sizeHint,可以更大
// QSizePolicy::Maximum 最多 sizeHint,可以更小
// QSizePolicy::Preferred 推荐 sizeHint,可以更大或更小(默认)
// QSizePolicy::Expanding 尽可能多占空间
// QSizePolicy::Ignored 忽略 sizeHint,尽可能多占
4.8 QFormLayout(表单专用布局)
虽然路线图没列,但做表单非常好用,补充一下。
#include <QFormLayout>
#include <QLineEdit>
#include <QSpinBox>
QFormLayout *form = new QFormLayout(widget);
form->addRow("姓名:", new QLineEdit(widget));
form->addRow("年龄:", new QSpinBox(widget));
form->addRow("邮箱:", new QLineEdit(widget));
// 自动对齐标签和输入框,比手写 QGridLayout 方便
五、常用控件速查
| QLabel | 显示文本/图片 | linkActivated |
| QPushButton | 按钮 | clicked |
| QLineEdit | 单行输入 | textChanged, editingFinished, returnPressed |
| QTextEdit | 多行富文本 | textChanged |
| QPlainTextEdit | 多行纯文本(性能好) | textChanged |
| QCheckBox | 复选框 | stateChanged, toggled |
| QRadioButton | 单选按钮 | toggled |
| QComboBox | 下拉框 | currentIndexChanged, currentTextChanged |
| QSpinBox | 数字输入 | valueChanged |
| QSlider | 滑块 | valueChanged |
| QProgressBar | 进度条 | – |
| QListWidget | 列表 | itemClicked, currentItemChanged |
| QTableWidget | 表格 | cellClicked, itemChanged |
| QTabWidget | 标签页 | currentChanged |
| QGroupBox | 分组框 | – |
| QScrollArea | 滚动区域 | – |
| QSplitter | 可拖动分割 | splitterMoved |
| QStatusBar | 状态栏 | – |
| QMenuBar | 菜单栏 | – |
| QToolBar | 工具栏 | – |
六、核心要点总结
下一章:资源系统与 QSS 样式



