当前位置: 首页 > news >正文

开源 C++ QT QML 开发(五)复杂控件--Gridview

           文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 相关链接:

开源 C++ QT QML 开发(一)基本介绍

开源 C++ QT QML 开发(二)工程结构

开源 C++ QT QML 开发(三)常用控件

开源 C++ QT QML 开发(四)复杂控件--Listview

开源 C++ QT QML 开发(五)复杂控件--Gridview

推荐链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

开源 C# 快速开发(十一)线程

开源 C# 快速开发(十二)进程监控

开源 C# 快速开发(十三)进程--管道通讯

开源 C# 快速开发(十四)进程--内存映射

开源 C# 快速开发(十五)进程--windows消息

开源 C# 快速开发(十六)数据库--sqlserver增删改查

本章节主要内容是:介绍复杂控件GridView 的使用方法,GridView 是一个用于显示网格布局数据的视图组件,非常适合显示图片库、图标集合等网格状内容。

1.代码分析

2.所有源码

3.效果演示

一、代码分析

1. C++ 后端 (ImageLoader 类)
类定义分析
 

class ImageLoader : public QObject
{Q_OBJECTQ_PROPERTY(QString currentFolder READ currentFolder NOTIFY currentFolderChanged)


继承自 QObject,支持 Qt 的元对象系统

Q_PROPERTY 声明了可在 QML 中访问的 currentFolder 属性

成员函数分析


loadImages(const QString &folderPath)

void ImageLoader::loadImages(const QString &folderPath)
{QDir directory(folderPath);// 检查文件夹是否存在if (!directory.exists()) {qWarning() << "文件夹不存在:" << folderPath;return;}m_currentFolder = directory.dirName();emit currentFolderChanged(m_currentFolder);// 设置图片过滤器QStringList imageFilters;imageFilters << "*.jpg" << "*.jpeg" << "*.png" << "*.bmp" << "*.gif" << "*.webp";// 获取文件列表并按名称排序QFileInfoList fileList = directory.entryInfoList(imageFilters, QDir::Files, QDir::Name);// 遍历所有图片文件for (const QFileInfo &fileInfo : fileList) {QString name = fileInfo.fileName();QString path = fileInfo.absoluteFilePath();QString size = formatFileSize(fileInfo.size());QString date = fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss");emit imageFound(name, path, size, date);}
}


功能:加载指定文件夹中的所有图片文件
流程:

验证文件夹存在性

更新当前文件夹属性并发出信号

设置支持的图片格式过滤器

获取排序后的文件列表

遍历文件,提取信息并发出信号

loadSingleImage(const QString &filePath)

void ImageLoader::loadSingleImage(const QString &filePath)
{QFileInfo fileInfo(filePath);if (!fileInfo.exists()) {qWarning() << "文件不存在:" << filePath;return;}m_currentFolder = "单个文件";emit currentFolderChanged(m_currentFolder);// 提取单个文件信息并发出信号QString name = fileInfo.fileName();QString path = fileInfo.absoluteFilePath();QString size = formatFileSize(fileInfo.size());QString date = fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss");emit imageFound(name, path, size, date);
}


功能:加载单个图片文件
特点:设置特殊文件夹名称"单个文件"

formatFileSize(qint64 bytes)

QString ImageLoader::formatFileSize(qint64 bytes)
{if (bytes < 1024) {return QString::number(bytes) + " B";} else if (bytes < 1024 * 1024) {return QString::number(bytes / 1024.0, 'f', 1) + " KB";} else {return QString::number(bytes / (1024.0 * 1024.0), 'f', 1) + " MB";}
}


功能:将字节数转换为易读的文件大小格式
转换规则:

< 1024B:显示为 B

1024B ~ 1MB:显示为 KB(保留1位小数)

≥ 1MB:显示为 MB(保留1位小数)

2. QML 前端分析
主要窗口组件
图片预览窗口 (imagePreviewWindow)
 

Window {id: imagePreviewWindow// 模态对话框,阻止主窗口交互flags: Qt.Dialogmodality: Qt.ApplicationModal
}


功能:提供大图预览界面
特性:

模态对话框

支持上一张/下一张导航

异步图片加载

网格视图 (GridView)
 

GridView {id: gridViewmodel: imageModelcellWidth: 200cellHeight: 220// 关键属性:启用裁剪,防止内容溢出clip: true
}


布局:网格布局显示图片缩略图
性能优化:只渲染可见区域的项

关键 JavaScript 函数
folderToString(url)
 

function folderToString(url) {var path = url.toString()// 移除 file:/// 前缀if (path.startsWith("file:///")) {path = path.substring(8)}// 路径格式标准化path = path.replace(/\\/g, "/")return path
}


功能:将 QUrl 转换为文件系统路径
处理逻辑:

移除 file:/// 协议前缀

统一路径分隔符为 /

showPreviousImage() 和 showNextImage()
 

function showNextImage() {var currentIndex = -1// 查找当前图片在模型中的索引for (var i = 0; i < imageModel.count; i++) {if (imageModel.get(i).path === imagePreviewWindow.imageSource.replace("file:///", "")) {currentIndex = ibreak}}// 显示下一张图片if (currentIndex < imageModel.count - 1) {var nextImage = imageModel.get(currentIndex + 1)imagePreviewWindow.imageSource = "file:///" + nextImage.pathimagePreviewWindow.imageName = nextImage.name}
}


功能:实现图片导航
算法:

遍历模型查找当前图片索引

计算相邻索引

更新预览窗口内容

交互功能
图片项委托 (delegate)
 

MouseArea {anchors.fill: parenthoverEnabled: trueacceptedButtons: Qt.LeftButton | Qt.RightButtononClicked: {if (mouse.button === Qt.LeftButton) {// 左键:选择/取消选择parent.selected = !parent.selected} else if (mouse.button === Qt.RightButton) {// 右键:快速预览imagePreviewWindow.imageSource = "file:///" + model.pathimagePreviewWindow.imageName = model.nameimagePreviewWindow.show()}}onDoubleClicked: {// 双击:打开预览窗口imagePreviewWindow.show()}
}


交互设计:

左键单击:选择图片

右键单击:快速预览

双击:打开预览窗口

悬停:缩放效果

3. 数据流分析
信号-槽连接
 

Connections {target: imageLoaderonImageFound: {imageModel.append({name: name,path: path,size: size,date: date})}onCurrentFolderChanged: {imageModel.clear()  // 切换文件夹时清空模型}
}


数据流程:
用户操作 → 打开文件夹/文件

C++ 处理 → ImageLoader 扫描文件系统

信号发射 → imageFound 携带文件信息

QML 响应 → 更新 ListModel

界面更新 → GridView 重新渲染

二、所有源码

总共有4个文件

main.qml文件源码

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import Qt.labs.platform 1.1
import QtQuick.Layouts 1.14Window {width: 1000height: 700visible: truetitle: "图片浏览器"// 图片模型ListModel {id: imageModel}// 图片预览窗口Window {id: imagePreviewWindowwidth: 800height: 600title: "图片预览"flags: Qt.Dialogmodality: Qt.ApplicationModalvisible: falseproperty string imageSource: ""property string imageName: ""Rectangle {anchors.fill: parentcolor: "#2c3e50"ColumnLayout {anchors.fill: parentanchors.margins: 10spacing: 10// 标题Text {text: imagePreviewWindow.imageNamecolor: "white"font.pixelSize: 18font.bold: trueLayout.alignment: Qt.AlignHCenter}// 图片显示区域Rectangle {Layout.fillWidth: trueLayout.fillHeight: truecolor: "#34495e"radius: 5Image {id: previewImageanchors.fill: parentanchors.margins: 10source: imagePreviewWindow.imageSourcefillMode: Image.PreserveAspectFitsourceSize.width: 800sourceSize.height: 600// 加载中提示BusyIndicator {anchors.centerIn: parentrunning: previewImage.status === Image.Loading}}}// 按钮区域RowLayout {Layout.alignment: Qt.AlignHCenterspacing: 10Button {text: "上一张"onClicked: showPreviousImage()background: Rectangle {color: "#3498db"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}Button {text: "关闭"onClicked: imagePreviewWindow.close()background: Rectangle {color: "#e74c3c"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}Button {text: "下一张"onClicked: showNextImage()background: Rectangle {color: "#3498db"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}}}}}// 文件夹选择对话框FolderDialog {id: folderDialogtitle: "选择图片文件夹"onAccepted: {// 将 QUrl 转换为字符串路径var folderPath = folderToString(folderDialog.folder)console.log("选择的文件夹路径:", folderPath)imageLoader.loadImages(folderPath)}}// 文件选择对话框(单文件)- 使用 QtQuick.Dialogs 的 FileDialog// 替代方案:使用 Qt.labs.platform 的 FileDialogFileDialog {id: fileDialogtitle: "选择图片文件"nameFilters: ["图片文件 (*.jpg *.jpeg *.png *.bmp *.gif *.webp)"]onAccepted: {var filePath = folderToString(file)console.log("选择的文件路径:", filePath)imageLoader.loadSingleImage(filePath)}}// 函数:将 QUrl 转换为文件路径字符串function folderToString(url) {var path = url.toString()// 移除 file:/// 前缀if (path.startsWith("file:///")) {path = path.substring(8)}// 在 Windows 上,可能需要处理额外的斜杠path = path.replace(/\\/g, "/")return path}// 函数:显示上一张图片function showPreviousImage() {var currentIndex = -1for (var i = 0; i < imageModel.count; i++) {if (imageModel.get(i).path === imagePreviewWindow.imageSource.replace("file:///", "")) {currentIndex = ibreak}}if (currentIndex > 0) {var prevImage = imageModel.get(currentIndex - 1)imagePreviewWindow.imageSource = "file:///" + prevImage.pathimagePreviewWindow.imageName = prevImage.name}}// 函数:显示下一张图片function showNextImage() {var currentIndex = -1for (var i = 0; i < imageModel.count; i++) {if (imageModel.get(i).path === imagePreviewWindow.imageSource.replace("file:///", "")) {currentIndex = ibreak}}if (currentIndex < imageModel.count - 1) {var nextImage = imageModel.get(currentIndex + 1)imagePreviewWindow.imageSource = "file:///" + nextImage.pathimagePreviewWindow.imageName = nextImage.name}}Rectangle {anchors.fill: parentcolor: "#2c3e50"// 顶部工具栏Rectangle {id: toolbarwidth: parent.widthheight: 60color: "#34495e"Row {anchors.centerIn: parentspacing: 15Button {text: "打开文件夹"onClicked: folderDialog.open()background: Rectangle {color: "#3498db"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.pixelSize: 14}}Button {text: "打开文件"onClicked: fileDialog.open()background: Rectangle {color: "#9b59b6"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.pixelSize: 14}}Button {text: "清除所有"onClicked: imageModel.clear()background: Rectangle {color: "#e74c3c"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.pixelSize: 14}}Rectangle {width: 1height: 30color: "#7f8c8d"anchors.verticalCenter: parent.verticalCenter}Text {text: "图片数量: " + imageModel.countcolor: "white"font.pixelSize: 14font.bold: trueanchors.verticalCenter: parent.verticalCenter}Text {text: "当前文件夹: " + (imageLoader ? imageLoader.currentFolder : "未选择")color: "#bdc3c7"font.pixelSize: 12anchors.verticalCenter: parent.verticalCenter}}}// 图片网格视图GridView {id: gridViewanchors {top: toolbar.bottomleft: parent.leftright: parent.rightbottom: parent.bottommargins: 15}model: imageModelcellWidth: 200cellHeight: 220clip: truedelegate: Rectangle {width: gridView.cellWidth - 10height: gridView.cellHeight - 10color: "#34495e"radius: 8border.color: selected ? "#3498db" : "#7f8c8d"border.width: selected ? 3 : 1property bool selected: falseColumn {anchors.fill: parentanchors.margins: 8spacing: 8// 图片显示Rectangle {width: parent.widthheight: parent.height - 50color: "#2c3e50"radius: 5Image {id: imganchors.fill: parentanchors.margins: 3source: "file:///" + model.pathfillMode: Image.PreserveAspectFitsourceSize.width: 180sourceSize.height: 150asynchronous: true// 图片加载失败时显示错误图标onStatusChanged: {if (status === Image.Error) {console.log("图片加载失败:", model.path)}}}// 加载中提示BusyIndicator {anchors.centerIn: parentrunning: img.status === Image.Loadingwidth: 25height: 25}}// 图片信息Column {width: parent.widthspacing: 3Text {text: model.namecolor: "white"font.pixelSize: 12font.bold: trueelide: Text.ElideRightwidth: parent.width}Text {text: model.sizecolor: "#bdc3c7"font.pixelSize: 10width: parent.width}Text {text: model.datecolor: "#95a5a6"font.pixelSize: 9width: parent.width}}}// 鼠标交互区域MouseArea {anchors.fill: parenthoverEnabled: trueacceptedButtons: Qt.LeftButton | Qt.RightButtononClicked: {if (mouse.button === Qt.LeftButton) {// 左键选中/取消选中图片parent.selected = !parent.selectedconsole.log("选中图片: " + model.name)} else if (mouse.button === Qt.RightButton) {// 右键快速预览imagePreviewWindow.imageSource = "file:///" + model.pathimagePreviewWindow.imageName = model.nameimagePreviewWindow.show()}}onDoubleClicked: {// 双击查看大图imagePreviewWindow.imageSource = "file:///" + model.pathimagePreviewWindow.imageName = model.nameimagePreviewWindow.show()}onEntered: {parent.scale = 1.03parent.border.color = "#3498db"}onExited: {parent.scale = 1.0if (!parent.selected) {parent.border.color = "#7f8c8d"}}}// 缩放动画Behavior on scale {NumberAnimation { duration: 150 }}// 选中标记Rectangle {visible: selectedwidth: 22height: 22radius: 11color: "#3498db"anchors.top: parent.topanchors.right: parent.rightanchors.margins: 6Text {anchors.centerIn: parenttext: "✓"color: "white"font.bold: truefont.pixelSize: 12}}}// 空状态提示Label {anchors.centerIn: parenttext: "暂无图片\n请点击上方按钮打开文件夹或选择图片文件"color: "white"font.pixelSize: 16horizontalAlignment: Text.AlignHCentervisible: imageModel.count === 0}ScrollBar.vertical: ScrollBar {policy: ScrollBar.AsNeededbackground: Rectangle {color: "#34495e"}contentItem: Rectangle {color: "#3498db"radius: 3}}}}// 连接 C++ 信号到 QMLConnections {target: imageLoaderonImageFound: {imageModel.append({name: name,path: path,size: size,date: date})}onCurrentFolderChanged: {// 清除旧图片imageModel.clear()}}// 初始化时显示欢迎信息Component.onCompleted: {console.log("图片浏览器已启动")}
}

ImageLoader.h文件源码
#ifndef IMAGELOADER_H
#define IMAGELOADER_H#include <QObject>
#include <QString>
#include <QDir>
#include <QFileInfo>
#include <QFileInfoList>
#include <QDateTime>
#include <QUrl>
#include <QDebug>class ImageLoader : public QObject
{Q_OBJECTQ_PROPERTY(QString currentFolder READ currentFolder NOTIFY currentFolderChanged)public:explicit ImageLoader(QObject *parent = nullptr) : QObject(parent) {}QString currentFolder() const { return m_currentFolder; }Q_INVOKABLE void loadImages(const QString &folderPath);Q_INVOKABLE void loadSingleImage(const QString &filePath);signals:void imageFound(const QString &name, const QString &path, const QString &size, const QString &date);void currentFolderChanged(const QString &folder);private:QString m_currentFolder;QString formatFileSize(qint64 bytes);
};#endif // IMAGELOADER_H

ImageLoader.cpp文件源码
#include "ImageLoader.h"void ImageLoader::loadImages(const QString &folderPath)
{QDir directory(folderPath);if (!directory.exists()) {qWarning() << "文件夹不存在:" << folderPath;return;}m_currentFolder = directory.dirName();emit currentFolderChanged(m_currentFolder);// 支持的图片格式QStringList imageFilters;imageFilters << "*.jpg" << "*.jpeg" << "*.png" << "*.bmp" << "*.gif" << "*.webp";QFileInfoList fileList = directory.entryInfoList(imageFilters, QDir::Files, QDir::Name);qDebug() << "在文件夹中找到图片文件数量:" << fileList.count() << folderPath;for (const QFileInfo &fileInfo : fileList) {QString name = fileInfo.fileName();QString path = fileInfo.absoluteFilePath();QString size = formatFileSize(fileInfo.size());QString date = fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss");qDebug() << "找到图片:" << name << "路径:" << path;emit imageFound(name, path, size, date);}
}void ImageLoader::loadSingleImage(const QString &filePath)
{QFileInfo fileInfo(filePath);if (!fileInfo.exists()) {qWarning() << "文件不存在:" << filePath;return;}m_currentFolder = "单个文件";emit currentFolderChanged(m_currentFolder);QString name = fileInfo.fileName();QString path = fileInfo.absoluteFilePath();QString size = formatFileSize(fileInfo.size());QString date = fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss");emit imageFound(name, path, size, date);
}QString ImageLoader::formatFileSize(qint64 bytes)
{if (bytes < 1024) {return QString::number(bytes) + " B";} else if (bytes < 1024 * 1024) {return QString::number(bytes / 1024.0, 'f', 1) + " KB";} else {return QString::number(bytes / (1024.0 * 1024.0), 'f', 1) + " MB";}
}

main.cpp文件源码

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ImageLoader.h"int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 设置应用程序信息app.setApplicationName("图片浏览器");app.setApplicationVersion("1.0");QQmlApplicationEngine engine;// 注册 C++ 类到 QMLImageLoader imageLoader;engine.rootContext()->setContextProperty("imageLoader", &imageLoader);const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);}, Qt::QueuedConnection);engine.load(url);return app.exec();
}

三、效果演示

可以选择打开文件夹,也可以选择打开文件。

http://www.dtcms.com/a/446079.html

相关文章:

  • 下载建设银行官方网站工程承包合同协议书
  • 第九章:装饰器模式 - 动态增强的艺术大师
  • OpenAI 发布 GPT-5 Instant:AI 有了 “情感温度计“
  • 苏州做网站公司选苏州聚尚网络做百度百科的网站
  • SSE与轮询技术实时对比演示
  • 示范专业网站建设深圳联雅网站建设
  • php 8.4.13 更新日志
  • MongoDB 认证失败(错误码 18)
  • 深圳网站建设主页什么公司需要建立网站吗
  • 陕西省建设信息管理网站网站开发 家具销售 文献
  • 数学标准库
  • 怎么做跳转不影响原网站排名云抢购网官方网站
  • 漳州手机网站建设公司陕西专业网站建设哪家好
  • 利用 VsCode + EIDE 进行嵌入式开发(保姆级教程)
  • 长春企业网站制作优化微商好货源app下载
  • PlayerChoice系统介绍
  • 网站要实名认证旅游网站建设方案之目标
  • [ SpringWeb ] 搭建和配置
  • 高层次综合hls设计第一章
  • Docker 启动 Easysearch 时自定义初始密码的几种方式
  • 15.C++三大重要特性之继承
  • AI 训练大显存配置实战:24G 显存(RTX 4090)如何配 32G 内存?—— 从 “显存挪用” 到 “效率翻倍”
  • JVM即时编译
  • 【JVM】——结构组成和垃圾回收
  • 建站模板推荐设计公司的企业使命
  • php网站怎么做百度做网站不给FTP密码
  • 自由学习记录(105)
  • 聚类之KMeans
  • 汽车企业管理系统自己的网站怎么做关键词优化
  • 大模型落地的四大核心引擎:从技术突破到产业重构