Qt 作为跨平台的 C++ 图形界面开发框架,提供了丰富的控件和便捷的布局系统,本文将从 QMainWindow 核心组件、对话框使用、布局管理、常用控件等维度,结合完整可运行的代码示例,详解 Qt 界面开发的核心知识点。
一、QMainWindow 核心组件
QMainWindow 是 Qt 中最常用的主窗口类,内置了菜单栏、工具栏、状态栏、停靠部件和中心控件,是构建复杂桌面应用的基础。
1.1 菜单栏(MenuBar)
菜单栏是窗口顶部的导航组件,支持多级菜单和分割符,示例代码如下:
cpp
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 获取/创建菜单栏(QMainWindow 内置,无需手动 new)
QMenuBar *mb = this->menuBar();
// 添加一级菜单
QMenu *menuFile = mb->addMenu("文件");
QMenu *menuEdit = mb->addMenu("编辑");
// 添加菜单项
QAction* actionNew = menuFile->addAction("新建");
QAction* actionOpen = menuFile->addAction("打开");
// 添加分割符
menuFile->addSeparator();
// 添加二级子菜单
QMenu* menuRecent = menuFile->addMenu("最近打开文件");
menuRecent->addAction("1.txt");
menuRecent->addAction("2.log");
// 菜单项点击信号关联(示例)
connect(actionNew, &QAction::triggered, this, [](){
qDebug() << "点击了【新建】菜单项";
});
}
1.2 工具栏(ToolBar)
工具栏是可停靠的快捷操作栏,支持自定义停靠位置、是否浮动/移动,示例代码:
cpp
#include <QToolBar>
// 初始化工具栏
void MainWindow::initToolBar()
{
// 创建工具栏(可创建多个)
QToolBar* toolbar = this->addToolBar("主工具栏");
// 向工具栏添加菜单项(复用菜单栏的 Action)
toolbar->addAction(actionNew);
toolbar->addAction(actionOpen);
// 设置默认停靠位置(左侧)
this->addToolBar(Qt::LeftToolBarArea, toolbar);
// 限制停靠区域:仅允许左右侧
toolbar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
// 禁止浮动(默认可浮动)
toolbar->setFloatable(false);
// 禁止移动(默认可拖动)
toolbar->setMovable(false);
}
1.3 状态栏(StatusBar)
状态栏位于窗口底部,用于显示临时或常驻信息,支持左右分区显示:
cpp
#include <QStatusBar>
#include <QLabel>
// 初始化状态栏
void MainWindow::initStatusBar()
{
// 获取/创建状态栏(QMainWindow 内置)
QStatusBar* sb = this->statusBar();
// 左侧常驻信息
QLabel* labelLeft = new QLabel("就绪", this);
sb->addWidget(labelLeft);
// 右侧常驻信息
QLabel* labelRight = new QLabel("当前用户:admin", this);
sb->addPermanentWidget(labelRight);
// 临时提示(3秒后自动消失)
sb->showMessage("窗口初始化完成", 3000);
}
1.4 停靠部件与中心控件
停靠部件(QDockWidget)是可浮动/停靠的子窗口,中心控件是主窗口的核心区域(如文本编辑区):
cpp
#include <QDockWidget>
#include <QTextEdit>
// 初始化停靠部件和中心控件
void MainWindow::initDockAndCentral()
{
// 创建停靠部件
QDockWidget* dw = new QDockWidget("辅助面板", this);
// 设置停靠位置(底部)
this->addDockWidget(Qt::BottomDockWidgetArea, dw);
// 限制停靠方向
dw->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
// 设置中心控件(QTextEdit 作为核心编辑区)
QTextEdit* textEdit = new QTextEdit(this);
textEdit->setPlaceholderText("请输入内容...");
this->setCentralWidget(textEdit);
}
二、UI 文件与资源文件使用
2.1 UI 文件的使用
Qt Designer 生成的 .ui 文件会被 qmake 转换为 C++ 代码,通过 ui 指针直接访问控件:
-
创建项目时勾选「Generate form」,自动生成
mainwindow.ui; -
在代码中通过
ui->控件对象名访问控件(需包含ui_mainwindow.h); -
setupUi(this)函数自动完成控件的创建和布局。
示例:通过 UI 文件访问菜单项
cpp
// 假设 ui 文件中创建了名为 actionSave 的菜单项
connect(ui->actionSave, &QAction::triggered, this, [](){
qDebug() << "点击了UI文件中的【保存】菜单项";
});
2.2 资源文件(qrc)
资源文件用于将图片、配置文件等嵌入程序,避免绝对路径依赖:
-
添加资源文件:右键项目 → Add New → Qt → Qt Resource File;
-
编辑资源文件,添加前缀(如
/icon)和文件; -
通过
:/前缀/文件名访问资源。
示例:设置菜单项图标
cpp
#include <QIcon>
// 方式1:绝对路径(不推荐,移植性差)
ui->actionNew->setIcon(QIcon("D:/Qt/Image/Luffy.png"));
// 方式2:资源文件(推荐,嵌入程序)
ui->actionNew->setIcon(QIcon(":/icon/Image/Luffy.png"));
三、对话框(QDialog)
对话框是临时窗口,分为模态 (阻塞其他窗口)和非模态(不阻塞),Qt 还提供了标准化对话框(如消息框、文件选择框)。
3.1 自定义对话框
模态对话框
cpp
#include <QDialog>
#include <QPushButton>
// 点击按钮弹出模态对话框
connect(ui->btnModal, &QPushButton::clicked, this, [](){
QDialog dlg(this);
dlg.setWindowTitle("模态对话框");
dlg.resize(300, 200);
// exec() 阻塞,直到对话框关闭
dlg.exec();
qDebug() << "模态对话框已关闭";
});
非模态对话框
非模态对话框需用 new 创建(避免栈销毁),并设置自动释放属性:
cpp
connect(ui->btnNonModal, &QPushButton::clicked, this, [=](){
QDialog* dlg = new QDialog(this);
dlg->setWindowTitle("非模态对话框");
dlg->resize(300, 200);
// 关闭时自动释放内存(避免内存泄漏)
dlg->setAttribute(Qt::WA_DeleteOnClose);
// show() 非阻塞,立即返回
dlg->show();
});
3.2 标准对话框
Qt 封装了常用的标准化对话框,无需自定义,直接调用静态函数即可:
消息对话框(QMessageBox)
支持 4 种类型:信息、警告、错误、提问(可自定义按钮)。
cpp
#include <QMessageBox>
// 错误提示
void MainWindow::on_btnCritical_clicked()
{
QMessageBox::critical(this, "错误", "文件读取失败!");
}
// 警告提示
void MainWindow::on_btnWarning_clicked()
{
QMessageBox::warning(this, "警告", "数据未保存,是否继续?");
}
// 信息提示
void MainWindow::on_btnInfo_clicked()
{
QMessageBox::information(this, "信息", "操作成功完成!");
}
// 提问对话框(带按钮选择)
void MainWindow::on_btnQuestion_clicked()
{
int ret = QMessageBox::question(this, "确认", "是否删除选中文件?",
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
switch(ret) {
case QMessageBox::Yes:
qDebug() << "点击了【是】";
break;
case QMessageBox::No:
qDebug() << "点击了【否】";
break;
case QMessageBox::Cancel:
qDebug() << "点击了【取消】";
break;
}
}
文件选择对话框(QFileDialog)
用于打开/保存文件,支持文件过滤:
cpp
#include <QFileDialog>
void MainWindow::on_btnOpenFile_clicked()
{
// 打开文件对话框:(父窗口, 标题, 默认路径, 文件过滤器)
QString filePath = QFileDialog::getOpenFileName(this, "选择文件", "D:/",
"图片文件 (*.png *.jpg);;文本文件 (*.txt);;所有文件 (*.*)");
if (!filePath.isEmpty()) {
qDebug() << "选中的文件:" << filePath;
}
}
四、布局管理
Qt 布局分为静态布局 (固定位置)和动态布局(自适应窗口大小),推荐使用动态布局提升用户体验。
4.1 核心布局类型
|-------------|------------------|
| 布局类 | 作用 |
| QHBoxLayout | 水平布局(控件横向排列) |
| QVBoxLayout | 垂直布局(控件纵向排列) |
| QGridLayout | 栅格布局(行列对齐) |
| QFormLayout | 表单布局(标签+输入框成对排列) |
4.2 布局使用示例(栅格布局)
cpp
#include <QGridLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QWidget>
void MainWindow::initLayout()
{
// 创建容器承载布局(中心控件)
QWidget* centralWidget = new QWidget(this);
this->setCentralWidget(centralWidget);
// 创建栅格布局
QGridLayout* gridLayout = new QGridLayout(centralWidget);
// 添加控件:(控件, 行, 列, 行跨度, 列跨度)
QLabel* labName = new QLabel("用户名:", this);
QLineEdit* edtName = new QLineEdit(this);
QLabel* labPwd = new QLabel("密码:", this);
QLineEdit* edtPwd = new QLineEdit(this);
edtPwd->setEchoMode(QLineEdit::Password);
QPushButton* btnLogin = new QPushButton("登录", this);
QPushButton* btnCancel = new QPushButton("取消", this);
gridLayout->addWidget(labName, 0, 0);
gridLayout->addWidget(edtName, 0, 1, 1, 2); // 跨2列
gridLayout->addWidget(labPwd, 1, 0);
gridLayout->addWidget(edtPwd, 1, 1, 1, 2);
gridLayout->addWidget(btnLogin, 2, 1);
gridLayout->addWidget(btnCancel, 2, 2);
// 设置布局间距
gridLayout->setMargin(20); // 布局与容器的间距
gridLayout->setSpacing(10); // 控件之间的间距
}
4.3 布局优化技巧
-
弹簧(QSpacerItem) :用于控件居中/留白,如
gridLayout->addItem(new QSpacerItem(0,0, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 0);; -
大小策略 :通过
setSizePolicy设置控件自适应规则(如固定大小、拉伸); -
固定窗口大小 :
setFixedSize(800, 600)或setMinimumSize/setMaximumSize。
五、常用控件实战
5.1 单选按钮(QRadioButton)
单选按钮默认互斥,需用 QGroupBox 隔离不同组:
cpp
#include <QRadioButton>
#include <QGroupBox>
void MainWindow::initRadioButton()
{
QGroupBox* groupBox = new QGroupBox("性别", this);
QHBoxLayout* hLayout = new QHBoxLayout(groupBox);
QRadioButton* rdoMale = new QRadioButton("男", this);
QRadioButton* rdoFemale = new QRadioButton("女", this);
rdoMale->setChecked(true); // 默认选中
hLayout->addWidget(rdoMale);
hLayout->addWidget(rdoFemale);
// 信号关联
connect(rdoMale, &QRadioButton::clicked, [](){
qDebug() << "选中:男";
});
}
5.2 复选框(QCheckBox)
支持三态(选中/未选中/半选),通过 stateChanged 信号监听状态:
cpp
#include <QCheckBox>
void MainWindow::initCheckBox()
{
QCheckBox* cbxRead = new QCheckBox("阅读权限", this);
QCheckBox* cbxWrite = new QCheckBox("写入权限", this);
QCheckBox* cbxAdmin = new QCheckBox("管理员权限", this);
cbxAdmin->setTristate(true); // 启用三态
connect(cbxRead, &QCheckBox::stateChanged, [](int state){
if (state == Qt::Checked) {
qDebug() << "阅读权限:已选中";
} else {
qDebug() << "阅读权限:未选中";
}
});
}
5.3 列表控件(QListWidget)
用于显示单列列表,支持添加单个/多个项:
cpp
#include <QListWidget>
#include <QListWidgetItem>
void MainWindow::initListWidget()
{
QListWidget* listWidget = new QListWidget(this);
// 方式1:添加单个项(居中对齐)
QListWidgetItem* item1 = new QListWidgetItem("床前明月光");
item1->setTextAlignment(Qt::AlignHCenter);
listWidget->addItem(item1);
// 方式2:批量添加项
QStringList poemList;
poemList << "疑是地上霜" << "举头望明月" << "低头思故乡";
listWidget->addItems(poemList);
// 选中项监听
connect(listWidget, &QListWidget::itemClicked, [](QListWidgetItem* item){
qDebug() << "选中:" << item->text();
});
}
5.4 树形控件(QTreeWidget)
用于显示层级结构数据,支持多级节点:
cpp
#include <QTreeWidget>
#include <QTreeWidgetItem>
void MainWindow::initTreeWidget()
{
QTreeWidget* treeWidget = new QTreeWidget(this);
// 设置表头(多列)
treeWidget->setHeaderLabels(QStringList() << "英雄分类" << "简介");
// 根节点
QTreeWidgetItem* rootStrength = new QTreeWidgetItem(QStringList() << "力量型");
QTreeWidgetItem* rootAgility = new QTreeWidgetItem(QStringList() << "敏捷型");
treeWidget->addTopLevelItem(rootStrength);
treeWidget->addTopLevelItem(rootAgility);
// 子节点
QTreeWidgetItem* hero1 = new QTreeWidgetItem(QStringList() << "刚背猪" << "前排坦克,范围输出");
QTreeWidgetItem* hero2 = new QTreeWidgetItem(QStringList() << "月骑" << "中排物理输出");
rootStrength->addChild(hero1);
rootAgility->addChild(hero2);
// 展开所有节点
treeWidget->expandAll();
}
5.5 表格控件(QTableWidget)
用于显示多行多列的表格数据:
cpp
#include <QTableWidget>
#include <QTableWidgetItem>
void MainWindow::initTableWidget()
{
QTableWidget* tableWidget = new QTableWidget(this);
// 1. 设置行列数
tableWidget->setRowCount(5);
tableWidget->setColumnCount(3);
// 2. 设置表头
tableWidget->setHorizontalHeaderLabels(QStringList() << "英雄" << "性别" << "年龄");
// 3. 填充数据
QStringList heroNames = {"亚瑟", "赵云", "妲己", "安琪拉", "孙悟空"};
QStringList heroGender = {"男", "男", "女", "女", "男"};
QStringList heroAge = {"123", "321", "18", "20", "500"};
for (int row = 0; row < 5; row++) {
tableWidget->setItem(row, 0, new QTableWidgetItem(heroNames[row]));
tableWidget->setItem(row, 1, new QTableWidgetItem(heroGender[row]));
tableWidget->setItem(row, 2, new QTableWidgetItem(heroAge[row]));
}
// 自动调整列宽
tableWidget->horizontalHeader()->setStretchLastSection(true);
}
5.6 堆叠控件(QStackedWidget)
用于页面切换(如 Tab 页效果),通过 setCurrentIndex 切换页面:
cpp
#include <QStackedWidget>
#include <QPushButton>
void MainWindow::initStackedWidget()
{
QStackedWidget* stackedWidget = new QStackedWidget(this);
// 创建页面
QWidget* page1 = new QWidget(this);
QWidget* page2 = new QWidget(this);
page1->setStyleSheet("background:#f0f0f0;");
page2->setStyleSheet("background:#e0e0e0;");
// 添加页面
stackedWidget->addWidget(page1);
stackedWidget->addWidget(page2);
// 切换按钮
QPushButton* btnPage1 = new QPushButton("页面1", this);
QPushButton* btnPage2 = new QPushButton("页面2", this);
connect(btnPage1, &QPushButton::clicked, [=](){
stackedWidget->setCurrentIndex(0); // 切换到第1页
});
connect(btnPage2, &QPushButton::clicked, [=](){
stackedWidget->setCurrentIndex(1); // 切换到第2页
});
}
5.7 图片/动画显示(QLabel)
QLabel 不仅能显示文本,还能显示静态图片和动态 GIF:
cpp
#include <QLabel>
#include <QPixmap>
#include <QMovie>
void MainWindow::initLabel()
{
// 1. 显示静态图片
QLabel* labImage = new QLabel(this);
labImage->setPixmap(QPixmap(":/icon/Image/Luffy.png").scaled(100, 100, Qt::KeepAspectRatio));
// 2. 显示动态 GIF
QLabel* labGif = new QLabel(this);
QMovie* movie = new QMovie(":/icon/Image/loading.gif");
labGif->setMovie(movie);
movie->start(); // 播放动画
}
六、自定义控件
当基础控件无法满足需求时,可通过「组合基础控件」创建自定义控件:
6.1 创建自定义控件类
-
右键项目 → Add New → Qt → Qt Designer Form Class → 选择 Widget 模板;
-
在 UI 文件中拖拽基础控件,编写内部逻辑(如信号槽);
-
对外提供接口(如设置值、获取值的函数)。
6.2 使用自定义控件
-
在主窗口 UI 文件中拖拽一个
QWidget; -
右键该 Widget → 提升为 → 输入自定义控件类名 → 添加 → 提升;
-
在代码中通过
ui->自定义控件对象名调用接口。
示例:自定义控件头文件(MyWidget.h)
cpp
#include <QLabel>
#include <QPixmap>
#include <QMovie>
void MainWindow::initLabel()
{
// 1. 显示静态图片
QLabel* labImage = new QLabel(this);
labImage->setPixmap(QPixmap(":/icon/Image/Luffy.png").scaled(100, 100, Qt::KeepAspectRatio));
// 2. 显示动态 GIF
QLabel* labGif = new QLabel(this);
QMovie* movie = new QMovie(":/icon/Image/loading.gif");
labGif->setMovie(movie);
movie->start(); // 播放动画
}
七、总结
本文覆盖了 Qt 界面开发的核心知识点:QMainWindow 组件、UI/资源文件、对话框、布局管理、常用控件及自定义控件。Qt 的控件体系高度封装,结合信号槽机制,能快速实现跨平台的桌面应用。实际开发中,建议优先使用 Qt Designer 可视化布局,配合动态布局和自定义控件,提升开发效率和界面适配性。
完整代码仓库 :可将上述代码整合到一个 MainWindow 类中,编译前需确保.pro 文件包含 QT += core gui widgets,并正确配置资源文件路径。