
一、前言
图片轮播是桌面软件开发中比较常见的一个功能,例如:
- 相册自动播放
- 广告图片轮播
- 产品展示
- 大屏图片切换
- 教学课件演示
本文使用 Qt 5.9、Qt Widgets 和 C++ 实现一个完整的图片轮播器。
项目支持:
- 自动轮播图片
- 循环播放
- 开始播放
- 暂停播放
- 显示当前图片序号
- 图片等比缩放
- 窗口尺寸变化后自动调整图片
- 使用 Qt 资源系统嵌入图片
- 程序换电脑后图片路径不会失效
运行环境:
Qt 5.9.0
Qt Creator 4.3
MinGW 5.3.0 32bit
C++11
二、项目功能效果
程序启动后显示第一张图片,并显示状态:
第 1 / 5 张 准备播放
点击"开始"按钮后,程序每秒切换一张图片:
第 1 / 5 张 正在播放
第 2 / 5 张 正在播放
第 3 / 5 张 正在播放
第 4 / 5 张 正在播放
第 5 / 5 张 正在播放
第 1 / 5 张 正在播放
点击"暂停"按钮后,当前图片停止切换:
第 3 / 5 张 已暂停
再次点击"开始",程序会从当前图片继续播放。
三、项目目录结构
项目名称:
objectTimer
目录结构:
objectTimer/
├── objectTimer.pro
├── main.cpp
├── widget.h
├── widget.cpp
├── widget.ui
├── resources.qrc
└── images/
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
└── 5.jpg
文件作用:
| 文件 | 作用 |
|---|---|
objectTimer.pro |
qmake 工程配置 |
main.cpp |
程序入口 |
widget.h |
Widget 类声明 |
widget.cpp |
图片轮播逻辑 |
widget.ui |
程序界面 |
resources.qrc |
Qt 图片资源配置 |
images |
存放轮播图片 |
四、图片资源配置
Qt 项目可以使用 .qrc 文件将图片、图标、样式等资源编译进可执行程序。
创建:
resources.qrc
内容如下:
<RCC>
<qresource prefix="/">
<file>images/1.jpg</file>
<file>images/2.jpg</file>
<file>images/3.jpg</file>
<file>images/4.jpg</file>
<file>images/5.jpg</file>
</qresource>
</RCC>
因为资源前缀是:
<qresource prefix="/">
所以程序中的资源路径是:
:/images/1.jpg
:/images/2.jpg
:/images/3.jpg
:/images/4.jpg
:/images/5.jpg
使用 Qt 资源系统有以下优点:
- 图片会被编译进程序。
- 不需要依赖外部相对路径。
- 更换电脑后图片不会丢失。
- 程序发布时管理更加方便。
- 不需要手动复制图片目录。
五、修改 objectTimer.pro
为了让 qmake 编译 .qrc 文件,需要在工程文件中加入:
RESOURCES += resources.qrc
完整工程文件:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = objectTimer
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
RESOURCES += resources.qrc
其中:
SOURCES表示 C++ 源文件。HEADERS表示头文件。FORMS表示.ui界面文件。RESOURCES表示 Qt 资源文件。
六、main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
主要流程:
创建 QApplication
↓
创建 Widget
↓
显示 Widget
↓
进入事件循环 exec()
QApplication::exec() 会持续处理鼠标、键盘、定时器和窗口事件。
七、widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QStringList>
#include <QWidget>
class QResizeEvent;
class QTimerEvent;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
protected:
void timerEvent(QTimerEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void on_startButton_clicked();
void on_pauseButton_clicked();
private:
void showImage(int index);
void updateStatus();
Ui::Widget *ui;
QStringList m_images;
int m_timerId;
int m_currentIndex;
bool m_hasStarted;
};
#endif // WIDGET_H
重点成员变量说明:
| 成员 | 作用 |
|---|---|
m_images |
保存所有图片资源路径 |
m_timerId |
保存定时器 ID,0 表示未运行 |
m_currentIndex |
当前图片下标 |
m_hasStarted |
判断是否播放过,用于显示"已暂停" |
八、widget.cpp 完整实现
#include "widget.h"
#include "ui_widget.h"
#include <QPixmap>
#include <QResizeEvent>
#include <QTimerEvent>
namespace {
const int kSlideshowIntervalMs = 1000;
} // namespace
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget),
m_timerId(0),
m_currentIndex(0),
m_hasStarted(false)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("图片轮播器"));
m_images << QStringLiteral(":/images/1.jpg")
<< QStringLiteral(":/images/2.jpg")
<< QStringLiteral(":/images/3.jpg")
<< QStringLiteral(":/images/4.jpg")
<< QStringLiteral(":/images/5.jpg");
if (m_images.isEmpty()) {
ui->imageLabel->setText(QStringLiteral("没有可播放的图片"));
ui->startButton->setEnabled(false);
ui->pauseButton->setEnabled(false);
return;
}
showImage(0);
}
Widget::~Widget()
{
if (m_timerId != 0) {
killTimer(m_timerId);
}
delete ui;
}
void Widget::on_startButton_clicked()
{
m_hasStarted = true;
if (m_timerId == 0) {
m_timerId = startTimer(kSlideshowIntervalMs);
}
updateStatus();
}
void Widget::on_pauseButton_clicked()
{
if (m_timerId != 0) {
killTimer(m_timerId);
m_timerId = 0;
}
updateStatus();
}
void Widget::timerEvent(QTimerEvent *event)
{
if (event && m_timerId != 0 && event->timerId() == m_timerId) {
const int nextIndex = (m_currentIndex + 1) % m_images.size();
showImage(nextIndex);
return;
}
QWidget::timerEvent(event);
}
void Widget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (!m_images.isEmpty()) {
showImage(m_currentIndex);
}
}
void Widget::showImage(int index)
{
if (m_images.isEmpty()) {
return;
}
const int imageCount = m_images.size();
m_currentIndex = (index % imageCount + imageCount) % imageCount;
QPixmap source(m_images.at(m_currentIndex));
if (source.isNull()) {
ui->imageLabel->setText(QStringLiteral("图片加载失败"));
return;
}
const QSize targetSize = ui->imageLabel->size() - QSize(12, 12);
if (targetSize.width() > 0 && targetSize.height() > 0) {
ui->imageLabel->setPixmap(
source.scaled(targetSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
ui->imageLabel->setText(QString());
}
updateStatus();
}
void Widget::updateStatus()
{
const bool isPlaying = (m_timerId != 0);
ui->startButton->setEnabled(!isPlaying);
ui->pauseButton->setEnabled(isPlaying);
QString stateText;
if (isPlaying) {
stateText = QStringLiteral("正在播放");
} else if (m_hasStarted) {
stateText = QStringLiteral("已暂停");
} else {
stateText = QStringLiteral("准备播放");
}
ui->statusLabel->setText(
QStringLiteral("第 %1 / %2 张 %3")
.arg(m_currentIndex + 1)
.arg(m_images.size())
.arg(stateText));
}
九、startTimer() 与 timerEvent() 原理
Qt 提供了两种常用的定时器方式。
方式一:QTimer
使用独立的 QTimer 对象:
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this]() {
// 定时执行
});
timer->start(1000);
方式二:startTimer() 与 timerEvent()
m_timerId = startTimer(1000);
当定时时间到达后,Qt 会调用:
void Widget::timerEvent(QTimerEvent *event)
{
}
本项目使用第二种方式。
需要注意:
- 一个对象可以同时启动多个定时器。
- 每个定时器都有不同的定时器 ID。
- 需要在
timerEvent()中判断timerId。 - 不再使用时需要调用
killTimer()。
代码中:
if (event && m_timerId != 0 && event->timerId() == m_timerId) {
const int nextIndex = (m_currentIndex + 1) % m_images.size();
showImage(nextIndex);
return;
}
m_currentIndex + 1 表示下一张图片。
取模运算:
% m_images.size()
可以实现循环播放。
例如一共有 5 张图片:
0 → 1 → 2 → 3 → 4 → 0 → 1 ...
十、开始与暂停功能
1. 开始播放
void Widget::on_startButton_clicked()
{
m_hasStarted = true;
if (m_timerId == 0) {
m_timerId = startTimer(kSlideshowIntervalMs);
}
updateStatus();
}
判断:
if (m_timerId == 0)
可以避免重复点击"开始"时创建多个定时器。
2. 暂停播放
void Widget::on_pauseButton_clicked()
{
if (m_timerId != 0) {
killTimer(m_timerId);
m_timerId = 0;
}
updateStatus();
}
暂停后:
- 定时器被销毁。
- 当前图片保持不变。
- 再次点击开始时从当前图片继续播放。
十一、图片等比缩放
直接显示大尺寸图片,可能会:
- 超出窗口范围。
- 图片被拉伸变形。
- 内存占用过大。
- 不同分辨率电脑显示效果不一致。
因此代码中使用了:
source.scaled(targetSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
参数说明:
| 参数 | 作用 |
|---|---|
targetSize |
目标显示尺寸 |
Qt::KeepAspectRatio |
保持原始宽高比 |
Qt::SmoothTransformation |
使用平滑缩放 |
为了避免图片紧贴边框,增加了少量内边距:
const QSize targetSize = ui->imageLabel->size() - QSize(12, 12);
当窗口大小改变时:
void Widget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (!m_images.isEmpty()) {
showImage(m_currentIndex);
}
}
程序会重新缩放当前图片,使其始终适应当前窗口。
十二、widget.ui 界面文件
界面主要包含:
- 一个图片显示标签
- 一个状态标签
- 一个开始按钮
- 一个暂停按钮
- 一个垂直布局
- 一个水平按钮布局
完整 .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>640</width>
<height>520</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>520</width>
<height>420</height>
</size>
</property>
<property name="windowTitle">
<string>图片轮播器</string>
</property>
<property name="styleSheet">
<string>QWidget {
background-color: #f3f4f6;
color: #1f2937;
font-family: 'Microsoft YaHei';
font-size: 10pt;
}
QLabel#imageLabel {
background-color: #ffffff;
border: 1px solid #cbd5e1;
border-radius: 10px;
}
QLabel#statusLabel {
color: #64748b;
font-size: 9pt;
}
QPushButton {
color: #ffffff;
border: none;
border-radius: 7px;
padding: 9px 24px;
font-weight: bold;
}
QPushButton#startButton {
background-color: #2563eb;
}
QPushButton#startButton:hover {
background-color: #1d4ed8;
}
QPushButton#startButton:pressed {
background-color: #1e40af;
}
QPushButton#pauseButton {
background-color: #64748b;
}
QPushButton#pauseButton:hover {
background-color: #475569;
}
QPushButton#pauseButton:pressed {
background-color: #334155;
}
QPushButton:disabled {
background-color: #cbd5e1;
color: #94a3b8;
}</string>
</property>
<layout class="QVBoxLayout" name="mainLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>16</number>
</property>
<item>
<widget class="QLabel" name="imageLabel">
<property name="minimumSize">
<size>
<width>480</width>
<height>340</height>
</size>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="text">
<string>图片加载中...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="text">
<string>准备播放</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="buttonLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QPushButton" name="startButton">
<property name="minimumSize">
<size>
<width>120</width>
<height>38</height>
</size>
</property>
<property name="text">
<string>开始</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pauseButton">
<property name="minimumSize">
<size>
<width>120</width>
<height>38</height>
</size>
</property>
<property name="text">
<string>暂停</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
十三、信号与槽自动连接
.ui 文件中按钮对象名分别是:
startButton
pauseButton
头文件中声明的槽函数分别是:
void on_startButton_clicked();
void on_pauseButton_clicked();
Qt Designer 生成的 setupUi() 中会调用:
QMetaObject::connectSlotsByName(Widget);
它会自动完成连接:
startButton.clicked()
↓
on_startButton_clicked()
pauseButton.clicked()
↓
on_pauseButton_clicked()
因此不需要手动再写:
connect(ui->startButton,
&QPushButton::clicked,
this,
&Widget::on_startButton_clicked);
前提是:
- 类中必须有
Q_OBJECT。 - 槽函数必须声明在
private slots或public slots中。 - 函数名必须符合
on_对象名_信号名()规则。
十四、状态栏更新
状态栏使用 statusLabel 显示:
ui->statusLabel->setText(
QStringLiteral("第 %1 / %2 张 %3")
.arg(m_currentIndex + 1)
.arg(m_images.size())
.arg(stateText));
效果:
第 3 / 5 张 正在播放
QString::arg() 可以依次替换字符串中的 %1、%2、%3。
十五、按钮状态控制
播放时禁用"开始"按钮,启用"暂停"按钮:
ui->startButton->setEnabled(!isPlaying);
ui->pauseButton->setEnabled(isPlaying);
状态关系:
| 播放状态 | 开始按钮 | 暂停按钮 |
|---|---|---|
| 未播放 | 可用 | 不可用 |
| 正在播放 | 不可用 | 可用 |
| 已暂停 | 可用 | 不可用 |
这样可以防止用户重复点击"开始"而创建多个定时器。
十六、资源路径常见错误
1. 使用普通文件路径
QPixmap pix("images/1.jpg");
这种写法依赖程序运行目录。
如果工作目录改变,图片就可能无法加载。
2. 使用 Qt 资源路径
QPixmap pix(":/images/1.jpg");
以冒号开头,表示从 Qt 资源系统加载。
推荐使用:
QStringLiteral(":/images/1.jpg")
3. .qrc 没有加入 .pro
只创建 resources.qrc 是不够的。
必须在 .pro 中加入:
RESOURCES += resources.qrc
然后重新执行 qmake,再编译项目。
十七、编译和运行
在 Qt Creator 中打开:
objectTimer.pro
选择:
Desktop Qt 5.9.0 MinGW 32bit
执行:
Ctrl+B 编译
Ctrl+R 运行
编译过程中,Qt 会自动调用:
uic.exe
moc.exe
rcc.exe
作用如下:
| 工具 | 作用 |
|---|---|
uic |
将 .ui 转换为 ui_widget.h |
moc |
处理 Q_OBJECT,生成元对象代码 |
rcc |
将 .qrc 中的图片编译成 C++ 资源代码 |
最终生成为:
objectTimer.exe
十八、完整运行流程
启动程序
↓
创建 Widget
↓
setupUi()
↓
读取 5 张图片资源路径
↓
显示第 1 张图片
↓
点击"开始"
↓
startTimer(1000)
↓
每隔 1 秒触发 timerEvent()
↓
计算下一张图片下标
↓
重新加载并缩放图片
↓
更新状态栏
↓
到达最后一张后回到第一张
暂停流程:
点击"暂停"
↓
killTimer(m_timerId)
↓
m_timerId = 0
↓
保留当前图片
↓
状态改为"已暂停"
十九、可以继续扩展的功能
1. 上一张和下一张
增加两个按钮:
上一张
下一张
点击后手动切换:
m_currentIndex = (m_currentIndex - 1 + m_images.size())
% m_images.size();
2. 设置轮播间隔
增加一个 QSpinBox:
QSpinBox *intervalSpinBox;
用户可以自定义图片切换时间。
3. 显示缩略图列表
使用 QListWidget 显示所有图片的小图,点击即可切换。
4. 添加淡入淡出动画
使用:
QPropertyAnimation
QGraphicsOpacityEffect
实现图片切换动画。
5. 支持拖拽图片
重写:
dragEnterEvent()
dropEvent()
将本地图片拖入窗口后自动播放。
6. 全屏播放
增加全屏按钮:
showFullScreen();
7. 读取文件夹中的全部图片
使用:
QDir
QFileInfoList
扫描指定目录中的 JPG、PNG、BMP 等图片。
8. 保存播放状态
使用:
QSettings
保存上次播放到的图片和轮播间隔。
二十、总结
本文使用 Qt 5.9 实现了一个完整的图片轮播器,涉及以下知识点:
QWidget
QLabel
QPushButton
QPixmap
QStringList
QVBoxLayout
QHBoxLayout
QTimerEvent
startTimer()
killTimer()
timerEvent()
resizeEvent()
Q_OBJECT
信号与槽
qmake
uic
moc
rcc
Qt 资源系统
图片等比缩放
核心实现思路是:
- 使用
QStringList保存图片资源路径。 - 使用
startTimer()创建定时器。 - 在
timerEvent()中计算下一张图片。 - 使用取模运算实现循环播放。
- 使用
QPixmap::scaled()自动缩放图片。 - 使用
killTimer()暂停播放。 - 使用
.qrc将图片编译进程序。 - 使用
QLabel显示图片和播放状态。
这个项目结构比较简单,适合作为 Qt 定时器、信号与槽、图片资源和界面布局的综合练习,也可以继续扩展成相册、广告轮播或大屏图片展示程序。