前言
有些与文件、图片相关桌面工具,为了提高交互性,允许把桌面的文件、图片直接拖拽到应用工具框体控件中,并且框体控件中也可以拖拽里面的文件、图片。本篇分享的是通过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
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);
    }
}