前言
有些与文件、图片相关桌面工具,为了提高交互性,允许把桌面的文件、图片直接拖拽到应用工具框体控件中,并且框体控件中也可以拖拽里面的文件、图片。本篇分享的是通过QListWidget实现处理来自外部以及自身内部的文件拖放功能。
效果图

源代码已上传到绑定资源中。
功能讲解
自定义QListWidget控件
代码中有详细的注释,以下先提供自定义控件的代码,后续再按照功能点进行说明。
//imagelistwidget.h
#ifndef IMAGELISTWIDGET_H
#define IMAGELISTWIDGET_H
#include <QListWidget>
#include <QString>
#include <QSize>
#include <QDebug>
class ImageListWidget : public QListWidget
{
Q_OBJECT
public:
explicit ImageListWidget(QWidget *parent = nullptr);
void addImage(const QString &filePath); // 添加单个图片
void addImages(const QStringList &filePaths); // 添加多个图片
void clearAllImages(); // 清空所有图片
int imageCount() const; // 获取图片数量
QStringList getImagePaths() const; // 获取所有图片路径
signals:
void imageSelected(const QString &filePath); // 图片被选中信号
void imageCountChanged(int count); // 图片数量变化信号
void imageOrderChanged(); // 图片顺序变化信号
void requestContextMenu(const QPoint &pos); // 请求上下文菜单信号
void imageDoubleClicked(const QString &filePath); // 图片双击信号
public slots:
void verifyDragOrder();
void deleteSelectedImage();// 删除选中图片
protected:
void dragEnterEvent(QDragEnterEvent *event) override; // 拖拽进入事件
void dragMoveEvent(QDragMoveEvent *event) override; // 拖拽移动事件
void dropEvent(QDropEvent *event) override; // 拖放事件
void startDrag(Qt::DropActions supportedActions) override; // 开始拖拽
void mousePressEvent(QMouseEvent *event) override; // 鼠标按下事件
void mouseDoubleClickEvent(QMouseEvent *event) override; // 鼠标双击事件
private:
void initializeUI();// 初始化UI
QSize m_gridSize; // 网格大小
QSize m_iconSize; // 图标大小
};
#endif // IMAGELISTWIDGET_H
//imagelistwidget.cpp
#include "imagelistwidget.h"
#include <QListWidgetItem>
#include <QPixmap>
#include <QFileInfo>
#include <QMessageBox>
#include <QDrag>
#include <QMimeData>
#include <QApplication>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QPainter>
#include <QMouseEvent>
ImageListWidget::ImageListWidget(QWidget *parent)
: QListWidget(parent)
, m_gridSize(150, 150)//初始化网格大小为150x150像素
, m_iconSize(120, 120)//初始化图标大小为120x120像素
{
initializeUI();
}
// UI初始化函数
void ImageListWidget::initializeUI()
{
setViewMode(QListWidget::IconMode);// 设置视图模式为图标模式(显示图标而非列表)
setGridSize(m_gridSize);// 设置网格大小
setIconSize(m_iconSize);// 设置图标大小
setResizeMode(QListWidget::Adjust); // 设置调整模式为自动调整
setMovement(QListWidget::Snap);// 设置移动模式为对齐网格
setSelectionMode(QListWidget::SingleSelection);// 设置选择模式为单选
setDragDropMode(QListWidget::InternalMove);// 设置拖放模式为内部移动
setDefaultDropAction(Qt::MoveAction);// 设置默认拖放动作为移动
setSpacing(0);// 设置项目间距为0
setDropIndicatorShown(true);// 显示拖放指示器
setUniformItemSizes(true);// 设置统一的项目大小
setWordWrap(true);// 启用文本自动换行
}
// 鼠标双击事件处理函数--双击弹出预览图片
void ImageListWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {//检测双击事件
QListWidgetItem *item = itemAt(event->pos());// 获取点击位置的项目
if (item) {
QString filePath = item->data(Qt::UserRole).toString();//获取文件路径
qDebug() << "双击预览图片:" << filePath;
emit imageDoubleClicked(filePath);// 发送信号--由主框架统一处理
event->accept();// 吸收事件
return;
}
}
//其他的事件调用父类的默认处理
QListWidget::mouseDoubleClickEvent(event);
}
//鼠标右键单击--菜单功能
void ImageListWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {//检测右键单击事件
emit requestContextMenu(event->pos());// 发送信号--由主框架统一处理
event->accept();// 吸收事件
return;
}
//其他的事件调用父类的默认处理
QListWidget::mousePressEvent(event);
}
// 删除选中图片的函数
void ImageListWidget::deleteSelectedImage()
{
QListWidgetItem *currentItem = this->currentItem();// 获取当前选中的项目
if (!currentItem) {
return;
}
int row = this->row(currentItem);// 获取项目所在行
delete takeItem(row); // 删除项目
emit imageCountChanged(count());// 发射图片数量变化信号--如果主框架有数量统计的需求,可以同步更新
emit imageOrderChanged();// 发射图片顺序变化信号
//qDebug() << "删除图片,当前剩余数量:" << count();
}
// 添加单个图片
void ImageListWidget::addImage(const QString &filePath)
{
QPixmap pixmap(filePath);// 从文件路径加载图片
if (pixmap.isNull()) return;
// 缩放图片到图标大小,保持宽高比
QPixmap scaledPixmap = pixmap.scaled(m_iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
// 创建列表项目,显示图标和文件名
QListWidgetItem *item = new QListWidgetItem(QIcon(scaledPixmap), QFileInfo(filePath).fileName());
item->setData(Qt::UserRole, filePath);// 将完整文件路径存储为用户数据
item->setSizeHint(m_gridSize);// 设置项目大小提示
// 设置项目标志Qt::ItemIsEnabled:启用;Qt::ItemIsSelectable:可选;Qt::ItemIsDragEnabled:可拖拽
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
addItem(item);// 添加项目到列表
emit imageCountChanged(count());// 发射图片数量变化信号
}
// 添加多个图片的函数
void ImageListWidget::addImages(const QStringList &filePaths)
{
for (const QString &filePath : filePaths) {
addImage(filePath);
}
}
// 清空所有图片的函数
void ImageListWidget::clearAllImages()
{
clear();
emit imageCountChanged(0);
}
// 获取图片数量的函数
int ImageListWidget::imageCount() const
{
return count();
}
// 获取所有图片路径的函数
QStringList ImageListWidget::getImagePaths() const
{
QStringList paths;
for (int i = 0; i < count(); ++i) {
QListWidgetItem *item = this->item(i);
paths.append(item->data(Qt::UserRole).toString());// 从项目数据中获取路径并添加到列表
}
return paths;
}
// 拖拽进入事件处理函数
void ImageListWidget::dragEnterEvent(QDragEnterEvent *event)
{
// 检查拖拽数据是否包含URL或来自自身
if (event->mimeData()->hasUrls() || event->source() == this) {
event->acceptProposedAction();// 接受动作
} else {
event->ignore();// 忽略事件
}
}
// 拖拽移动事件处理函数
void ImageListWidget::dragMoveEvent(QDragMoveEvent *event)
{
// 检查拖拽数据是否包含URL或来自自身
if (event->mimeData()->hasUrls() || event->source() == this) {
event->acceptProposedAction();// 接受动作
} else {
event->ignore();// 忽略事件
}
}
// 拖放事件处理函数
void ImageListWidget::dropEvent(QDropEvent *event)
{
const QMimeData *mimeData = event->mimeData();// 获取拖拽的MIME数据
// 处理来自自身的拖拽(内部重排序)
if (event->source() == this) {
QListWidgetItem *draggedItem = currentItem();// 获取被拖拽的项目
if (!draggedItem) return;
QPoint pos = event->pos();// 获取拖放位置
QListWidgetItem *targetItem = itemAt(pos);// 获取目标位置的项目
//项目拖拽了,但不代表item中的顺序会同步变化,以下手动方式排列
// 如果目标项目存在且不是被拖拽项目本身
if (targetItem && targetItem != draggedItem) {
// 获取拖动前和拖动后的索引值,比如从2->6
int draggedIndex = row(draggedItem);
int targetIndex = row(targetItem);
// 提取出被拖拽项目,把2提取出(比如一个list链表,2被提取之后,2之后的就会自动往前排)
QListWidgetItem *item = takeItem(draggedIndex);
//把提取出的被拖拽项目插入到目标位置,2项的数据就插入到6位置了
insertItem(targetIndex, item);
setCurrentItem(item);//设置当前选中项目,设置6位置为选中项,保持原来选中的项内容不变
// 发射图片顺序变化信号
emit imageOrderChanged();
}
event->acceptProposedAction();
} else if (mimeData->hasUrls()) {// 处理来自外部的文件拖放
QStringList filePaths;
QList<QUrl> urlList = mimeData->urls();// 获取所有URL
for (const QUrl &url : urlList) {
QString filePath = url.toLocalFile();// 转换为本地文件路径
QFileInfo fileInfo(filePath);
QString extension = fileInfo.suffix().toLower();// 获取文件扩展名并转换为小写
// 检查是否为支持的图片格式
if (extension == "png" || extension == "jpg" ||
extension == "jpeg" || extension == "bmp" || extension == "gif") {
filePaths.append(filePath);// 添加到文件路径列表
}
}
if (!filePaths.isEmpty()) {
addImages(filePaths);//添加图片
}
event->acceptProposedAction();
} else {
event->ignore();// 忽略不支持的拖放
}
}
// 开始拖拽操作函数
void ImageListWidget::startDrag(Qt::DropActions supportedActions)
{
QListWidgetItem *item = currentItem();// 获取当前选中的项目
if (!item) return;
QDrag *drag = new QDrag(this);// 创建拖拽对象
QMimeData *mimeData = new QMimeData(); // 创建MIME数据对象
// 从项目数据中获取文件路径
QString filePath = item->data(Qt::UserRole).toString();
QList<QUrl> urls;
urls.append(QUrl::fromLocalFile(filePath));// 添加文件URL到URL列表
mimeData->setUrls(urls);// 设置MIME数据的URL
// 获取项目图标并转换为指定大小的像素图
QPixmap pixmap = item->icon().pixmap(m_iconSize);
// 创建稍大的透明拖拽图标
QPixmap dragPixmap(m_iconSize + QSize(20, 20));
dragPixmap.fill(Qt::transparent);
// 在拖拽图标上绘制半透明的原图标
QPainter painter(&dragPixmap);
painter.setRenderHint(QPainter::Antialiasing);// 设置抗锯齿
painter.setOpacity(0.7);// 设置透明度为70%
painter.drawPixmap(10, 10, pixmap); // 绘制图标(偏移10像素)
painter.end();
drag->setMimeData(mimeData);// 设置拖拽对象的MIME数据
drag->setPixmap(dragPixmap);// 设置拖拽时显示的图标
// 设置拖拽热点(中心点)
drag->setHotSpot(QPoint(dragPixmap.width()/2, dragPixmap.height()/2));
// 执行拖拽操作
drag->exec(supportedActions, Qt::MoveAction);
}
// 验证拖拽顺序的函数(用于调试)
void ImageListWidget::verifyDragOrder()
{
qDebug() << "验证拖拽顺序 - 项目数量:" << count();
for (int i = 0; i < count(); ++i) {
QListWidgetItem *item = this->item(i);
qDebug() << "位置" << i << ":" << item->text()
<< "路径:" << item->data(Qt::UserRole).toString();
}
}
1、UI初始化
涉及以下4到要点:
- 内容显示模式:视图模式(网格图标模式)
- 显示内容的样式:网格大小、图标大小
- 选中方式:单选
- 拖放方式:可拖放移动
2、添加图片
对应的函数名称为:addImage()和addImages()。涉及到以下3要素:
- 显示的内容:缩小的图片+文件名称
- 存储数据:存储文件路径用于其他操作
- 设置拖拽属性:可拖拽
3、清空所有图片
直接调用QListWidget的clear()即可。
4、获取所有图片路径
获取添加图片时存储的数据(文件路径),方便做图片处理操作。
5、拖拽事件重载
包含3个函数
- 开始拖拽:startDrag
- 拖拽进入事件:dragEnterEvent
- 拖拽移动事件:dragMoveEvent
- 拖拽放下事件:dropEvent
开始拖拽startDrag()是拖拽初始化的操作,涉及以下5个要点:
- 创建拖拽对象
- 创建MIME数据对象,存储数据(文件路径)
- 设置显示的内容格式
- 设置拖拽移动效果
- 执行拖拽操作
dragEnterEvent与dragMoveEvent的代码相同,但两者是有区别的:
| 特性 | dragEnterEvent | dragMoveEvent |
|---|---|---|
| 触发时机 | 拖拽操作首次进入控件边界时触发一次 | 在控件区域内移动时连续触发 |
| 主要用途 | 决定是否允许进入控件区域 | 跟踪拖拽位置,为视觉反馈和drop做准备 |
| 执行频率 | 单次(进入时) | 高频(移动时) |
重点的交互是在dropEvent()拖拽放下时,需要调整已有图片项的排序。特别是内部拖放之后默认是不更新排序的,需要编写调整代码:
QListWidgetItem *draggedItem = currentItem();// 获取被拖拽的项目
if (!draggedItem) return;
QPoint pos = event->pos();// 获取拖放位置
QListWidgetItem *targetItem = itemAt(pos);// 获取目标位置的项目
//项目拖拽了,但不代表item中的顺序会同步变化,以下手动方式排列
// 如果目标项目存在且不是被拖拽项目本身
if (targetItem && targetItem != draggedItem) {
// 获取拖动前和拖动后的索引值,比如从2->6
int draggedIndex = row(draggedItem);
int targetIndex = row(targetItem);
// 提取出被拖拽项目,把2提取出(比如一个list链表,2被提取之后,2之后的就会自动往前排)
QListWidgetItem *item = takeItem(draggedIndex);
//把提取出的被拖拽项目插入到目标位置,2项的数据就插入到6位置了
insertItem(targetIndex, item);
setCurrentItem(item);//设置当前选中项目,设置6位置为选中项,保持原来选中的项内容不变
// 发射图片顺序变化信号
emit imageOrderChanged();
}
- takeItem(draggedIndex)从指定位置取出节点项
- insertItem(targetIndex, item)在指定位置插入取出的节点项
以上这两项操作可以用链表的特性来理解,比如:节点2插入到节点6,即是把节点2从链表中取出,此时节点3移动到节点2位置,以此类推节点6移动到节点5的位置,然后节点2再插入到节点6的位置,即节点5的后面,此时的效果就算节点2移动到了节点6的效果。
6、鼠标单击事件重载
涉及右键菜单功能,监测右键单击事件,右键会被主框架劫持,这里是发生信号到主框架,由主框架处理右键事件。
if (event->button() == Qt::RightButton) {//测右键单击事件
emit requestContextMenu(event->pos());// 发送信号--由主框架统一处理
event->accept();// 吸收事件
return;
}
7、鼠标双击事件重载
涉及左键双击显示图片预览功能,这里是发生信号到主框架,由主框架处理双击事件
if (event->button() == Qt::LeftButton) {//检测双击事件
QListWidgetItem *item = itemAt(event->pos());// 获取点击位置的项目
if (item) {
QString filePath = item->data(Qt::UserRole).toString();//获取文件路径
qDebug() << "双击预览图片:" << filePath;
emit imageDoubleClicked(filePath);// 发送信号--由主框架统一处理
event->accept();// 吸收事件
return;
}
}
主框架功能
mainwindow引用自定义控件
在UI界面添加一个QListWidget控件,右键点击"升级为",设置为ImageListWidget类,具体的操作可以参考我之前分享的博文:
【QT常用技术讲解】自定义支持多选项的下拉框
https://blog.csdn.net/liangyuna8787/article/details/152454461
1、添加图片
1.1、点击按钮方式添加
在on_pushButton_clicked按钮事件中调用addImages()
1.2、拖拽方式从外部添加
重载拖拽函数:dragEnterEvent、dragMoveEvent、dropEvent,只监测外部拖拽进来的事件。
if (mimeData->hasUrls())//只判断是不是从外部拖拽进来的事件
并且在拖放时,调用addImages增加图片到图片项中。
// 遍历所有URL,转换为本地文件路径
for (const QUrl &url : urlList) {
QString filePath = url.toLocalFile();
if (isImageFile(filePath)) {
filePaths.append(filePath);
}
}
if (!filePaths.isEmpty()) {
ui->imageListWidget->addImages(filePaths);// 添加图片
}
2、删除图片
2.1、点击按钮清空图片
调用clearAllImages进行清空。
2.2、点击键盘Delete键删除选中图片
设置快捷键:QKeySequence::Delete
m_deleteImageAction = new QAction("删除图片", this);// 创建删除动作
connect(m_deleteImageAction, &QAction::triggered, this, &MainWindow::deleteSelectedImage);
m_deleteImageAction->setShortcut(QKeySequence::Delete);// 设置快捷键为Delete键
this->addAction(m_deleteImageAction);//将动作添加到窗口,使快捷键全局生效
2.3、右键菜单删除选中的图片
上面已经可以实现删除功能了,这里只是为了演示右键菜单功能的设置
m_imageListContextMenu = new QMenu(this);// 创建右键菜单
m_deleteImageAction = new QAction("删除图片", this);// 创建删除动作
m_deleteImageAction->setStatusTip("删除选中的图片");// 设置状态栏提示
m_imageListContextMenu->addAction(m_deleteImageAction);// 将动作添加到菜单
connect(m_deleteImageAction, &QAction::triggered, this, &MainWindow::deleteSelectedImage);
以上2给的删除动作都是调用槽函数deleteSelectedImage实现的。
3、上移/下移图片
核心功能与ImageListWidget中的拖放重载函数dropEvent一样,都是先takeItem(currentRow)提取出需要移动位置的图片项,然后insertItem(currentRow - 1, currentItem)/insertItem(currentRow+1, currentItem)来实现移动。
4、生成PDF
在按钮事件on_generatePdfButton_clicked中实现,比较简单,通过QT的QPrinter模块生成PDF文件。
5、左键双击图片预览图片
接收ImageListWidget发过来的信号后,通过槽函数previewImage响应,引用了一个自定义预览对话框ImagePreviewDialog,自定义对话框代码如下:
//imagepreviewdialog.h
#ifndef IMAGEPREVIEWDIALOG_H
#define IMAGEPREVIEWDIALOG_H
#include <QDialog>
#include <QLabel>
#include <QScrollArea>
#include <QAction>
#include <QToolBar>
#include <QString>
#include <QPixmap>
#include <QTimer>
class ImagePreviewDialog : public QDialog
{
Q_OBJECT
public:
explicit ImagePreviewDialog(const QString &imagePath, QWidget *parent = nullptr);
~ImagePreviewDialog() = default;
protected:
void keyPressEvent(QKeyEvent *event) override;// 重写键盘事件处理函数
void wheelEvent(QWheelEvent *event) override;// 重写鼠标滚轮事件处理函数
void showEvent(QShowEvent *event) override;// 重写显示事件处理函数(窗口显示时触发)
private:
void setupUI();
void setupToolbar();
void loadImage(const QString &imagePath);
void updateImageDisplay();
void scaleImage(double factor);
void adjustScrollBar(QScrollBar *scrollBar, double factor);
void calculateOptimalSize();
// 界面控件
QLabel *m_imageLabel; // 显示图片的标签
QScrollArea *m_scrollArea; // 滚动区域(用于显示大图片)
QToolBar *m_toolBar; // 工具栏
// 图片数据
QPixmap m_originalPixmap; // 原始图片数据
double m_scaleFactor; // 当前缩放比例
int m_rotationAngle; // 旋转角度(0, 90, 180, 270度)
QString m_currentImagePath; // 当前图片路径
int m_currentImageIndex; // 当前图片索引(预留,用于多图片浏览)
// 工具栏动作
QAction *m_zoomInAction; // 放大动作
QAction *m_zoomOutAction; // 缩小动作
QAction *m_fitToWindowAction; // 适应窗口动作
QAction *m_originalSizeAction; // 原始大小动作
QAction *m_rotateLeftAction; // 左旋转动作
QAction *m_rotateRightAction; // 右旋转动作
private slots:
void zoomIn(); // 放大图片
void zoomOut(); // 缩小图片
void fitToWindow(); // 适应窗口显示
void originalSize(); // 原始大小显示
void rotateLeft(); // 向左旋转90度
void rotateRight(); // 向右旋转90度
};
#endif // IMAGEPREVIEWDIALOG_H
//imagepreviewdialog.cpp
#include "imagepreviewdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QToolBar>
#include <QFileInfo>
#include <QApplication>
#include <QScreen>
#include <QMessageBox>
#include <QKeyEvent>
#include <QScrollBar>
#include <QClipboard>
#include <QFileDialog>
#include <QGuiApplication>
#include <QShowEvent>
#include <QDesktopWidget>
ImagePreviewDialog::ImagePreviewDialog(const QString &imagePath, QWidget *parent)
: QDialog(parent)
, m_scaleFactor(1.0)// 初始化缩放因子为1.0(原始大小)
, m_rotationAngle(0) // 初始化旋转角度为0度
, m_currentImageIndex(-1)// 初始化当前图片索引为-1(单图模式)
{
setWindowTitle("图片预览 - " + QFileInfo(imagePath).fileName());
setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint);
setMinimumSize(600, 500); // 增大最小尺寸
setupUI();
setupToolbar();// 设置工具栏
loadImage(imagePath);// 加载图片
}
// 设置UI布局和组件
void ImagePreviewDialog::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(2, 2, 2, 2); // 减少边距
// 创建工具栏
m_toolBar = new QToolBar(this);
m_toolBar->setMovable(false); // 禁止拖动工具栏
// 创建动作
m_zoomInAction = new QAction("放大", this);
m_zoomOutAction = new QAction("缩小", this);
m_fitToWindowAction = new QAction("适应窗口", this);
m_originalSizeAction = new QAction("原始大小", this);
m_rotateLeftAction = new QAction("左旋转", this);
m_rotateRightAction = new QAction("右旋转", this);
// 设置快捷键
m_zoomInAction->setShortcut(QKeySequence::ZoomIn);
m_zoomOutAction->setShortcut(QKeySequence::ZoomOut);
m_fitToWindowAction->setShortcut(QKeySequence(Qt::Key_F));
m_originalSizeAction->setShortcut(QKeySequence(Qt::Key_R));
// 添加动作到工具栏
m_toolBar->addAction(m_zoomInAction);
m_toolBar->addAction(m_zoomOutAction);
m_toolBar->addSeparator();
m_toolBar->addAction(m_fitToWindowAction);
m_toolBar->addAction(m_originalSizeAction);
m_toolBar->addSeparator();
m_toolBar->addAction(m_rotateLeftAction);
m_toolBar->addAction(m_rotateRightAction);
// 创建滚动区域和图片标签
m_scrollArea = new QScrollArea(this);
m_imageLabel = new QLabel(this);
m_imageLabel->setBackgroundRole(QPalette::Base);
m_imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_imageLabel->setScaledContents(false);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_scrollArea->setBackgroundRole(QPalette::Dark);
m_scrollArea->setWidget(m_imageLabel);
m_scrollArea->setVisible(true);
m_scrollArea->setWidgetResizable(true); // 允许调整大小
// 添加到布局
mainLayout->addWidget(m_toolBar);
mainLayout->addWidget(m_scrollArea, 1); // 设置拉伸因子为1,让图片区域占据更多空间
// 连接信号槽
connect(m_zoomInAction, &QAction::triggered, this, &ImagePreviewDialog::zoomIn);
connect(m_zoomOutAction, &QAction::triggered, this, &ImagePreviewDialog::zoomOut);
connect(m_fitToWindowAction, &QAction::triggered, this, &ImagePreviewDialog::fitToWindow);
connect(m_originalSizeAction, &QAction::triggered, this, &ImagePreviewDialog::originalSize);
connect(m_rotateLeftAction, &QAction::triggered, this, &ImagePreviewDialog::rotateLeft);
connect(m_rotateRightAction, &QAction::triggered, this, &ImagePreviewDialog::rotateRight);
}
void ImagePreviewDialog::setupToolbar()
{
// 设置工具栏样式表
m_toolBar->setStyleSheet(
"QToolBar {" // 工具栏样式
" background-color: #f5f5f5;" // 背景色
" border-bottom: 1px solid #dddddd;" // 底部边框
" spacing: 5px;" // 间距
" padding: 3px;" // 内边距
"}"
"QToolButton {" // 工具按钮样式
" background-color: transparent;" // 透明背景
" border: 1px solid transparent;" // 透明边框
" border-radius: 3px;" // 圆角
" padding: 5px 8px;" // 内边距
" font-size: 12px;" // 字体大小
"}"
"QToolButton:hover {" // 鼠标悬停样式
" background-color: #e0e0e0;" // 背景色
" border: 1px solid #cccccc;" // 边框
"}"
"QToolButton:pressed {" // 鼠标按下样式
" background-color: #d0d0d0;" // 背景色
"}"
);
}
// 加载图片
void ImagePreviewDialog::loadImage(const QString &imagePath)
{
m_originalPixmap = QPixmap(imagePath);// 从文件路径加载图片
if (m_originalPixmap.isNull()) {
QMessageBox::warning(this, "错误", "无法加载图片: " + imagePath);
return;
}
m_currentImagePath = imagePath;
m_rotationAngle = 0;
// 显示图片
updateImageDisplay();
// 计算最佳显示尺寸
calculateOptimalSize();
}
// 计算最佳显示尺寸
void ImagePreviewDialog::calculateOptimalSize()
{
if (m_originalPixmap.isNull()) return;
// 获取屏幕尺寸
QScreen *screen = QApplication::primaryScreen();
QRect screenGeometry = screen->availableGeometry();
// 计算图片原始尺寸
QSize imageSize = m_originalPixmap.size();
// 计算对话框的理想尺寸(屏幕的70-80%)
int maxWidth = screenGeometry.width() * 0.8;
int maxHeight = screenGeometry.height() * 0.8;
// 如果图片比最大尺寸小,则按图片尺寸显示
if (imageSize.width() <= maxWidth && imageSize.height() <= maxHeight) {
// 图片较小,按原始大小显示,但加上一些边距
resize(imageSize.width() + 50, imageSize.height() + 100);
m_scaleFactor = 1.0;
} else {
// 图片较大,适应屏幕
double widthRatio = (double)maxWidth / imageSize.width();
double heightRatio = (double)maxHeight / imageSize.height();
m_scaleFactor = qMin(widthRatio, heightRatio) * 0.9; // 稍微缩小一点留出边距
// 设置对话框大小
QSize dialogSize = imageSize * m_scaleFactor;
dialogSize += QSize(50, 100); // 加上工具栏和边距的空间
resize(dialogSize);
}
updateImageDisplay();
}
// 更新图片显示
void ImagePreviewDialog::updateImageDisplay()
{
if (m_originalPixmap.isNull()) return;
// 应用旋转
QTransform transform;
transform.rotate(m_rotationAngle);
QPixmap rotatedPixmap = m_originalPixmap.transformed(transform, Qt::SmoothTransformation);
// 应用缩放
if (qFuzzyCompare(m_scaleFactor, 1.0)) {
m_imageLabel->setPixmap(rotatedPixmap);
} else {
QSize newSize = rotatedPixmap.size() * m_scaleFactor;
QPixmap scaledPixmap = rotatedPixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_imageLabel->setPixmap(scaledPixmap);
}
m_imageLabel->adjustSize();// 调整标签大小以适应图片
}
void ImagePreviewDialog::zoomIn()
{
scaleImage(1.25);// 放大25%
}
void ImagePreviewDialog::zoomOut()
{
scaleImage(0.8);// 缩小到80%
}
void ImagePreviewDialog::scaleImage(double factor)
{
m_scaleFactor *= factor;
// 限制缩放范围
if (m_scaleFactor < 0.1) m_scaleFactor = 0.1;
if (m_scaleFactor > 10.0) m_scaleFactor = 10.0;
updateImageDisplay();
// 调整滚动条以保持视图中心
adjustScrollBar(m_scrollArea->horizontalScrollBar(), factor);
adjustScrollBar(m_scrollArea->verticalScrollBar(), factor);
}
void ImagePreviewDialog::adjustScrollBar(QScrollBar *scrollBar, double factor)
{
scrollBar->setValue(int(factor * scrollBar->value() + ((factor - 1) * scrollBar->pageStep() / 2)));
}
// 适应窗口大小
void ImagePreviewDialog::fitToWindow()
{
if (m_originalPixmap.isNull()) return;
QSize scrollAreaSize = m_scrollArea->viewport()->size();
QSize imageSize = m_originalPixmap.size();
// 如果旋转了90或270度,则交换宽高
if (m_rotationAngle % 180 == 90) {
imageSize.transpose();
}
// 计算适应窗口的比例
double widthRatio = (double)scrollAreaSize.width() / imageSize.width();
double heightRatio = (double)scrollAreaSize.height() / imageSize.height();
m_scaleFactor = qMin(widthRatio, heightRatio) * 0.95; // 留一点边距
updateImageDisplay();
}
// 显示原始大小
void ImagePreviewDialog::originalSize()
{
m_scaleFactor = 1.0;
updateImageDisplay();
}
// 向左旋转
void ImagePreviewDialog::rotateLeft()
{
m_rotationAngle -= 90;// 减少90度
if (m_rotationAngle < 0) m_rotationAngle += 360;
updateImageDisplay();
// 旋转后重新适应窗口
QTimer::singleShot(50, this, &ImagePreviewDialog::fitToWindow);
}
void ImagePreviewDialog::rotateRight()
{
m_rotationAngle += 90;
if (m_rotationAngle >= 360) m_rotationAngle -= 360;
updateImageDisplay();
// 旋转后重新适应窗口
QTimer::singleShot(50, this, &ImagePreviewDialog::fitToWindow);
}
void ImagePreviewDialog::showEvent(QShowEvent *event)
{
QDialog::showEvent(event);
// 窗口显示后,确保图片正确适应
QTimer::singleShot(100, this, [this]() {
if (m_scaleFactor < 0.5) { // 如果缩放比例太小,重新适应窗口
fitToWindow();
}
});
}
// 键盘事件处理
void ImagePreviewDialog::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Escape: // ESC键:关闭窗口
close();
break;
case Qt::Key_Plus: // +键:放大
case Qt::Key_Equal: // =键:放大(与+键相同)
zoomIn();
break;
case Qt::Key_Minus: // -键:缩小
zoomOut();
break;
case Qt::Key_0: // 0键:原始大小
originalSize();
break;
case Qt::Key_1: // 1键:适应窗口
fitToWindow();
break;
case Qt::Key_Left: // 左箭头键:预留(可添加上一张图片功能)
break;
case Qt::Key_Right: // 右箭头键:预留(可添加下一张图片功能)
break;
default:
// 其他按键交给父类处理
QDialog::keyPressEvent(event);
}
}
// 鼠标滚轮事件处理
void ImagePreviewDialog::wheelEvent(QWheelEvent *event)
{
// 如果按住Ctrl键滚动滚轮,进行缩放操作
if (event->modifiers() & Qt::ControlModifier) {
if (event->angleDelta().y() > 0) {
// 向上滚动:放大
zoomIn();
} else {
// 向下滚动:缩小
zoomOut();
}
event->accept(); // 标记事件已处理
} else {
// 其他情况交给父类处理
QDialog::wheelEvent(event);
}
}