DataGridView 实现树形表格:自绘缩进、展开折叠与性能优化全指南
简介面向 WinForms 开发者的 DataGridView 树形列表实现示例解决表格控件无法直接展示层次数据的痛点。资源以 Visual Studio 2012 C# 为环境提供完整项目与源码涵盖树节点模型定义、控件扩展、数据绑定、列显隐控制、绘制展开折叠图标及事件刷新等关键环节适合正在学习 C# 数据展示或需要在界面中呈现树状信息的开发者。压缩包共 32 个文件以 cs 源码、config 配置、resx/resources 资源文件为主辅以可运行的 exe、pdb 调试符号及工程文件整体约 68KB轻量便于直接打开 grid_test 项目对照学习。已有 1952 人浏览学习是快速理解 DataGridView 自定义扩展的实用参考。通过阅读源码和调试运行你能掌握事件驱动下动态显隐列的技巧以及自定义单元格绘制树形缩进的方法示例目录结构清晰可直接移植到实际项目中。1. 树结构进 DataGridView为什么我说这是件反直觉的事如果你的项目里有一个老资格的 WinForms 界面业务方突然提出“把部门树、菜单树或者分类树塞进 DataGridView 里显示”先别急着答应也别急着拒绝。DataGridView 本身是二维表格控件它没有 TreeView 那样的节点概念每一行就是一条独立记录行与行之间没有任何父子关系。而树结构的核心恰恰是父子层级、展开折叠、缩进连接线这三样东西 DataGridView 默认一样都不提供。我见过不少团队在这件事上硬刚原生能力有人把树拍平塞进表格层级信息全丢用户看不出来谁是谁的上级有人直接换 TreeView结果丢失了原本的列头、筛选、编辑和导出能力业务方不接受还有人去搜第三方树网格控件发现要么收费、要么停止维护、要么和现有代码风格冲突。最后绕了一圈还是回到 DataGridView 上做扩展。这篇文章就围绕“DataGridView 控件显示树结构”这件事把数据准备、绘制方案、交互实现、性能优化和踩坑点一次讲透。适合那些不想换控件、又必须保留表格能力的 WinForms 开发者也适合刚接手这类需求、想评估工作量的新手。2. 先搭数据链路把父子关系压平成树表格2.1 数据源设计Id、ParentId、Level 三件套树形数据在业务系统里最常见的存储方式是两张表或者一张带父级字段的表典型的字段是 Id、ParentId、Name、SortNo。这类数据的特点是数据库里存的是扁平记录层级关系靠 ParentId 指向父记录。DataGridView 直接绑定这样的扁平列表显示出来就是无序清单用户看不出层级。所以第一步是把扁平记录转换成带层级信息的行集合每一行除了原始业务字段还要额外携带 Level、IsParent、IsExpanded 这类展示辅助字段。我一般会定义一个专门用于绑定的行模型而不是直接绑业务实体。原因有两点第一业务实体往往有很多不需要展示的字段直接绑定会把列弄得很乱第二Level、IsParent 这些展示逻辑不该污染业务对象。模型大概长这样public class TreeGridRow { public string Id { get; set; } // 节点唯一标识 public string ParentId { get; set; } // 父节点标识根节点为 null public string Name { get; set; } // 显示名称 public int Level { get; set; } // 缩进层级根为 0 public bool IsParent { get; set; } // 是否有子节点 public bool IsExpanded { get; set; } // 当前展开状态 public int SortNo { get; set; } // 同级排序号 }参数说明Level 用于自绘时计算缩进像素IsParent 用于决定是否显示展开按钮IsExpanded 用于记录当前节点的展开状态。注意 IsExpanded 是展示状态不是业务数据重建数据源时需要用字典单独保存否则每次刷新都会丢失用户的展开操作。SortNo 用来保证同级节点的显示顺序稳定没有这个字段树在多次刷新后顺序可能乱跳。这种设计还有个好处如果后续要把树导出成 Excel 或传给别人看直接把这个列表拍平输出就行树的父子关系已经有 Level 标记还原层级很方便。2.2 递归构造从扁平列表生成带层级行的绑定表有了行模型接下来要把扁平的 List 递归展开。这个步骤是整条链路的地基后面绘制、展开折叠、懒加载都依赖它。常见做法是写一个递归方法从根节点集合开始遍历每个节点把它转成 TreeGridRow 加入结果列表然后继续往下一层找它的子节点。伪代码里最容易出错的是“找子节点”的实现方式如果每层都用nodes.Where(n n.ParentId current.Id)去全表扫描数据量大的时候会非常慢。我一般会先按 ParentId 分组建一个字典把查找子节点的成本从 O(n) 降到 O(1)。// 构造树行列表nodes 是扁平数据parentId 是当前父节点 private ListTreeGridRow BuildTreeRows( ListCategoryNode nodes, string parentId, int level, Dictionarystring, ListCategoryNode childrenMap) { var result new ListTreeGridRow(); if (!childrenMap.TryGetValue(parentId ?? string.Empty, out var children)) return result; // 同级按 SortNo 排序保证顺序稳定 foreach (var node in children.OrderBy(n n.SortNo)) { var row new TreeGridRow { Id node.Id, ParentId parentId, Name node.Name, Level level, SortNo node.SortNo, IsParent childrenMap.ContainsKey(node.Id), IsExpanded false }; result.Add(row); if (row.IsParent) // 只有父节点才继续递归 { result.AddRange( BuildTreeRows(nodes, node.Id, level 1, childrenMap)); } } return result; }逻辑说明方法先查当前父节点的直接子级集合拿不到就说明是叶子直接返回。拿到后按 SortNo 排序逐个转成 TreeGridRow遇到有子节点的行就递归处理下一层。注意 childrenMap 的 Key 用parentId ?? string.Empty来兼容根节点的 ParentId 为空的情况。IsExpanded 在这里先默认 false真正的展开状态由调用方传入并在外层维护。调用方式是把原始列表按 ParentId 分组然后对每个根节点调用这个方法把结果合并。构造完成后把 List 直接赋给 DataGridView 的 DataSource 即可先跑通一版表格里能看到每个节点的 Level 数字层级关系已经体现在数据里。这一步验证的是数据转换有没有 bug界面上还是一张普通表不着急画线。3. 画树的三种做法自绘缩进是我最推荐的一条路3.1 方案对比三方控件、缩进自绘、多列伪树把树的层级灌进 DataGridView 之后下一个问题是“怎么让用户看得出来这是树”。现阶段的 Level 字段只是数据视觉上没有任何层级提示。业界常见做法有三种我先说结论最推荐的是“自绘缩进”方案即在 CellPainting 事件里根据 Level 画缩进和连接线。原因后面展开。第一种替换成第三方树网格控件。这类控件本质是继承 DataGridView 加了树逻辑功能完整有展开按钮、连接线、复选框看起来最美观。但问题是很多这类组件已经多年不更新.NET 版本和系统兼容性存在风险而且引入后无法深度定制绘制细节遇到需求变化容易卡死。如果项目允许引第三方包、且团队没有多余精力维护绘制逻辑这个方案可以接受但需要评估维护成本。第二种多列伪树即把第一列用来做缩进把名称拆到第二列用空格填充出层级感。这种方案实现最廉价但效果最差没有连接线没有展开按钮也不能折叠本质上只是“看起来有点缩进的表格”。它适合一次性展示只读树用户没有交互诉求的场景。第三种自绘缩进方案也是我推荐的做法。保留 DataGridView 的全部原生能力把名称列设成未绑定列在 CellPainting 里自己绘制缩进、展开按钮和连接线。它有三个明显优点不引入三方依赖展开折叠交互逻辑完全可控列头排序、选中、编辑这些 DataGridView 原生能力全部保留。代价是绘制代码有一定复杂度需要处理坐标计算和重绘时机这部分我在下一节展开。3.2 核心绘制事件CellPainting 里画缩进、连接线和展开按钮自绘方案的绘制集中在 DataGridView 的 CellPainting 事件里。绘制内容是三样东西根据 Level 计算的左缩进、展开按钮加号或减号、从按钮到名称之间的连接线。这段代码是整个方案的核心也最容易出问题先看实现private const int IndentWidth 18; // 每一级缩进的像素宽度 private const int ExpandBoxSize 14; // 展开按钮的边长 private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { // 只处理名称列表头行不处理 if (e.RowIndex 0 || e.ColumnIndex ! ColumnName.Index) return; var row dataGridView1.Rows[e.RowIndex]; var node row.DataBoundItem as TreeGridRow; if (node null) return; // 先画背景否则后面的自绘会覆盖系统选中高亮色 e.PaintBackground(e.CellBounds, true); int top e.CellBounds.Top (e.CellBounds.Height - ExpandBoxSize) / 2; int boxLeft e.CellBounds.Left node.Level * IndentWidth; using (var pen new Pen(Color.Gray)) { if (node.IsParent) { // 父节点画一个小方框作为展开按钮区域 e.Graphics.DrawRectangle(pen, boxLeft, top, ExpandBoxSize, ExpandBoxSize); // 加号/减号标记 string mark node.IsExpanded ? : ; using (var font new Font(dataGridView1.Font.FontFamily, 9)) { e.Graphics.DrawString(mark, font, Brushes.DarkSlateGray, boxLeft 3, top - 1); } } // 从展开按钮右侧到名称文字之间画一条横线 int lineX boxLeft ExpandBoxSize; int textLeft lineX 6; using (var textBrush new SolidBrush(dataGridView1.ForeColor)) { e.Graphics.DrawLine(pen, lineX, top ExpandBoxSize / 2, textLeft, top ExpandBoxSize / 2); e.Graphics.DrawString(node.Name, dataGridView1.Font, textBrush, textLeft, e.CellBounds.Top 4); } } e.Handled true; // 阻止默认绘制避免文字重叠 }逻辑说明首先排除表头行和无关列然后取当前行的绑定对象 TreeGridRow。用e.PaintBackground先画好背景这一步非常重要因为后续e.Handled true会禁用系统的默认绘制如果不先画背景选中行的蓝色高亮会丢失。然后是展开按钮的绘制只有 IsParent 为 true 的节点才画方框和加号减号叶子节点直接画名字。缩进量由node.Level * IndentWidth计算Level 越大缩进越多。连接线是一段从按钮右侧到文字左侧的灰色横线视觉上把名称和折叠按钮串起来。需要设置的几个关键点名称列必须是未绑定列DataPropertyName 为空或指向 Level 以外的字段否则 DataGridView 会自动覆盖自绘内容。行的高度建议固定或设置 MinimumHeight避免文字顶部对齐出差错。绘制用的字体、颜色不要写死尽量从 dataGridView1.Font 和 ForeColor 取这样跟随主题变化时不会出现不协调。3.3 展开折叠交互点击命中判断与数据重建绘制完成只是静态效果用户点击加号要能展开子级、点击减号要能收起。DataGridView 没有提供树节点的点击事件需要在 CellClick 或 CellMouseClick 里手动判断“点击位置是否落在展开按钮范围内”。这里有个坐标计算细节e.Location 是相对于单元格左上角的坐标要判断按钮位置必须把单元格本身的显示位置加进来用 GetCellDisplayRectangle 拿到单元格在 DataGridView 客户区中的实际坐标再叠加缩进偏移。private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex 0 || e.ColumnIndex ! ColumnName.Index) return; var row dataGridView1.Rows[e.RowIndex]; var node row.DataBoundItem as TreeGridRow; if (node null || !node.IsParent) return; // 计算展开按钮在当前单元格里的左边界 Rectangle cellRect dataGridView1.GetCellDisplayRectangle( e.ColumnIndex, e.RowIndex, false); int boxLeft cellRect.Left node.Level * IndentWidth; // 用鼠标相对于控件左上角的位置来判断命中 Point mousePos dataGridView1.PointToClient(Cursor.Position); if (mousePos.X boxLeft || mousePos.X boxLeft ExpandBoxSize || mousePos.Y cellRect.Top || mousePos.Y cellRect.Bottom) return; ToggleExpand(node.Id); // 展开/折叠切换内部维护状态 }参数说明GetCellDisplayRectangle 的第三个参数 false 表示返回未裁剪的单元格显示位置这比直接算 CellBounds.Left 更可靠因为当 DataGridView 出现水平滚动条时CellBounds 可能不是实际可见位置。命中判断用的是鼠标相对整个控件的坐标和 cellRect.Left 处于同一坐标系不会出现偏移。ToggleExpand 的逻辑是维护一个 HashSet 或字典 keyed by 节点 Id保存当前展开的节点集合然后重建数据源。重建的过程基于 2.2 节的 BuildTreeRows但增加一个过滤条件如果一个父节点不在展开集合里就不再递归进入它的子级。这样展开和折叠本质上都是“重新构造绑定列表并重新赋值 DataSource”。这个方法简单可靠缺点是大树全量重建时有性能损耗如何优化放到第 5 章细说。还有一个容易遗漏的点展开折叠后要重新定位选中行。如果用户在第三级节点上点击展开重建数据源后行索引全部变化如果不处理选中状态会消失。比较稳妥的做法是在重建前记录当前节点的 Id重建后按 Id 找到新行号并设置 CurrentCell这个细节也是第 4 章要展开的坑之一。4. DataGridView 树结构的 4 类常见翻车现场与排查4.1 坑一Row.Visible 不存在折叠只能靠重建数据源现象第一次实现折叠功能的人会在网上搜“DataGridView 隐藏行”然后试图写dataGridView1.Rows[i].Visible false结果编译直接报错因为 DataGridViewRow 根本没有 Visible 属性它是 DataGridViewBand 的成员而 DataGridViewRow 不公开。原因DataGridView 的可见性管理不像 ListView 那样按行控制。行是否显示取决于绑定的数据源里有没有这条记录不是控件自己的属性。尤其在使用 DataSource 绑定模式时行集合完全由数据源驱动控件层面没有提供行级隐藏接口。解决接受“重建数据源”的做法。把要显示的节点重新构造一份列表赋值给 DataSource不显示的节点压根不放进列表里。这样既符合绑定模式的规则也方便后续加入懒加载逻辑。注意重建后要恢复滚动位置和选中行否则用户每次折叠都会跳回顶部体验很差。4.2 坑二绘制错位与残留滚动后画面像“鬼影”现象自绘完成后的表格在初始状态看起来正常一旦上下滚动或拖动水平滚动条发现连接线错位、文字重叠、部分行出现旧内容残留看起来像重影。原因CellPainting 在滚动时会反复触发每次绘制用的坐标必须基于当前可见区域计算。常见错误是直接存了一个绝对坐标滚动之后坐标失效另一个高频原因是e.Handled true之前没有调用 PaintBackground导致背景上叠了旧内容新画面没盖全。解决确保每次绘制都用 e.CellBounds 作为基准不要缓存坐标绘制前先e.PaintBackground(e.CellBounds, true)把背景清干净。如果问题只在水平滚动时出现检查是不是缩进宽度设置过大导致树的连接线画到了单元格外面被裁剪必要时把名称列的 MinimumWidth 设大一些。4.3 坑三展开折叠后选中行丢失键盘操作直接失灵现象展开一个节点后原来选中的那一行失去高亮继续按方向键时焦点停留在旧行号上但视觉上选中的是另一行非常诡异。原因重建数据源后Rows 集合整体刷新行号和内容重新映射。旧选中行号可能仍然落在某个节点的位置但这个位置已经换了内容如果重建后的总行数少了旧行号甚至越界CurrentCell 会变成 null整个控件的选中态被清空。解决重建数据源之前记录当前节点的 Id 和当前列索引。重建完成后遍历新数据源找同 Id 的行设置dataGridView1.CurrentCell dataGridView1.Rows[newIndex].Cells[columnIndex]。如果找不到说明该行被折叠隐藏了就把选中状态定位到它的父节点至少保证用户知道当前位置在哪。4.4 坑四列头排序把整棵树“拍扁”现象树的层级显示正常用户点了一下名称列的列头排序整棵树突然变成无序表格父子关系全乱了展开按钮也没了意义。原因DataGridView 的默认排序是“整列排序”它整行移动数据不关心你这列数据之间是否存在父子引用。一旦按某个业务列排序子节点会被甩到父节点前面或隔得很远树结构被彻底打散。解决树形显示的场景下最好的做法是禁用用户触发的列头排序即把需要保留层级的那几列设为SortMode DataGridViewColumnSortMode.NotSortable。如果业务确实需要排序就只能在“同一父节点下做同级排序”即点击列头时只重排当前层级的兄弟节点不跨层级移动。这个逻辑不能在原生事件里直接改需要先捕获ColumnHeaderMouseClick阻止默认排序然后修改数据源里同级节点的 SortNo 并重建树。5. 大数据量下的保命操作虚模式与懒加载5.1 虚模式让万级节点不再卡顿当树的节点总数超过几千行每次展开折叠都重建数据源会发现明显的卡顿尤其是带有自绘和大量行的场景。这时候第一个要开的开关是 DataGridView 的 VirtualMode。虚模式的核心思路是DataGridView 不保存行数据只在界面需要显示某一行时通过 CellValueNeeded 事件向你要数据。界面只显示可视区域的几十行所以不管你背后有多少数据绘制压力都只和屏幕上的行数有关和总行数无关。开启虚模式的步骤设置dataGridView1.VirtualMode true同时把RowCount设为当前可见节点总数并处理 CellValueNeeded 事件。在事件里根据行索引从你的可见列表里取节点填充各列的值。要注意的是虚模式下很多基于 DataBoundItem 的习惯写法不能用比如从 row.DataBoundItem 取节点这个属性不会自动赋值需要你自己维护“行号到节点”的映射通常用一个 List 保存当前所有可见节点索引和行号一一对应。dataGridView1.VirtualMode true; dataGridView1.RowCount visibleNodes.Count; dataGridView1.CellValueNeeded (s, e) { if (e.RowIndex 0 || e.RowIndex visibleNodes.Count) return; var node visibleNodes[e.RowIndex]; switch (e.ColumnIndex) { case 0: e.Value node.Name; break; case 1: e.Value node.Level; break; case 2: e.Value node.IsParent ? 是 : 否; break; } };逻辑说明visibleNodes 是当前所有展开可见节点的列表每次展开折叠后先重建这个列表再更新 RowCount 并调用InvalidateRow刷新。CellValueNeeded 里不能做耗时操作这里只是从内存列表取值速度很快。注意虚模式开启后单元格编辑保存逻辑也要走 CellValuePushed 事件否则用户改了内容不落回数据源这个误踩的频率很高。5.2 懒加载只在展开时加载直接子级虚模式解决的是“绘制压力”但没有解决“构造压力”。如果原始数据有上万条BuildTreeRows 每次全量递归展开所有子节点光是构造列表就要几十毫秒叠加虚模式也救不回来。所以大数据量下必须配合懒加载初始只加载根节点用户点击展开某个节点时才动态拉取它的下一级子节点。实现上我给每个父节点维护“是否已加载过”的标记。第一次展开时从数据库或缓存查询该节点的直接子级补进 childrenMap后面再次展开直接用内存缓存不再查库。这样每次重建 visibleNodes 时循环遍历的节点数量控制在“当前展开层级的节点数”而不是全树节点数。懒加载还有一个额外收益首屏加载时间大幅缩短用户不用等待整棵树构造完才能看到第一屏。需要警惕的是懒加载和括号内“延时空白”的配合。点击展开后到子级数据返回前要确保 UI 不被锁死常见做法是把加载动作放到异步任务里加载完成回到 UI 线程刷新数据源。否则大数据量下点哪个节点都像卡死。5.3 双缓冲与无效刷新隔离自绘方案最怕的另一个问题是闪烁。树节点多时展开折叠会触发整表重绘画面会明显闪一下。最直接的解法是开启 DataGridView 的双缓冲。DataGridView 支持双缓冲但属性没有直接对外开放需要反射或者继承重写。常见做法是建一个子类在构造函数里设置双缓冲参数public class TreeDataGridView : DataGridView { public TreeDataGridView() { DoubleBuffered true; // Disable 内部的重绘闪烁保护 } }参数说明DoubleBuffered 是 Control 的受保护属性外部访问不到只能通过继承来改。开启后控件内部先在内存画布上完成整块绘制再一次性刷新到屏幕闪烁问题基本消失。如果你不方便改控件类型也可以在 CellPainting 里尽量减少不必要的 Graphics 操作比如同一行多次触发绘制时用状态变量判断内容没有变化就跳过绘制。但最省事的还是双缓冲这个开关值得优先开。此外不要动辄调用dataGridView1.Refresh()。展开折叠后只需要更新 RowCount、调用 Invalidate 刷新受影响区域即可。整表 Refresh 在自绘树场景下会触发所有 CellPainting白耗 CPU。把“数据重建”和“界面刷新”分开数据重建不自动触发重绘手动在逻辑完成后统一刷新一次能明显改善卡顿感。6. 三个让树格更像正经树控件的小习惯自绘和性能都搞定之后剩下的都是体验细节但这些细节决定了用户愿不愿意用、业务方会不会来找你改需求。第一个习惯是让左右方向键承担折叠展开操作。键盘党用户会下意识地在树上按右键展开按左键折叠如果你不处理左右键只会左右移动列焦点非常怪。在 KeyDown 事件里捕获 Keys.Right 和 Keys.Left判断当前行是否是父节点、是否已展开然后复用 ToggleExpand 逻辑即可。这样键盘鼠标两套操作都能用用户迁就成本为零。第二个习惯是根据内容自适应行高但要有度。名称列的内容长短不一时行高一刀切会导致长文本被截断。常见做法是设置AutoSizeRowsMode AllCells但树节点多时全表自动行高会让性能回退到解放前。折中方案是只对名称列开启AutoSizeMode DisplayedCells并且固定行高的最小值。这样行高只会在一开始计算一遍不会随展开折叠反复重算既保证可读性又不会拖慢交互。第三个习惯是记住滚动位置。展开折叠后如果视图跳回顶部用户在一个深层节点上操作会非常崩溃。我一般会在展开折叠前记录当前最顶部的可见行号对应的节点 Id重建数据源后用FirstDisplayedScrollingRowIndex找到该行位置尽量恢复原来的滚动位置。代码上就是两个字段prevTopId 和 prevTopIndex重建后遍历可见节点找到匹配行设置FirstDisplayedScrollingRowIndex。注意这个属性要在数据源赋值之后、界面刷新之前设置顺序反了会失效。这三个习惯加上前面的自绘逻辑DataGridView 显示树结构基本就能达到 TreeView 的操作手感同时保留表格的全部能力。回想我自己最早做这个需求时在排序和滚动位置上栽过跟头后来做成了固定的处理模板才稳定下来。如果你正被这个需求卡住可以按这篇文章的顺序推进先搭数据模型再画缩进再补交互最后处理性能。数据层不出错后面的问题都是可排查的数据层乱了画得再好看也是错的。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Ubuntu下WPS中文显示方块?fontconfig字体配置与别名映射实战

Ubuntu下WPS中文显示方块?fontconfig字体配置与别名映射实战

简介:这份资源面向在 Ubuntu 系统下使用 WPS 办公软件、却频繁遇到字体缺失提示的用户,尤其是需要处理含特殊符号文档的办公与排版人群。当 WPS 弹出缺少 Symbol、Wingdings、Wingdings 2、Wingdings 3 等字体的警告时,文档中的符号与图形往往…

2026/10/12 4:03:26 阅读更多 →
WinForms Chart 时间轴实战:DateTime 转 OADate 与滚动条控制

WinForms Chart 时间轴实战:DateTime 转 OADate 与滚动条控制

简介:这份资源围绕VS自带Chart控件展开,面向需要在WinForms项目中实现时间轴图表的.NET开发者,重点解决x轴按时间刻度显示并配合滚动条浏览长时数据的问题。示例采用从Excel读取数据的方式,x轴时间格式为MM-dd HH:mm:ss:fff&#…

2026/10/12 4:02:25 阅读更多 →
Java微信退款接口实战:从签名、证书到异步回调与对账的完整链路

Java微信退款接口实战:从签名、证书到异步回调与对账的完整链路

简介:这是一份面向Java后端开发者的微信退款接口实现示例资源,聚焦商户在用户发起退款时通过API与微信服务器完成安全交互的完整流程。内容围绕Java网络编程、HTTPS安全通信、PKCS12证书管理、RSA2048数字签名与JSON数据处理展开,适合需要对接…

2026/10/12 4:02:25 阅读更多 →

最新新闻

Tortoise-ORM 与 Sanic 集成实战:register_tortoise 生命周期管理全解析

Tortoise-ORM 与 Sanic 集成实战:register_tortoise 生命周期管理全解析

数据库后端 【免费下载链接】tortoise-orm Familiar asyncio ORM for python, built with relations in mind 项目地址: https://gitcode.com/gh_mirrors/to/tortoise-orm 点击查看 免费下载 本文以 Tortoise-ORM 仓库中 Sanic 集成示例 为主线,系统讲解…

2026/10/12 6:02:32 阅读更多 →
从ABP到Clean DDD:中后台系统架构迁移实践与反思

从ABP到Clean DDD:中后台系统架构迁移实践与反思

做中后台和SaaS类系统的团队,大概率都绕不开 ABP 这个名字。它把模块化、仓储模式、工作单元、动态 API、审计日志、多租户这些东西一次性打包成开箱即用的起点,用 .NET 技术栈做内部系统,几乎第一天就能跑起来。我所在的团队也是这样起步的&…

2026/10/12 6:02:32 阅读更多 →
STM32 | CLion + ST-Link下载调试完整流程

STM32 | CLion + ST-Link下载调试完整流程

一、下载程序 先创建一个文件夹: 命名:stlink.cfg 写入以下代码: # choose st-link/j-link/dap-link etc. #adapter driver cmsis-dap #transport select swdsource [find interface/stlink.cfg]transport select hla_swdsource [find target/stm32f4x.…

2026/10/12 6:02:32 阅读更多 →
Python代码打包成exe文件详解

Python代码打包成exe文件详解

一、pyhon代码打包成exe文件1-1:安装打包工具在PyCharm底部的 终端(Terminal) 里输入:pip install pyinstaller1-2:输入打包命令在同一个终端里输入(直接复制):pyinstaller --onefile --noconsole --hidden…

2026/10/12 6:02:32 阅读更多 →
知识工作插件化:从信息捕获到配置同步的效率体系

知识工作插件化:从信息捕获到配置同步的效率体系

平时做知识工作,最耗时间的往往不是思考本身,而是信息的搬运。你从网页摘一段话,粘贴进笔记里,格式全乱;你复制了一段关键论述,过了几天想找来源,翻遍聊天记录和文档都找不到;你给十…

2026/10/12 6:02:32 阅读更多 →
DJL与Spring集成:Java后端部署深度学习模型的实践指南

DJL与Spring集成:Java后端部署深度学习模型的实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 6:01:32 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →