【QT常用技术讲解】可拖拽文件的Widget--QListWidget

前言

有些与文件、图片相关桌面工具,为了提高交互性,允许把桌面的文件、图片直接拖拽到应用工具框体控件中,并且框体控件中也可以拖拽里面的文件、图片。本篇分享的是通过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);
    }
}
相关推荐
isyangli_blog2 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008112 小时前
FastAPI APIRouter
开发语言·python
Benszen2 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆2 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木2 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充3 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~3 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6163 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草3 小时前
反射、Tomcat执行
java·开发语言
雪的季节4 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt