VC2008 MFC 老项目用 CTabSheet 做窗口 Tab 的轻量方案
简介这份资源面向在VC2008环境下开发MFC桌面程序的初学者与中级开发者聚焦于用CTabSheet类实现窗口选项卡界面这一常见需求。CTabSheet是对Windows TabCtrl控件的封装配合CPropertySheet可快速构建多页面切换的对话框适合需要优化界面布局、提升屏幕空间利用率的场景。压缩包共23个文件约200KB包含8个h头文件与6个cpp源文件承载核心类实现另有sln解决方案、vcproj工程文件、rc资源脚本、ico图标、exe可执行文件及ReadMe说明等覆盖从源码到编译产物的完整工程结构。资源已有306人学习下载读者可从中获得可直接编译运行的选项卡示例工程理解CPropertySheet与CTabSheet派生类的组织方式、AddPage添加页面、消息映射与数据交换等关键环节并借助现成目录结构快速对照调试减少自行摸索底层API的时间成本。1. VC2008 里用 CTabSheet 做窗口 Tab一个被低估的轻量方案手上维护着一套 2008 年前后立项的 MFC 老工程界面还是单文档加一堆按钮的形态产品那边要求把配置、日志、设备状态拆成三个页签。第一反应是上 MFC 自带的 CTabCtrl但真动手才发现CTabCtrl 只给你一个标签条页面切换、子对话框的创建销毁、尺寸自适应全得自己写一个下午就耗在 ShowWindow 和 SetWindowPos 的坐标计算上。后来翻老代码发现前人留了一个叫 CTabSheet 的封装类本质是把 CTabCtrl 和一组子对话框绑在一起对外只暴露 AddPage 和 ShowPage 两个动作几十行就能把窗口 tab 跑起来。这篇就把这套做法从头讲清楚CTabSheet 是什么、在 VC2008 环境下怎么落地、参数怎么设、哪些地方最容易翻车。适合还在维护 MFC 老项目、又不想为了几个页签引入整套第三方界面库的同行。顺带说一句最近总有人搜 vc2008 运行库安装失败是不是重装系统就能好这类环境问题后面单独拎出来讲它和 CTabSheet 本身没关系但确实会卡住你第一步编译。2. CTabSheet 的定位与 VC2008 工程准备先搞清楚它替你干了什么2.1 CTabSheet 不是 MFC 官方类别去头文件里找很多人第一次听到 CTabSheet会下意识以为它是 MFC 自带的某个类跑去 afxcmn.h 里翻结果当然找不到。CTabSheet 是社区里流传很广的一个自定义封装核心思路是继承 CTabCtrl内部维护一个子对话框指针数组把「标签选中变化」这个通知翻译成「隐藏当前页、显示目标页」。它没有官方文档也没有版本号你拿到手的通常就是一个 CTabSheet.h 加一个 CTabSheet.cpp代码量不大正好适合读一遍再决定用不用。它和直接用 CTabCtrl 的区别用一句话概括CTabCtrl 管标签CTabSheet 管标签加页面。你自己写 CTabCtrl 方案需要处理 TCN_SELCHANGE 通知、维护页面指针、处理页面首次创建时机、处理父窗口尺寸变化时的重排CTabSheet 把这些收进类内部你只需要在父对话框的 OnInitDialog 里 AddPage然后 ShowPage。代价是灵活性下降比如你想让某个页签禁用、想动态增删页就得改它的源码。选型上我的判断标准很直接页签数量固定、页面之间没有复杂联动、项目还在用 VC2008 这种老工具链那就用 CTabSheet省下来的时间够你调别的 bug。如果页签要动态生成、要做拖拽排序、要嵌 Web 控件那还是老老实实上 CTabCtrl 自己控或者换更现代的界面框架。2.2 VC2008 工程里把 CTabSheet 加进来的最小步骤假设你已经有一个基于对话框的 MFC 工程主对话框叫 CMainDlg。第一步是把 CTabSheet.h 和 CTabSheet.cpp 拷进工程目录在解决方案资源管理器里右键工程 → 添加 → 现有项把这两个文件加进来。注意 VC2008 默认用的是 Unicode 字符集如果拿到的 CTabSheet 源码是 ANSI 的字符串处理部分可能要改这个后面避坑章节细说。第二步是在主对话框头文件里包含 CTabSheet.h并声明一个成员变量。这里有个细节CTabSheet 内部要创建子对话框所以主对话框的 OnInitDialog 必须返回 TRUE不能提前 return FALSE否则子页面创建会失败。// MainDlg.h #include CTabSheet.h class CMainDlg : public CDialog { // ... 其他成员 protected: CTabSheet m_tab; // Tab 控件成员随主对话框一起构造析构 CPageConfig m_pageConfig; // 三个子页面各自是一个 CDialog 派生类 CPageLog m_pageLog; CPageDevice m_pageDevice; };第三步是在主对话框的 OnInitDialog 里做绑定和加页。绑定的意思是把 CTabSheet 关联到对话框上预留的 Tab 占位控件通常是一个 ID 为 IDC_TAB_PLACEHOLDER 的静态框或者空白的 Tab 控件。加页时传入子对话框对象的地址和页签标题。BOOL CMainDlg::OnInitDialog() { CDialog::OnInitDialog(); // 把 CTabSheet 绑定到对话框上预留的占位控件 m_tab.SubclassDlgItem(IDC_TAB_PLACEHOLDER, this); // 依次加入三个页面顺序即页签从左到右的顺序 m_tab.AddPage(_T(配置), m_pageConfig, IDD_PAGE_CONFIG); m_tab.AddPage(_T(日志), m_pageLog, IDD_PAGE_LOG); m_tab.AddPage(_T(设备), m_pageDevice, IDD_PAGE_DEVICE); // 显示第一页不调用这行会看到空白 m_tab.ShowPage(0); return TRUE; }这段代码里 AddPage 的第三个参数是子对话框的模板 ID也就是资源视图里那个 IDD_PAGE_XXX。CTabSheet 内部会用这个 ID 去 Create 子对话框所以三个子对话框资源必须存在且风格要设成 Child不能是 Popup否则创建出来会飘在屏幕外面。ShowPage 的参数是页索引从 0 开始。2.3 子对话框资源怎么建才不会被 CTabSheet 拒绝子页面本身是普通的对话框资源但有几个属性必须改对否则现象很迷惑页签能点但页面区域一片空白或者页面跑到主窗口左上角去了。属性必须的值不改的后果StyleChild页面变成独立弹窗脱离 Tab 区域BorderNone页面四周出现多余边框和 Tab 边缘对不齐Visible不勾选三个页面同时显示叠在一起IDIDD_PAGE_XXXAddPage 找不到模板创建失败资源建好后每个子页面对应一个 CDialog 派生类类里不用写额外逻辑CTabSheet 会负责创建和显示。如果你在子页面里要访问主对话框的数据用 GetParent() 拿到主对话框指针再转换不要用全局变量老项目里全局变量一多页签切换时的状态同步就是灾难。3. 让 Tab 真正跑起来AddPage、ShowPage 与尺寸自适应3.1 AddPage 内部做了什么参数传错会怎样AddPage 看着只有三个参数但它内部的动作决定了后面所有现象。典型实现是先调用 CTabCtrl::InsertItem 插入一个标签把标题和索引存下来然后保存子对话框指针和模板 ID最后在第一次 ShowPage 时才真正 Create 子对话框。也就是说AddPage 阶段子对话框还没被创建你在子对话框构造函数里打的日志这时候不会出现别以为代码没生效。参数传错最常见的两种。第一种是标题传了空字符串标签条上出现一个空白页签点它能切换但不知道是哪个页排查时盯着看半天。第二种是模板 ID 传成了主对话框的 IDDCTabSheet 会拿主对话框模板去 Create结果递归创建直接崩。我的习惯是给每个 IDD_PAGE_XXX 起名时带上 PAGE 前缀AddPage 时肉眼扫一遍避免手滑。// CTabSheet 内部 AddPage 的典型逻辑示意非官方源码 void CTabSheet::AddPage(LPCTSTR title, CDialog* pPage, UINT nIDTemplate) { if (pPage NULL) return; // 插入标签保存索引 TCITEM item; item.mask TCIF_TEXT; item.pszText (LPTSTR)title; int nIndex InsertItem(GetItemCount(), item); // 记录页面信息此时不创建 m_pPages[nIndex].pDlg pPage; m_pPages[nIndex].nID nIDTemplate; m_nPageCount; }从这段逻辑能看出AddPage 的顺序就是页签顺序中途想调整顺序只能改调用顺序或者改源码。另外 m_pPages 这种固定数组的写法在老版本里很常见页数超过数组上限会越界用之前先看一眼数组大小别加着加着加出问题。3.2 ShowPage 的创建时机与切换时的隐藏逻辑ShowPage 是真正干活的地方。第一次调用某个索引时它检查该页是否已创建没有就 Create创建时父窗口传 this也就是 CTabSheet 本身然后把页面移动到标签条下方的客户区。已经创建过的页面切换时只是 ShowWindow(SW_HIDE) 和 ShowWindow(SW_SHOW) 的差别。void CTabSheet::ShowPage(int nPage) { if (nPage 0 || nPage m_nPageCount) return; // 隐藏当前页 if (m_nCurPage 0 m_pPages[m_nCurPage].pDlg-GetSafeHwnd()) m_pPages[m_nCurPage].pDlg-ShowWindow(SW_HIDE); // 首次显示则创建 if (!m_pPages[nPage].pDlg-GetSafeHwnd()) { m_pPages[nPage].pDlg-Create(m_pPages[nPage].nID, this); // 调整页面位置到标签条下方 CRect rcClient; GetClientRect(rcClient); AdjustRect(FALSE, rcClient); // CTabCtrl 自带的客户区计算 m_pPages[nPage].pDlg-MoveWindow(rcClient); } m_pPages[nPage].pDlg-ShowWindow(SW_SHOW); m_nCurPage nPage; SetCurSel(nPage); }这里的关键是 AdjustRect(FALSE, rcClient)它把 Tab 控件的整体矩形换算成标签条下方的显示区域省得你自己减标签高度。不同版本的 CTabSheet 对 AdjustRect 的调用位置不一样有的放在 OnSize 里有的放在 ShowPage 里效果差别在窗口缩放时体现。如果你发现缩放主窗口后页面不跟着变多半是 OnSize 没处理。3.3 主窗口缩放时页面跟着变OnSize 里补一刀CTabSheet 默认不一定处理父窗口尺寸变化主窗口一拉大标签条跟着变大但子页面还停在原来的大小右下角露出一块主对话框背景很难看。解决办法是在主对话框的 OnSize 里把新尺寸转给 CTabSheet让它重排当前页。void CMainDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); // 窗口最小化时 cx/cy 为 0跳过避免异常 if (nType SIZE_MINIMIZED) return; if (!::IsWindow(m_tab.GetSafeHwnd())) return; // 让 Tab 控件铺满主对话框客户区 m_tab.MoveWindow(0, 0, cx, cy); // 通知 CTabSheet 重排当前页面 m_tab.ResizePage(); }ResizePage 不是标准方法名不同来源的 CTabSheet 叫法不一样有的叫 AdjustPage有的直接在 OnSize 里内联。拿到源码后先搜一遍有没有现成的重排函数没有就自己加一个逻辑就是取当前页指针重新算 AdjustRect 然后 MoveWindow。注意 MoveWindow 之前判断一下子对话框句柄是否有效没创建过的页面跳过否则空指针。提示OnSize 在 OnInitDialog 之前就可能被调用一次所以里面所有对 m_tab 的操作都要先判断窗口句柄不然启动阶段直接崩这个坑我踩过不止一次。4. 避坑与排查CTabSheet 在 VC2008 下最容易翻车的五件事4.1 现象编译报错「无法打开包括文件 CTabSheet.h」原因通常不是文件没拷而是拷贝位置和工程包含路径对不上。VC2008 的附加包含目录在项目属性 → C/C → 常规里默认不包含工程子目录。如果你把 CTabSheet.h 放在工程目录下的 include 文件夹里就得把 include 加进附加包含目录或者干脆和 MainDlg.h 放同一层。解决右键工程 → 属性 → 配置属性 → C/C → 常规 → 附加包含目录加上 $(ProjectDir)include 这类相对路径。改完记得把配置切到 Release 再确认一遍VC2008 的 Debug 和 Release 配置是分开的只改一个另一个照样报错。4.2 现象页签能点页面区域全白子对话框没出来原因基本锁定在子对话框资源属性上。最常见的是 Style 没设成 ChildCreate 出来的对话框是弹出式位置不受 Tab 控制看起来就像没显示。其次是 Visible 勾了三个页面同时可见互相覆盖你看到的是最上面那个切换时没变化。解决打开每个 IDD_PAGE_XXX 资源确认 StyleChild、BorderNone、Visible 不勾。改完清理解决方案重新生成VC2008 有时候资源改动不重新编译直接运行还是旧资源这个玄学问题清一次工程就好。4.3 现象切换页签时子对话框里的控件闪烁或重绘异常原因是页面切换用了 ShowWindow某些带背景刷的控件在隐藏再显示时会重绘整个区域。如果子页面上有列表控件或者自绘控件闪烁会很明显。解决在子对话框的 OnInitDialog 里把背景刷设成 NULL或者给子对话框加 WS_CLIPCHILDREN 风格。更彻底的做法是切换时不用 ShowWindow改用 SetWindowPos 带 SWP_NOREDRAW但这样代码复杂度上升页数少的话忍一下闪烁也行。4.4 现象vc2008 运行库安装失败程序在别人机器上起不来这个和 CTabSheet 本身无关但老项目交付时必遇到。你本机装了 VC2008 运行库所以跑得好好的拷到客户机上提示缺少 mfc90.dll 或者 msvcr90.dll。有人搜 vc2008 运行库安装失败是不是重装系统就能好重装系统大概率没用因为问题不在系统在运行库没装对版本。解决确认目标机是 32 位还是 64 位装对应版本的 Microsoft Visual C 2008 Redistributable。如果安装失败先看是不是已经装了更高版本的运行库导致冲突或者系统缺少 Windows Installer 的某个更新。最稳的办法是把 MFC 静态链接进 exe项目属性 → 配置属性 → 常规 → 使用 MFC 选「在静态库中使用 MFC」这样不依赖目标机的运行库代价是 exe 体积大几 MB。老项目交付我一般直接静态链接省得现场折腾。4.5 现象AddPage 加了四页第四页点不动或者程序崩CTabSheet 内部如果用固定大小数组存页面比如 CDialog* m_pPages[3]加到第四页就越界。这种问题在 Debug 下可能只是数据错乱Release 下直接崩很难查。解决打开 CTabSheet.h 看页面数组的声明大小不够就改大或者把数组换成 CArray 或 std::vector。改的时候注意同步改构造函数里的初始化逻辑别只改声明。加页之前先数一遍要几个页签留一两个余量。5. 进阶把 CTabSheet 改造成能动态增删页的版本固定页数的 CTabSheet 够用但遇到「根据设备数量动态生成页签」的需求就抓瞎。我的做法是不动它的核心切换逻辑只把存储结构从固定数组换成 std::vector再加一个 RemovePage 方法。这样既保留原有调用习惯又能动态调整。// 把固定数组换成 vector页面信息用结构体存 struct TABPAGE { CDialog* pDlg; UINT nID; CString strTitle; }; std::vectorTABPAGE m_vecPages; // 替换原来的 CDialog* m_pPages[N] void CTabSheet::RemovePage(int nPage) { if (nPage 0 || nPage (int)m_vecPages.size()) return; // 先销毁子对话框再删标签顺序反了会残留窗口 if (m_vecPages[nPage].pDlg-GetSafeHwnd()) m_vecPages[nPage].pDlg-DestroyWindow(); DeleteItem(nPage); m_vecPages.erase(m_vecPages.begin() nPage); // 修正当前页索引删的是当前页就往前挪一位 if (m_nCurPage (int)m_vecPages.size()) m_nCurPage (int)m_vecPages.size() - 1; if (m_nCurPage 0) ShowPage(m_nCurPage); }改造时有三个点要盯住。第一DestroyWindow 必须在 DeleteItem 之前否则标签没了但窗口还在句柄泄漏。第二删除后当前页索引要修正不然 ShowPage 越界。第三如果子对话框里有线程或者定时器DestroyWindow 之前先停掉不然窗口销毁了回调还在跑直接访问野指针。验证改造是否成功我一般写一个简单的测试启动时加三页点一个按钮删中间那页再点另一个按钮加一页来回切几次看有没有残影或者崩溃。用 VC2008 自带的调试器看窗口句柄数量删页后句柄数应该回落不回落就是没销毁干净。这套改造下来CTabSheet 就从「固定页签工具」变成了「轻量页签容器」在老 MFC 项目里足够应付大部分配置界面需求。我自己的习惯是拿到任何第三方封装类先读一遍源码再决定用不用CTabSheet 代码量小读一遍半小时比出了事再查省太多。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

视频编解码工具链实战:从FFmpeg到自动化流水线

视频编解码工具链实战:从FFmpeg到自动化流水线

视频编解码开发工具合集:从踩坑到搭起一套顺手的工作台先说说我为什么想写这篇东西。上个月帮朋友调一个视频转码服务,他用的还是几年前的FFmpeg命令行硬怼方案,遇到H.265编码的源素材就频繁报错,我到他公司一看,整个调…

2026/10/14 19:10:16 阅读更多 →
开源无代码爬虫Maxun:录制式采集训练与本地部署实战

开源无代码爬虫Maxun:录制式采集训练与本地部署实战

干了这么多年数据处理和爬虫开发,我对"写爬虫一时爽,维护火葬场"这句话体会太深了。一个看似简单的采集脚本,上线之后要跟页面改版斗、跟访问限制斗、跟登录过期斗,没完没了。所以当我在开源社区看到Maxun这个项目时&am…

2026/10/14 9:53:21 阅读更多 →
天梯赛真题代码解析:链表、二叉树、动态规划与图论一次讲透

天梯赛真题代码解析:链表、二叉树、动态规划与图论一次讲透

简介:面向团体程序设计天梯赛(CCCC)备赛选手的真题解析PDF,覆盖近三年L2、L3梯级题目,适合已有基础、希望冲击进阶分与登顶分的高校学生及竞赛教练。这份文档将梯赛高频题型分类整理,每个题目都给出题意分析…

2026/10/10 6:32:31 阅读更多 →

最新新闻

库存扣减的并发控制:乐观锁的应用

库存扣减的并发控制:乐观锁的应用

财务主管跨界写技术文,先交代缘由:公司商城搞限时促销,活动结束对账,账面库存和实物对不上,多卖了四十七件。排查一周,病根是库存扣减没做并发控制,两个请求同时读到同样的库存数,各…

2026/10/14 19:10:22 阅读更多 →
Python案例练习:图书管理系统

Python案例练习:图书管理系统

Python案例练习:图书管理系统、新增图书、查看所有图书、按书名搜索、修改图书信息、删除图书、保存数据到本地文件、从文件加载数据 """ 图书管理系统 新增图书、 查看所有图书、 按书名搜索、 修改图书信息、 删除图书、 保存数据到本地文件、 从文…

2026/10/14 19:10:22 阅读更多 →
防拍照泄密核心知识点合集精讲

防拍照泄密核心知识点合集精讲

在企业数据安全建设过程里,很多运维新人学习防泄密体系都是碎片化的。大多优先关注 U 盘拷贝、聊天软件外发、邮件附件导出这类传统泄密通道,很容易漏掉手机拍摄电脑屏幕这个低成本、高隐蔽性的泄密途径。很多企业投入预算搭建文档加密体系,管…

2026/10/14 19:10:22 阅读更多 →
Keepalived + DRBD 实现 NFS 文件服务高可用:架构、配置与避坑指南

Keepalived + DRBD 实现 NFS 文件服务高可用:架构、配置与避坑指南

简介:这份资源面向运维工程师与VoIP通信系统开发者,聚焦Freeswitch高可用架构的落地实践,通过Keepalived实现主备节点故障自动切换,解决单点故障导致通信服务中断的问题。压缩包共4个文件,约38KB,包含2个Sh…

2026/10/14 19:10:22 阅读更多 →
Reactor 3背压机制深度解析与生产级实践

Reactor 3背压机制深度解析与生产级实践

1. 项目概述:为什么背压不是“可选项”,而是Reactor 3的生存底线 你写完第一个 Flux.create() ,用 onNext() 疯狂发射数据,下游用 subscribe(System.out::println) 接收——一切丝滑。直到某天压测时,控制台突然…

2026/10/14 19:10:22 阅读更多 →
Python车险理赔车型识别与合规校验:数据工程实践指南

Python车险理赔车型识别与合规校验:数据工程实践指南

1. 项目背景与整体设计思路1.1 车型信息在车险理赔中的关键作用车险理赔看起来是“拍照—定损—打款”的简单流程,但真正做过车险数据的人都知道,车型信息是贯穿整个理赔链路的一个隐形骨架。从承保端的车型定价,到理赔端的配件价格与工时费计…

2026/10/14 19:09:21 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →