一个简单的基于QT的图像浏览器

news2024/11/28 3:48:07

以前学习前端的时候,对于多张图片的布局一般使用瀑布流布局(CSS总结——瀑布流布局_css 瀑布流_黑白程序员的博客-CSDN博客),然后再通过懒加载(如何实现图片懒加载,预加载!! - 简书)加载图片。但如果是在图片数量有限的情况下,无法得到整齐的布局。同时,鄙人也觉得桌面端向下滚动浏览图片实为不合理。所以在这篇文章中,我采用了横向滚动的方法,并将每一行图片使用QHeaderView布局图片,使得每一行图片都可以独立地进行滚动,以解决我们在上面提到的第一个问题。

不足:

1. 并没有实现所有行的滚动控制,只实现了单行,可以考虑额外添加一个滚动条。

2. 没有实现图片的懒加载。

3. 没有实现对图片的更多操作。

4. 不能移动查看图片窗口。

5. 只使用了最基础的知识,实现了最简单的功能,需要进一步改进。

最终实现的效果如下:

 浏览单张图片(双击图片)如下:

 单击返回图片浏览窗口

具体代码如下:

imgQLabel.h

#ifndef IMGLABEL_H
#define IMGLABEL_H

#include <QLabel>
#include <QMouseEvent>
#include <QWheelEvent>
#include <cmath>

class ImgLabel : public QLabel
{
    Q_OBJECT

public:
    explicit ImgLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QLabel(parent, f) {}

    void setImage(QPixmap pixmap)
    {
        _pixmap = pixmap;
    }

signals:
    void clicked();

protected:
    void mousePressEvent(QMouseEvent* event) override
    {
        /* QMouseEvent是Qt图形用户界面框架的一部分,它包含有关鼠标事件的信息,
         * 例如按下或释放的按钮、鼠标的位置、移动距离等。通过在类中重写mousePressEvent()函数
         * 并使用QMouseEvent获取有关鼠标单击的信息,我们可以创建自定义的可单击QLabel。*/

        if (event->button() == Qt::LeftButton)
        {
            emit clicked();
        }
        QLabel::mousePressEvent(event);
    };

//    void wheelEvent(QWheelEvent* event)
//    {
//        // 根据鼠标滚轮的滚动距离计算缩放因子
//        int numDegrees = event->delta() / 8;
//        double numSteps = numDegrees / 15.0;
//        double scalingFactor = std::pow(1.15, numSteps);

//        // 调整缩放因子,使得图片不会过大或过小
//        _scale_factor *= scalingFactor;
//        _scale_factor = std::max(0.1, std::min(_scale_factor, 2.0));

//        // 缩放图片并更新标签大小
//        QPixmap pixmap = _pixmap.scaled(_pixmap.width() * _scale_factor, _pixmap.height() * _scale_factor);
//        setPixmap(pixmap);
//        adjustSize();

//        // 接受滚轮事件
//        event->accept();
//    };


private:
    double _scale_factor=1.0;
    QPixmap _pixmap;
};


#endif // IMGLABEL_H

listImgQLabel.h

#ifndef LISTIMGLABEL_H
#define LISTIMGLABEL_H

#include <QLabel>
#include <QMouseEvent>
#include <QWheelEvent>

#include <cmath>

class ListImgLabel : public QLabel
{
    Q_OBJECT

public:
    explicit ListImgLabel(QWidget* parent = nullptr) : QLabel(parent) {}

signals:
    void doubleClicked();

protected:
    void mouseDoubleClickEvent(QMouseEvent* event) override
    {
        if (event->type() == QEvent::MouseButtonDblClick)
        {
            if (event->button() == Qt::LeftButton)
            {
                emit doubleClicked();
            }
        }
        QLabel::mouseDoubleClickEvent(event);
    }
};


#endif // LISTIMGLABEL_H

scrollWidget.h

#ifndef SCROLLWIDGET_H
#define SCROLLWIDGET_H

#include <QWidget>
#include <QWheelEvent>

class ScrollWidget : public QWidget
{
public:
    ScrollWidget(QWidget* parent = nullptr) : QWidget(parent) { }
    void setScrollWidget(QWidget* parent = nullptr) {
        if (parent != nullptr)
        {
            parent_widget = parent;
            QString type_name = typeid (parent).name();
//            qDebug() << parent << " " << type_name << endl;
        }
    }

    void scroll_widget(int scroll_num, QWheelEvent* event=nullptr)
    {
        parent_widget_width = parent_widget->width();
        widget_width = this->width();

        if (parent_widget_width >= widget_width)
        {
            event->accept();
            return;
        }

//        qDebug() << scrolled_x << " " << scroll_step * scroll_num << "  " << scroll_num << endl;

        if (scroll_num > 0)
        {
            if(scrolled_x - scroll_step * scroll_num < 0)
            {
                if(scrolled_x > 0)
                {
                    scroll(scrolled_x, 0);
                    scrolled_x = 0;
                }
                if(event != nullptr)
                    event->accept();
                return;
            }

            scroll(scroll_step * scroll_num, 0);
            scrolled_x -= scroll_step * scroll_num;
        }
        else
        {
            scroll_num = -scroll_num;
            if((scrolled_x + scroll_step * scroll_num) > (widget_width - parent_widget_width))
            {
                if ((widget_width - parent_widget_width) - scrolled_x > 0)
                {
                    scroll(-(widget_width - parent_widget_width - scrolled_x), 0);
                    scrolled_x = widget_width - parent_widget_width;
                }
                if(event != nullptr)
                    event->accept();
                return;
            }
            scroll(-scroll_step * scroll_num, 0);
            scrolled_x += scroll_step * scroll_num;
        }

    }

protected:
    void wheelEvent(QWheelEvent* event) override
    {
        if(event->angleDelta().y() > 0)
            scroll_widget(1);
        else
            scroll_widget(-1);
        event->accept();
    }
public:
    int scrolled_x = 0;
    int scroll_step = 22;
    int widget_width = 0;
    int parent_widget_width = 0;
private:
    QWidget* parent_widget;
};


#endif // SCROLLWIDGET_H

main.cpp

#include "mainwindow.h"

#include <QApplication>
#include <qdesktopwidget.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    /*窗口居中对齐*/
    QDesktopWidget *desktop = QApplication::desktop();
    w.move((desktop->width() - w.frameGeometry().width())/ 2, (desktop->height() - w.frameGeometry().height()) /2);

    w.show();
    return a.exec();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSpacerItem>


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    void setupUI();
    void setupLineUI();
    void readImages(const QString& directory);
    void displayImage(const QString& path, double scale_factor);
    void onLabelClicked();

    QVBoxLayout *line_layout;
    QHBoxLayout *line_1;
    QHBoxLayout *line_2;
    QHBoxLayout *line_3;

    QSpacerItem* spacer_line_1;
    QSpacerItem* spacer_line_2;
    QSpacerItem* spacer_line_3;

    int img_height = 140;

};
#endif // MAINWINDOW_H

mainwindow.cpp

#include <QCoreApplication>
#include <QDir>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QPixmap>
#include <QLabel>
#include <QFileDialog>
#include <QScrollArea>
//#include <QScrollBar>
#include <QProgressDialog>

#include<QDebug>

#include "scrollWidget.h"
#include "mainwindow.h"
#include "imgQLabel.h"
#include "listImgQLabel.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle("ImgReader");
    setupUI();
}

MainWindow::~MainWindow()
{
}

void MainWindow::setupUI()
{
    this->setFixedSize(800, img_height*3);
    QWidget* win = new QWidget(this);
    this->setCentralWidget(win);

    line_layout = new QVBoxLayout(win);

//    win->setStyleSheet("background-color: blue;");

    QScrollArea* line_1_scroll = new QScrollArea(win);
    line_1_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_1_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_1_scroll->setFrameShape(QFrame::NoFrame); // 去除frame边框
    ScrollWidget* line_1_widget = new ScrollWidget;

    QScrollArea* line_2_scroll = new QScrollArea(win);
    line_2_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_2_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_2_scroll->setFrameShape(QFrame::NoFrame);
    ScrollWidget* line_2_widget = new ScrollWidget;

    QScrollArea* line_3_scroll = new QScrollArea(win);
    line_3_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_3_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    line_3_scroll->setFrameShape(QFrame::NoFrame);
    ScrollWidget* line_3_widget = new ScrollWidget;

    line_1 = new QHBoxLayout();
    line_1->setContentsMargins(0,0,0,0); // setContentsMargins()设置布局的内容边距,依次为左,上,右,下
    line_2 = new QHBoxLayout();
    line_2->setContentsMargins(0,0,0,0);
    line_3 = new QHBoxLayout();
    line_3->setContentsMargins(0,0,0,0);

    QString imgPath = "imgs";   // 图片文件夹路径
    QDir dir(imgPath);

    // 检查文件夹是否存在
    if (!dir.exists()) {
        QString newPath = QFileDialog::getExistingDirectory(this, "选择文件夹", QDir::homePath());
        // 检查该文件夹是否存在(如果用户没有选择则 newPath 会为空字符串)
        if (!newPath.isEmpty() && dir.mkpath(newPath)) {
            dir.setPath(newPath);
        } else {
            qWarning("没有选择文件夹");
            return;
        }
    }
    QStringList nameFilter;
    nameFilter << "*.jpg" << "*.png" << "*.bmp";  // 图片类型过滤器
    QFileInfoList fileList = dir.entryInfoList(nameFilter);

    /*图片读取进度对话框*/
    QProgressDialog dialog(nullptr, nullptr, 0, fileList.size(), this);
    dialog.setFixedSize(420, 50);
    dialog.setWindowTitle(tr("正在从文件夹读取图片"));
    dialog.setWindowModality((Qt::WindowModal));
    dialog.show();

    // 显示图片和调整大小
    for (int i = 0; i < fileList.size(); i++) {
        QPixmap pixmap;
        pixmap.load(fileList[i].absoluteFilePath());
        pixmap = pixmap.scaledToHeight(img_height, Qt::FastTransformation);

        ListImgLabel* img_label = new ListImgLabel();
        img_label->setPixmap(pixmap);

        // 设置 QLabel 的 user data
        QVariant data;
        QString img_path = fileList[i].absoluteFilePath();
        data.setValue(img_path);
        img_label->setProperty("img_path", data);

        if (i % 3 == 0) {
            line_1->addWidget(img_label);
        } else if (i % 3 == 1) {
            line_2->addWidget(img_label);
        } else {
            line_3->addWidget(img_label);
        }

        connect(img_label, &ListImgLabel::doubleClicked, [this, img_path](){
//            qDebug() << img_path << endl;
            displayImage(img_path, 1.0);
        });

        dialog.setValue(i);
        QCoreApplication::processEvents(); // 避免界面冻结
        if(dialog.wasCanceled()) break;
    }
    dialog.close();

    line_1_widget->setLayout(line_1);
    line_2_widget->setLayout(line_2);
    line_3_widget->setLayout(line_3);

    line_1_scroll->setWidget(line_1_widget);
    line_2_scroll->setWidget(line_2_widget);
    line_3_scroll->setWidget(line_3_widget);

    line_1_widget->setScrollWidget(line_1_scroll);
    line_2_widget->setScrollWidget(line_2_scroll);
    line_3_widget->setScrollWidget(line_3_scroll);

    line_layout->addWidget(line_1_scroll);
    line_layout->addWidget(line_2_scroll);
    line_layout->addWidget(line_3_scroll);

    win->setLayout(line_layout);
}

void MainWindow::displayImage(const QString &path, double scale_factor=1.0)
{
    QPixmap pixmap(path);
    if (pixmap.isNull()) {
        qWarning("无法加载图片");
        return;
    }

    int img_width = static_cast<int>(pixmap.width() * scale_factor);
    int img_height = static_cast<int>(pixmap.height() * scale_factor);

    ImgLabel* label = new ImgLabel(this,Qt::Window | Qt::FramelessWindowHint); // 无边框窗口
    if(img_height > 800 || img_width > 800)
    {
        if(img_height > img_width)
        {
            img_width = static_cast<int>(800 * img_width / img_height);
            img_height = 800;
        }
        else
        {
            img_height = static_cast<int>(800 * img_height / img_width);
            img_width = 800;
        }
    }
    label->setPixmap(pixmap.scaled(img_width, img_height, Qt::KeepAspectRatio));

    label->setImage(pixmap);
    label->setAlignment(Qt::AlignCenter);
    label->resize(img_width, img_height);
    label->show();

    // 关闭显示窗口时删除QLabel对象
    connect(this, &MainWindow::destroyed, [=](){
        delete label;
    });

    // 点击返回按钮时关闭并返回至主窗口
    connect(label, &ImgLabel::clicked, [=](){
        label->close();
        this->show();
    });


    // 隐藏主窗口,显示图像
    this->hide();
}

资源下载地址:https://download.csdn.net/download/weixin_40679158/87785638

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/528789.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AutoDL使用百度网盘来进行数据的交互

文章目录 1. 简介 2. 操作2.1. 设置密码2.1.1. 登录网盘 2.2. 访问网盘中的数据2.3. 将数据放入到网盘中 1. 简介 AutoDL上面其实是可以直接使用百度网盘、阿里云盘等公共网盘的&#xff0c;这样传资料的时候就不用通过Xshell慢悠悠的传输了&#xff0c;如果有百度网盘会员的话…

[MTK7621] dhcp.script 脚本分析

该脚本只要完成IP地址、DNS和路由设置。由udhcpc程序调用。调用的函数在下面两个文件中定义&#xff1a; . /lib/functions.sh&#xff1a;定义了一些基础操作&#xff0c;例如配置文件获取等 . /lib/netifd/netifd-proto.sh&#xff1a;定义了IP、DNS和路由的处理函数 在dhcp.…

关于Photoshop中的【Stable-Diffusion WEBUI】插件:Auto.Photoshop.SD.plugin

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;PS的插件&#xff08;1.1&#xff09;安装&#xff08;1.2&#xff09;简评 &#xff08;零&#xff09;前言 本篇主要提到Photoshop中的Stable-Diffusion WEBUI插件&#xff0c;相对WEBUI并不算好用&#x…

MySQL 数据库面试题

TOC 1. MySQL 的内连接、左连接、右连接有有什么区别&#xff1f; inner join 内连接&#xff0c;在两张表进行连接查询时&#xff0c;只保留两张表中完全匹配的结果集。 left join 在两张表进行连接查询时&#xff0c;会返回左表所有的行&#xff0c;即使在右表中没有匹配的记…

STM32F401RET6 LQFP64 (Nucleo-F401RE) uart 打印输出

STM32F401RET6 LQFP64 (Nucleo-F401RE) uart 打印输出 1.STM32F401RET6 芯片情况 2.板子硬件 电源部分&#xff0c;开发板可利用JP5跳帽座子来选择使用USB供电或者使用管脚座子的外部Vin进行供电&#xff0c;无论是哪种供电&#xff0c;都需要经过U4的5V转成3.3V的LDO的再给S…

Simulink中如何获取所需变量对应的时间,并实时传输给其他模块

假设需要将时间信息传输给一个显示器模块,可以按照以下步骤进行操作: 在模型中添加“Clock”模块,将其输出与MATLAB函数“simulinktime”模块进行连接。 在模型中添加一个Scope模块,将其输入与Clock模块的输出进行连接。 运行模型,Scope模块将显示当前的仿真时间。 将Scop…

2023自动化测试选择什么工具或者框架好呢?

自动化测试的工具或者框架在市场上种类是比较繁多的。那么作为软件测试开发者应该怎么去选择呢&#xff1f;笔者觉得可以从测试需求、测试用例复杂度、技能水平和预算等方面去考虑。 Selenium 这是最常用的自动化测试工具之一&#xff0c;它可以模拟用户在不同的浏览器中进行操…

工厂人员定位系统源码,支持智能考勤、工时统计、行为检测、历史轨迹回放、人员管理、电子围栏功能

系统概述&#xff1a; 工厂人员定位系统&#xff0c;采用UWB定位技术&#xff0c;通过在厂区内部署一定数量的定位基站&#xff0c;以及为人员、车辆、物资佩戴标签卡的形式&#xff0c;实时获取人员精确位置&#xff0c;精度高达10cm。 工厂人员定位系统可实现物资/车辆实时定…

【Linux】】Linux权限的理解

一.Linux中的用户 Linux中分为两种用户&#xff0c;分别为&#xff1a; 1.root 用户&#xff0c;也叫超级用户&#xff0c;它的权限非常高&#xff0c;不受其他权限的约束&#xff0c;也就是可以为所欲为&#xff1b; 2.普通用户&#xff1a;除了root用户外&#xff0c;都是普通…

【2023春招】4399 web后台-Java后端开发

目录 一、JVM1.类加载过程2.static和final变量的初始化时机 二、依赖1.Spring Boot 自动装配2.使用依赖过程中遇到问题如何排查3.引入的某个依赖不符合预期&#xff0c;如何处理 三、数据库&#xff1a;1.InnoDB 和 MyISAM 索引的区别2.字符串类型字段&#xff0c;WHERE 数字&a…

枚举【Java】

文章目录 枚举的使用switch语句Enum类的常用方法枚举的构造方法 反射与枚举 在之前的学习中&#xff0c;如果我们需要组织一组枚举类型的数据&#xff0c;我们通常会使用常量来进行定义。但这种定义方式存在一个问题就是&#xff1a;如果在一个程序中&#xff0c;普通常量和使用…

1053 Path of Equal Weight(超级无敌详细注释+45行代码)

分数 30 全屏浏览题目 切换布局 作者 CHEN, Yue 单位 浙江大学 Given a non-empty tree with root R, and with weight Wi​ assigned to each tree node Ti​. The weight of a path from R to L is defined to be the sum of the weights of all the nodes along the pa…

UE5.1.1C++从0开始(6.两个额外的魔法弹:瞬移魔法弹和黑洞魔法弹)

做完这两个功能总共花费了一个下午加一个晚上的时间&#xff0c;瞬移魔法弹难度较低&#xff0c;黑洞魔法可能我的理解有误导致消耗时间较长&#xff0c;我会在下面把踩的坑写出来。 加上这个作业&#xff0c;我们一共做了三个魔法子弹了。同时那个老师也说我们可以写一个父类…

js中各种运算符

文章目录 扩展运算符&#xff1a;...逻辑运算符&#xff08;&& 或 || &#xff09;第一、&& (逻辑与)运算&#xff0c;看一个简单的例子&#xff1a;第二、|| (逻辑或)运算&#xff0c;看一个简单的例子&#xff1a;三、&& (逻辑与) 和||&#xff08;逻…

JMX vs JFR:谁才是最强大的JVM监控利器?

大家好&#xff0c;我是小米&#xff01;今天我们来聊一聊JVM监控系统&#xff0c;特别是关于JMX和JFR的使用。你是否有过在线上应用出现性能问题时&#xff0c;无法准确获取关键指标的困扰呢&#xff1f;那么&#xff0c;不妨听听我给大家带来的解决方案。 什么是JMX 首先&a…

什么是CTF?打CTF的意义是什么?(附网络安全入门教程)

什么是CTF? CTF在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。它起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。发展至今&#xff0c;已经成为全球范围网络安全圈流行的竞赛形式&#xff0…

使用mkdocs快速部署上线静态站点到Github

背景 mkdocs是一种方便地生成站点的工具&#xff0c;相比hugo、hexo等个人博客而言&#xff0c;mkdocs更加简便、轻量级&#xff0c;可以快速帮助部署上线类似个人技术本&#xff08;notebook&#xff0c;wiki&#xff09;之类的站点。并且支持默认支持站点内搜索&#xff0c;…

网络安全自学路线

很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习&#xff0c;最终也只是会无疾而终&#xff01;黑客是一个大的概念&#xff0c;里面包含了许多方向&#xff0c;不同的方向需要学习的内容也不一样。 算上从学校开始学习&#xff0c;已经在网安这条路上走…

基于ensp的mpls vpn跨域optionA方案(分公司之间不能互访但是能访问总部场景)

目录 一 实验拓扑 二 实验要求 三 实验分析 四 实验配置说明 4.1完成正常ip地址配置&#xff0c;ISP内部使用ospf协议和mpls ldp协议 4.2 PE和CE之间起ebgp邻居 4.3 PE和PE之间起mpbgp邻居 4.4 PE和CE、ASBR之间设置VRF来进行RT值的过滤&#xff0c;选择合适的RT值来完…

网络编程:TCP socket

文章目录 阅读前导 服务端定义日志框架成员属性服务端框架 初始化服务器创建套接字绑定开启监听 运行服务器netstat 工具获取连接和通信准备通信逻辑 单进程服务端函数&#xff08;version1&#xff09;telent 工具测试 多进程服务端&#xff08;version2&#xff09;创建子进程…