QML用ListView实现带section的GridView

QML自带的GridView只能定义delegate,没有section,类似手机相册带时间分组标签的样式就没法做。最简单的方式就是组合ListView+GridView,或者ListView+Flow,但是嵌套View时,子级View一般是完全展开的,只显示该分组几行就得把该分组全部加载了,这样就没有了View在需要时才实例化Item的优势,所以最好还是在单层View实现最终效果。

QML的ListView支持section,可以自定义分组样式,所以可以通过ListView来实现带section的GridView。当然,你也可以直接修改GridView的C++源码给他加上section。

ListView实现GridView的效果无非就是把多行显示到一行。可以让ListView某一行撑高,其他行高度为0;也可以平均分配一行高度。因为delegate会被ListView控制位置,所以相对位置可以在内部嵌套然后设置偏移量,使之看起来在一行上。

本文完整代码:

https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20240205_SectionGrid

先实现一个不带section的GridView:

import QtQuick 2.15
import QtQuick.Controls 2.15// ListView 实现 GridView 效果
Rectangle {id: controlborder.color: "black"// 边距property int padding: 10// Item 间隔property int spacing: 10// Item 宽property int itemWidth: 300// Item 高property int itemHeight: 100// Delegate 宽property int delegateWidth: itemWidth + spacing// Delegate 高property int delegateHeight: itemHeight + spacing// 列数根据可视宽度和 Item 宽度计算property int columns: (list_view.width + spacing - padding) / delegateWidth < 1? 1: (list_view.width + spacing - padding) / delegateWidth// 套一层 Item clip 剪去 ListView 尾巴上多余的部分不显示出来Item {anchors.fill: parentanchors.margins: control.padding// 右侧留下滚动条位置,所以 columns 里 list_view.width 要减一个 paddinganchors.rightMargin: 0clip: trueListView {id: list_viewwidth: parent.width// 高度多一个 delegate 放置 footer,防止末尾的一行滑倒底部后隐藏// 多出来的一部分会被外部 Item clip 掉height: parent.height + control.delegateHeight + control.spacingflickableDirection: Flickable.HorizontalAndVerticalFlickboundsBehavior: Flickable.StopAtBoundsheaderPositioning: ListView.OverlayHeader// 底部多一个 footer 撑高可显示范围,防止末尾的一行滑倒底部后隐藏footerPositioning: ListView.OverlayFooterScrollBar.vertical: ScrollBar {// padding 加上 ListView 多出来的一部分bottomPadding: padding + (control.delegateHeight + control.spacing)// 常驻显示只是方便调试policy: ScrollBar.AlwaysOn}footer: Item {// 竖向的 ListView 宽度无所谓width: control.delegateWidth// 高度大于等于 delegate 高度才能保证显示height: control.delegateHeight}// 奇数方便测试model: 31delegate: Item {width: control.delegateWidth// 每行第一个 Item 有高度,后面的没高度,这样就能排列到一行// 因为 0 高度 Item 在末尾,超出范围 visible 就置为 false 了,所以才需要 footer 撑高多显示一行的内容// delegate 高度不一致会导致滚动条滚动时长度变化height: (model.index % control.columns === 0) ? control.delegateHeight : 0// 放置真正的内容Rectangle {// 根据列号计算 xx: (model.index % control.columns) * control.delegateWidth// 负高度就能和每行第一个的 y 一样y: (model.index % control.columns !== 0) ? -control.delegateHeight : 0width: control.itemWidthheight: control.itemHeightborder.color: "black"Text {anchors.centerIn: parent// 显示行号列号text: "(%1,%2)".arg(parseInt(model.index / control.columns)).arg(model.index % control.columns)}}}}}
}

如果要带section,就得每个分组有单独的index,这样才能计算分组内的行列号,需要我们自定义一个ListModel:

#pragma once
#include <QAbstractListModel>// 实际数据
struct DataInfo
{int value;// 本例用日期来分组QString date;
};// 分组信息,如 index
struct SectionInfo
{int index;
};class DataModel : public QAbstractListModel
{Q_OBJECT
private:enum ModelRole {ValueRole = Qt::UserRole, GroupNameRole, GroupIndexRole};
public:explicit DataModel(QObject *parent = nullptr);// Model 需要实现的必要接口int rowCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;QHash<int, QByteArray> roleNames() const override;// 在头部添加一个数据Q_INVOKABLE void appendData(int value, const QString &date);// 根据 model.index 删除一个数据Q_INVOKABLE void removeData(int index);// 加点测试数据void test();private:QVector<DataInfo> datas;QVector<SectionInfo> inners;
};DataModel::DataModel(QObject *parent): QAbstractListModel(parent)
{test();
}int DataModel::rowCount(const QModelIndex &parent) const
{if (parent.isValid())return 0;return datas.size();
}QVariant DataModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();auto &&item = datas.at(index.row());auto &&inner = inners.at(index.row());switch (role){case ValueRole: return item.value;case GroupNameRole: return item.date;case GroupIndexRole: return inner.index;}return QVariant();
}QHash<int, QByteArray> DataModel::roleNames() const
{static QHash<int, QByteArray> names{{ValueRole, "value"}, {GroupNameRole, "groupName"}, {GroupIndexRole, "groupIndex"}};return names;
}void DataModel::appendData(int value, const QString &date)
{// 先判断分组是否相同if (datas.isEmpty() || datas.first().date != date) {// 没有该组,新建一个分组DataInfo item;item.value = value;item.date = date;SectionInfo inner;inner.index = 0;beginInsertRows(QModelIndex(), 0, 0);datas.push_front(item);inners.push_front(inner);endInsertRows();} else {// 已有该组,插入并移动该组后面的 ItemDataInfo item;item.value = value;item.date = date;SectionInfo inner;inner.index = 0;beginInsertRows(QModelIndex(), 0, 0);datas.push_front(item);inners.push_front(inner);endInsertRows();// 刷新该组int update_count = 0;// 0 是新插入,1 是旧 0for (int i = 1; i < inners.size(); i++) {auto &&inner_i = inners[i];if (i > 1 && inner_i.index == 0)break;inner_i.index = i;update_count ++;}emit dataChanged(QAbstractListModel::index(1, 0), QAbstractListModel::index(1 + update_count, 0));}
}void DataModel::removeData(int index)
{if (index < 0 || index >= datas.size())return;beginRemoveRows(QModelIndex(), index, index);datas.removeAt(index);inners.removeAt(index);endRemoveRows();int update_count = 0;for (int i = index; i < inners.size(); i++) {auto &&inner_i = inners[i];if (inner_i.index == 0)break;inner_i.index -= 1;update_count ++;}if (update_count > 0) {emit dataChanged(QAbstractListModel::index(index, 0), QAbstractListModel::index(index + update_count, 0));}
}void DataModel::test()
{DataInfo item;SectionInfo inner;item.date = "2022.2.22";for (int i = 0; i < 11; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}item.date = "2010.10.10";for (int i = 0; i < 21; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}item.date = "1999.9.9";for (int i = 0; i < 31; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}
}
import QtQuick 2.15
import QtQuick.Controls 2.15
import Test 1.0// ListView 实现带 section 分组的 GridView
Rectangle {id: controlborder.color: "black"// 边距property int padding: 10// Item 间隔property int spacing: 10// Item 宽property int itemWidth: 300// Item 高property int itemHeight: 100// Delegate 宽property int delegateWidth: itemWidth + spacing// Delegate 高property int delegateHeight: itemHeight + spacing// 列数根据可视宽度和 Item 宽度计算property int columns: (list_view.width + spacing - padding) / delegateWidth < 1? 1: (list_view.width + spacing - padding) / delegateWidth// 套一层 Item clip 剪去 ListView 尾巴上多余的部分不显示出来Item {anchors.fill: parentanchors.margins: control.padding// 右侧留下滚动条位置,所以 columns 里 list_view.width 要减一个 paddinganchors.rightMargin: 0clip: trueListView {id: list_viewwidth: parent.width// 高度多一个 delegate 放置 footer,防止末尾的一行滑倒底部后隐藏// 多出来的一部分会被外部 Item clip 掉height: parent.height + control.delegateHeight + control.spacingflickableDirection: Flickable.HorizontalAndVerticalFlickboundsBehavior: Flickable.StopAtBoundsheaderPositioning: ListView.OverlayHeader// 底部多一个 footer 撑高可显示范围,防止末尾的一行滑倒底部后隐藏footerPositioning: ListView.OverlayFooterScrollBar.vertical: ScrollBar {// padding 加上 ListView 多出来的一部分bottomPadding: padding + (control.delegateHeight + control.spacing)// 常驻显示只是方便调试policy: ScrollBar.AlwaysOn}footer: Item {// 竖向的 ListView 宽度无所谓width: control.delegateWidth// 高度大于等于 delegate 高度才能保证显示height: control.delegateHeight}model: DataModel {id: list_model}section {property: "groupName"criteria: ViewSection.FullStringdelegate: Item {width: list_view.width - control.paddingheight: 40Rectangle {width: parent.widthheight: parent.height - control.spacingcolor: "gray"Text {anchors.centerIn: parenttext: sectioncolor: "white"}}}labelPositioning: ViewSection.InlineLabels}delegate: Item {width: control.delegateWidth// 每行第一个 Item 有高度,后面的没高度,这样就能排列到一行// 因为 0 高度 Item 在末尾,超出范围 visible 就置为 false 了,所以才需要 footer 撑高多显示一行的内容// delegate 高度不一致会导致滚动条滚动时长度变化height: (model.groupIndex % control.columns === 0) ? control.delegateHeight : 0// 放置真正的内容Rectangle {// 根据列号计算 xx: (model.groupIndex % control.columns) * control.delegateWidth// 负高度就能和每行第一个的 y 一样y: (model.groupIndex % control.columns !== 0) ? -control.delegateHeight : 0width: control.itemWidthheight: control.itemHeightborder.color: "black"Text {anchors.centerIn: parent// 显示行号列号text: "(%1,%2) - %3".arg(parseInt(model.groupIndex / control.columns)).arg(model.groupIndex % control.columns).arg(model.value)}Column {x: 12anchors.verticalCenter: parent.verticalCenterspacing: 12Button {width: 100height: 30text: "append"onClicked: {list_model.appendData(model.value, "2222.2.22")}}Button {width: 100height: 30text: "remove"onClicked: {list_model.removeData(model.index)}}}}} // end delegate Item} // end ListView}
}

这里只是实现了一个简单的效果,很多细节还需要调整。

通过添加更多的属性和计算,也可以实现带section的FlowView,即Item的宽高不是固定大小,整体为流式布局。

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

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

相关文章

Mybatis中的sql-xml延迟加载机制

Mybatis中的sql-xml延迟加载机制 hi&#xff0c;我是阿昌&#xff0c;今天记录一下关于Mybatis中的sql-xml延迟加载机制 一、前言 首先mybatis技术本身就不多介绍&#xff0c;说延迟加载机制之前&#xff0c;那要先知道2个概念&#xff1a; 主查询对象关联对象 假设咱们现…

WPF绘制矢量图形并绑定到界面的方法

这里先封装一个方法&#xff0c;使用的时候先创建一个Draw&#xff0c;拿到DrawingContext 之后就可以通过DrawingContext 去绘制想要的图形&#xff0c;绘制完成后通过GetDraw拿到所有绘制的结果 添加一个PrintImage()用于测试当前绘图的样子用于测试 public class DrawVector…

OpenCV 笔记(20):霍夫圆检测

1. 霍夫圆变换 霍夫圆变换(Hough Circle Transform)是一种数字图像处理中的特征提取技术&#xff0c;用于在图像中检测圆形。它将二维图像空间中一个圆转换为该圆半径、圆心横纵坐标所确定的三维参数空间中一个点的过程。因此&#xff0c;圆周上任意三点所确定的圆&#xff0c…

ElasticSearch查询语句用法

查询用法包括&#xff1a;match、match_phrase、multi_match、query_string、term 1.match 1.1 不同字段权重 如果需要为不同字段设置不同权重&#xff0c;可以考虑使用bool查询的should子句来组合多个match查询&#xff0c;并为每个match查询设置不同的权重 {"query&…

二叉树的详解

二叉树 【本节目标】 掌握树的基本概念掌握二叉树概念及特性掌握二叉树的基本操作完成二叉树相关的面试题练习 树型结构&#xff08;了解&#xff09; 概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。…

MySQL深入——18

我们来看看一主多从的情况 比如A是主库&#xff0c;A’ B C D都是副库&#xff0c;但A与A互为备库&#xff0c;当A库出现问题&#xff0c;我现在要将主库转到A’该怎么办。 以下是基于位点的主备切换 CHANGE MASTER TO MASTER_HOST$host_name MASTER_PORT$port MASTER_USER…

JS-本地文件上传

由于不需要原上传文件的样式&#xff0c;所以自己书写了一个按钮触发文件上传&#xff0c;并将原本的样式隐藏 <!doctype html> <html><head><meta charset"utf-8"><title>文件传输</title> </head><body><inpu…

工业笔记本丨行业三防笔记本丨亿道加固笔记本定制丨极端温度优势

工业笔记本是专为在恶劣环境条件下工作而设计的高度耐用的计算机设备。与传统消费者级笔记本电脑相比&#xff0c;工业笔记本在极端温度下展现出了许多优势。本文将探讨工业笔记本在极端温度环境中的表现&#xff0c;并介绍其优势。 耐高温性能: 工业笔记本具有更高的耐高温性…

QT 应用程序中集成浏览器

QT 应用程序中集成浏览器 前言 前言 在很多情况下&#xff0c;我们需要在应用程序中集成浏览器&#xff0c;比如应用程序界面是使用 H5 页面开发&#xff0c;或者我们的应用程序需要访问 Web 网站。 应用程序中集成浏览器&#xff0c;并不一定是需要一个具有地址栏、多标签等…

安卓动态链接库文件体积优化探索实践

背景介绍 应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面&#xff0c;据Google Play统计&#xff0c;应用体积每增加6MB&#xff0c;安装的转化率将下降1%。 安装包的体积受诸多方面影响&#xff0c;针对dex、资源文件、so文件都有不同的优化策略&…

【极数系列】Flink集成KafkaSource 实时消费数据(10)

文章目录 01 引言02 连接器依赖2.1 kafka连接器依赖2.2 base基础依赖 03 连接器使用方法04 消息订阅4.1 主题订阅4.2 正则表达式订阅4.3 Partition 列分区订阅 05 消息解析06 起始消费位点07 有界 / 无界模式7.1 流式7.2 批式 08 其他属性8.1 KafkaSource 配置项&#xff08;1&…

【ASP.NET Core 基础知识】--Web API--Swagger文档生成

Swagger是一种用于设计、构建和文档化Web API的开源工具。它提供了一套标准化的规范&#xff0c;使得开发者能够清晰地定义API端点、参数、请求和响应。通过Swagger&#xff0c;用户可以生成具有交互式UI的实时API文档&#xff0c;便于团队协作和第三方开发者理解和使用API。它…

如何训练自己的模型

无论数据类型或目标如何&#xff0c;用于训练和使用 AutoML 模型的工作流都是相同的&#xff1a; 准备训练数据。 我们需要将需要训练的数据准备为jsonl格式&#xff0c;这种格式的特点就是每一行都是json的格式 {"prompt": "<prompt text>", "…

机器学习之DeepSequence软件使用学习2-helper模块学习

在学习1中粗略地运行了一下软件的例子文件&#xff0c;但其中的很多东西都未能理解。该文中主要是对helper模块中代码的初步注释及学习以求能够熟练使用该软件。 from __future__ import print_function #from __future__ import print_function只在Python 2中有意义。在Pytho…

java执行可执行文件

文章目录 概要使用Runtime.exec使用ProcessBuilder使用第三方工具包commons-exec.jar 概要 java执行bat或shell脚本的方式主要有三种方式 1、 使用Runtime.exec 2、 使用ProcessBuilder 3、 使用第三方的工具包commons-exec.jar 使用Runtime.exec 在 Java 中&#xff0c;使用…

【初识爬虫+requests模块】

爬虫又称网络蜘蛛、网络机器人。本质就是程序模拟人使用浏览器访问网站&#xff0c;并将需要的数据抓取下来。爬虫不仅能够使用在搜索引擎领域&#xff0c;在数据分析、商业领域都得到了大规模的应用。 URL 每一个URL指向一个资源&#xff0c;可以是一个html页面&#xff0c;一…

配置git环境与项目创建

项目设计 名称&#xff1a;KOB 项目包含的模块 PK模块&#xff1a;匹配界面&#xff08;微服务&#xff09;、实况直播界面&#xff08;WebSocket协议&#xff09; 对局列表模块&#xff1a;对局列表界面、对局录像界面 排行榜模块&#xff1a;Bot排行榜界面 用户中心模块&…

从Kafka系统中读取消息数据——消费

从Kafka系统中读取消息数据——消费 消费 Kafka 集群中的主题消息检查消费者是不是单线程主题如何自动获取分区和手动分配分区subscribe实现订阅&#xff08;自动获取分区&#xff09;assign&#xff08;手动分配分区&#xff09; 反序列化主题消息反序列化一个类.演示 Kafka 自…

软件测试学习笔记-使用jmeter进行性能测试

性能测试&#xff1a;使用自动化工具&#xff0c;模拟不同的场景&#xff0c;对软件各项性能指标进行测试和评估的过程。 性能测试的目的&#xff1a; 评估当前系统的能力寻找性能瓶颈&#xff0c;优化性能评估软件是否能够满足未来的需要 性能测试和功能测试对比 焦点不同&…

基于FPGA的图像最近邻插值算法verilog实现,包括tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将FPGA数据导入matlab显示图片&#xff0c;效果如下&#xff1a; 2.算法运行软件版本 vivado2019.2&#xff0c;matlab2022a 3.部分核心程序 ti…