Skip to content

Commit 8cbec8b

Browse files
committed
[重构GridViewModel并优化UI初始化逻辑]: 全面重构GridViewModel模块架构,采用PIMPL设计模式提升代码质量,同时优化UI初始化过程避免阻塞
- **GridViewModel架构重构**: 将GridModel和GridView完全重构为PIMPL模式,改善封装性和编译依赖,重命名ImageInfo为GridCell,统一数据结构和命名规范 - **UI初始化机制优化**: 在CheckableTreeItem和Clock示例中将QTimer::singleShot替换为QMetaObject::invokeMethod,确保初始化代码在事件循环中异步执行,避免UI阻塞 - **代码质量提升**: 清理GridViewModel模块的CMakeLists.txt文件结构,统一源文件顺序,移除废弃的图片资源,更新README文档描述和示例图片 - **功能增强**: 为GridModel添加动态单元格尺寸调整能力,完善选中项处理逻辑,改进网格视图的交互体验和可维护性
1 parent ef93039 commit 8cbec8b

12 files changed

Lines changed: 266 additions & 182 deletions

File tree

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@
6565

6666
### [FlowLayout](https://doc.qt.io/qt-6/qtwidgets-layouts-flowlayout-example.html) - Qt 官方Flow Layout Example
6767

68-
### [GridViewModel](src/GridViewModel/) - 自适应网格视图
68+
# [GridViewModel](src/GridViewModel/) - 自适应网格布局
6969

70-
- 基于 QListView 的自适应网格布局
71-
- <img src="src/GridViewModel/picture/GridView.png" width="90%" alt="网格视图">
70+
- 基于 Qt Model-View 架构的网格布局组件
71+
- 支持自适应列数和自定义单元格
72+
- 内置多选功能和流畅的交互体验
73+
- <img src="src/GridViewModel/images/grid_view_model.png" width="800" alt="网格布局视图">
7274

7375
### [HttpClient](src/HttpClient/) - HTTP 客户端实现
7476

src/CheckableTreeItem/mainwindow.cc

Lines changed: 97 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -21,128 +21,132 @@ void buildFileSystemTreeImpl(QStandardItemModel *model,
2121
mainLayout->insertWidget(2, loadingLabel); // 插入到树视图上方
2222

2323
// 使用单次定时器延迟构建,确保UI先显示
24-
QTimer::singleShot(100, [=]() {
25-
loadingLabel->setText("Building root directory...");
24+
QMetaObject::invokeMethod(
25+
qApp,
26+
[=]() {
27+
loadingLabel->setText("Building root directory...");
2628

27-
// 获取根目录路径(跨平台)
28-
QList<QPair<QString, QString>> rootPaths;
29+
// 获取根目录路径(跨平台)
30+
QList<QPair<QString, QString>> rootPaths;
2931

3032
#ifdef Q_OS_WIN
31-
// Windows系统:获取所有驱动器
32-
QFileInfoList drives = QDir::drives();
33-
for (const QFileInfo &drive : drives) {
34-
rootPaths.append(qMakePair(drive.absoluteFilePath(), drive.absoluteFilePath()));
35-
}
33+
// Windows系统:获取所有驱动器
34+
QFileInfoList drives = QDir::drives();
35+
for (const QFileInfo &drive : drives) {
36+
rootPaths.append(qMakePair(drive.absoluteFilePath(), drive.absoluteFilePath()));
37+
}
3638
#else
37-
// Unix/Linux/Mac系统:使用用户主目录和常见系统目录
38-
rootPaths.append(qMakePair(QDir::homePath(), "Home Directory"));
39-
rootPaths.append(qMakePair("/", "Root Directory"));
40-
rootPaths.append(qMakePair("/etc", "System Configuration"));
41-
rootPaths.append(qMakePair("/usr", "User Programs"));
39+
// Unix/Linux/Mac系统:使用用户主目录和常见系统目录
40+
rootPaths.append(qMakePair(QDir::homePath(), "Home Directory"));
41+
rootPaths.append(qMakePair("/", "Root Directory"));
42+
rootPaths.append(qMakePair("/etc", "System Configuration"));
43+
rootPaths.append(qMakePair("/usr", "User Programs"));
4244
#endif
4345

44-
progressBar->setVisible(true);
45-
statusLabel->setVisible(true);
46+
progressBar->setVisible(true);
47+
statusLabel->setVisible(true);
4648

47-
int totalPaths = rootPaths.size();
48-
int processed = 0;
49+
int totalPaths = rootPaths.size();
50+
int processed = 0;
4951

50-
// 清空模型
51-
model->clear();
52+
// 清空模型
53+
model->clear();
5254

53-
// 构建根节点
54-
CheckableTreeItem *rootItem = new CheckableTreeItem("Local File System");
55-
model->appendRow(rootItem);
55+
// 构建根节点
56+
CheckableTreeItem *rootItem = new CheckableTreeItem("Local File System");
57+
model->appendRow(rootItem);
5658

57-
// 递归构建文件树函数
58-
std::function<void(CheckableTreeItem *, const QString &, int)> buildTree;
59-
buildTree = [&](CheckableTreeItem *parentItem, const QString &path, int depth) {
60-
if (depth >= 5) { // 限制最大层级为5层
61-
return;
62-
}
59+
// 递归构建文件树函数
60+
std::function<void(CheckableTreeItem *, const QString &, int)> buildTree;
61+
buildTree = [&](CheckableTreeItem *parentItem, const QString &path, int depth) {
62+
if (depth >= 5) { // 限制最大层级为5层
63+
return;
64+
}
6365

64-
QDir dir(path);
65-
if (!dir.exists()) {
66-
return;
67-
}
66+
QDir dir(path);
67+
if (!dir.exists()) {
68+
return;
69+
}
6870

69-
// 设置过滤器:只显示目录,不显示隐藏文件和系统文件
70-
dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
71-
dir.setSorting(QDir::Name | QDir::IgnoreCase);
71+
// 设置过滤器:只显示目录,不显示隐藏文件和系统文件
72+
dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
73+
dir.setSorting(QDir::Name | QDir::IgnoreCase);
7274

73-
QFileInfoList entries;
74-
try {
75-
entries = dir.entryInfoList();
76-
} catch (...) {
77-
// 跳过无权限访问的目录
78-
return;
79-
}
75+
QFileInfoList entries;
76+
try {
77+
entries = dir.entryInfoList();
78+
} catch (...) {
79+
// 跳过无权限访问的目录
80+
return;
81+
}
8082

81-
// 限制每个目录下最多显示10个子项,避免过多
82-
int maxChildren = 10;
83-
int count = 0;
83+
// 限制每个目录下最多显示10个子项,避免过多
84+
int maxChildren = 10;
85+
int count = 0;
8486

85-
for (const QFileInfo &entry : entries) {
86-
if (count >= maxChildren)
87-
break;
87+
for (const QFileInfo &entry : entries) {
88+
if (count >= maxChildren)
89+
break;
8890

89-
QString entryName = entry.fileName();
90-
QString fullPath = entry.absoluteFilePath();
91+
QString entryName = entry.fileName();
92+
QString fullPath = entry.absoluteFilePath();
9193

92-
// 跳过一些系统目录和无关目录
93-
if (entryName.startsWith(".") || entryName == "System32" || entryName == "Windows"
94-
|| entryName == "proc" || entryName == "sys" || entryName == "dev") {
95-
continue;
96-
}
94+
// 跳过一些系统目录和无关目录
95+
if (entryName.startsWith(".") || entryName == "System32"
96+
|| entryName == "Windows" || entryName == "proc" || entryName == "sys"
97+
|| entryName == "dev") {
98+
continue;
99+
}
97100

98-
CheckableTreeItem *item = new CheckableTreeItem(entryName);
99-
item->setToolTip(fullPath);
100-
parentItem->appendRow(item);
101+
CheckableTreeItem *item = new CheckableTreeItem(entryName);
102+
item->setToolTip(fullPath);
103+
parentItem->appendRow(item);
101104

102-
// 递归构建子目录(深度+1)
103-
buildTree(item, fullPath, depth + 1);
105+
// 递归构建子目录(深度+1)
106+
buildTree(item, fullPath, depth + 1);
104107

105-
count++;
106-
}
107-
};
108+
count++;
109+
}
110+
};
108111

109-
// 为每个根路径构建子树
110-
for (const auto &rootPair : rootPaths) {
111-
QString path = rootPair.first;
112-
QString displayName = rootPair.second;
112+
// 为每个根路径构建子树
113+
for (const auto &rootPair : rootPaths) {
114+
QString path = rootPair.first;
115+
QString displayName = rootPair.second;
113116

114-
statusLabel->setText(QString("Scanning: %1").arg(path));
115-
qApp->processEvents(); // 处理事件,更新UI
117+
statusLabel->setText(QString("Scanning: %1").arg(path));
118+
qApp->processEvents(); // 处理事件,更新UI
116119

117-
CheckableTreeItem *driveItem = new CheckableTreeItem(displayName);
118-
driveItem->setToolTip(path);
119-
rootItem->appendRow(driveItem);
120+
CheckableTreeItem *driveItem = new CheckableTreeItem(displayName);
121+
driveItem->setToolTip(path);
122+
rootItem->appendRow(driveItem);
120123

121-
buildTree(driveItem, path, 1);
124+
buildTree(driveItem, path, 1);
122125

123-
processed++;
124-
progressBar->setValue((processed * 100) / totalPaths);
125-
qApp->processEvents(); // 处理事件,更新UI
126-
}
126+
processed++;
127+
progressBar->setValue((processed * 100) / totalPaths);
128+
qApp->processEvents(); // 处理事件,更新UI
129+
}
127130

128-
// 完成构建,隐藏进度条和加载标签
129-
progressBar->setVisible(false);
130-
statusLabel->setVisible(false);
131-
loadingLabel->setVisible(false);
132-
loadingLabel->deleteLater();
131+
// 完成构建,隐藏进度条和加载标签
132+
progressBar->setVisible(false);
133+
statusLabel->setVisible(false);
134+
loadingLabel->setVisible(false);
135+
loadingLabel->deleteLater();
133136

134-
// 展开根节点
135-
treeView->expand(rootItem->index());
137+
// 展开根节点
138+
treeView->expand(rootItem->index());
136139

137-
// 显示统计信息
138-
int totalItems = countTreeItems(rootItem);
139-
statusLabel->setText(QString("Loaded %1 directory items").arg(totalItems));
140-
statusLabel->setVisible(true);
140+
// 显示统计信息
141+
int totalItems = countTreeItems(rootItem);
142+
statusLabel->setText(QString("Loaded %1 directory items").arg(totalItems));
143+
statusLabel->setVisible(true);
141144

142-
// 初始计数
143-
int checkedCount = countCheckedItems(rootItem);
144-
countLabel->setText(QString("Checked Items: %1").arg(checkedCount));
145-
});
145+
// 初始计数
146+
int checkedCount = countCheckedItems(rootItem);
147+
countLabel->setText(QString("Checked Items: %1").arg(checkedCount));
148+
},
149+
Qt::QueuedConnection);
146150
}
147151

148152
} // namespace

src/Clock/mainwindow.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,13 @@ MainWindow::MainWindow(QWidget *parent)
333333

334334
// 初始化状态
335335
clock->updateTime();
336-
QTimer::singleShot(100, [timeLabel]() {
337-
timeLabel->setText(tr("Current time: %1").arg(QTime::currentTime().toString("hh:mm:ss")));
338-
});
336+
QMetaObject::invokeMethod(
337+
this,
338+
[timeLabel]() {
339+
timeLabel->setText(
340+
tr("Current time: %1").arg(QTime::currentTime().toString("hh:mm:ss")));
341+
},
342+
Qt::QueuedConnection);
339343
}
340344

341345
MainWindow::~MainWindow() {}

src/GridViewModel/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
set(PROJECT_SOURCES
2-
main.cpp
3-
mainwindow.cpp
4-
mainwindow.h
52
gridmodel.h
63
gridmodel.cpp
74
gridview.h
8-
gridview.cpp)
5+
gridview.cpp
6+
main.cpp
7+
mainwindow.cpp
8+
mainwindow.h)
99

1010
qt_add_executable(GridViewModel MANUAL_FINALIZATION ${PROJECT_SOURCES})
1111
target_link_libraries(GridViewModel PRIVATE Qt::Widgets)

src/GridViewModel/gridmodel.cpp

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,74 @@
11
#include "gridmodel.h"
22

3-
#include <QDebug>
3+
class GridModel::GridModelPrivate
4+
{
5+
public:
6+
explicit GridModelPrivate(GridModel *parent)
7+
: q_ptr(parent)
8+
{}
9+
10+
GridModel *q_ptr;
11+
12+
GridCellList cellList;
13+
int cellWidth = GRID_CELL_SIZE;
14+
int cellHeight = GRID_CELL_SIZE;
15+
};
416

517
GridModel::GridModel(QObject *parent)
618
: QAbstractListModel(parent)
19+
, d_ptr(new GridModelPrivate(this))
720
{}
821

9-
auto GridModel::data(const QModelIndex &index, int role) const -> QVariant
22+
GridModel::~GridModel() = default;
23+
24+
int GridModel::rowCount(const QModelIndex &parent) const
25+
{
26+
return parent.isValid() ? 0 : d_ptr->cellList.size();
27+
}
28+
29+
QVariant GridModel::data(const QModelIndex &index, int role) const
1030
{
11-
if (!index.isValid()) {
31+
if (!index.isValid() || index.row() < 0 || index.row() >= d_ptr->cellList.size()) {
1232
return {};
1333
}
1434

15-
auto image = m_imageVector.at(index.row());
35+
const auto &cell = d_ptr->cellList.at(index.row());
1636

1737
switch (role) {
18-
case Qt::DecorationRole: return image->image;
19-
case Qt::ToolTipRole: return image->color;
20-
case Qt::SizeHintRole: return QSize{WIDTH, WIDTH};
38+
case Qt::DecorationRole: return cell.image;
39+
case Qt::ToolTipRole: return cell.label;
40+
case Qt::SizeHintRole: return QSize{d_ptr->cellWidth, d_ptr->cellHeight};
2141
case Qt::TextAlignmentRole: return Qt::AlignCenter;
2242
default: break;
2343
}
2444

2545
return {};
2646
}
47+
48+
void GridModel::setCellList(const GridCellList &cellList)
49+
{
50+
beginResetModel();
51+
d_ptr->cellList = cellList;
52+
endResetModel();
53+
}
54+
55+
void GridModel::clearCells()
56+
{
57+
beginResetModel();
58+
d_ptr->cellList.clear();
59+
endResetModel();
60+
}
61+
62+
void GridModel::setCellSize(int width, int height)
63+
{
64+
if (d_ptr->cellWidth != width || d_ptr->cellHeight != height) {
65+
d_ptr->cellWidth = width;
66+
d_ptr->cellHeight = height;
67+
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0), {Qt::SizeHintRole});
68+
}
69+
}
70+
71+
QSize GridModel::cellSize() const
72+
{
73+
return QSize{d_ptr->cellWidth, d_ptr->cellHeight};
74+
}

0 commit comments

Comments
 (0)