ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MFC对话框添加状态栏:CStatusBar的创建、布局与避坑指南

MFC对话框添加状态栏:CStatusBar的创建、布局与避坑指南 简介面向MFC初中级开发者的VS2010示例工程演示如何在普通Dialog对话框底部添加状态栏并动态更新信息。资源围绕创建状态栏控件、配置多分区指示器、用定时器刷新时间等关键环节展开适合正在学习MFC界面编程或希望为对话框增加实时反馈的读者。压缩包共47个文件以C源码、资源脚本、VS工程配置为主同时包含编译中间文件和调试信息可直接查看生成逻辑整体大小约29.05MB目录结构清晰。已有736人学习下载。通过源码可快速理解CStatusBar的创建、SetIndicators设置分区、SetPaneText更新文本以及定时器与CTime配合获取当前时间的用法工程内还包含可运行的VS项目配置和说明文档便于直接打开调试并对照学习。状态栏不仅用于信息展示还能增强应用的专业感和交互反馈这份资料能帮助读者完整掌握在MFC Dialog中集成状态栏的可行方案。1. 在 Dialog 上硬塞一条状态栏这件事比你想的要绕最早做 MFC 对话框工具时我总想在窗口底部加一条状态栏用来显示文件路径、操作结果、运行耗时这些实时信息。当时的直觉是状态栏嘛从工具箱拖一条下来不就完了。结果在 VC 的资源编辑器里翻了半天工具箱里根本没有“状态栏”这个控件。后来才知道MFC 里 CStatusBar 不是普通控件它跟 CReBar、CDockBar 一样属于框架窗口的“附属物”和 Dialog 的资源模板是两个体系。这就是 mfc dialog 对话框添加状态栏 这个需求的第一道坎先搞清楚状态栏在 MFC 里的定位再用代码方式“硬塞”进 Dialog 的客户区底部。这篇东西适合正在做 MFC 对话框工具、想把底部状态信息做出来的人。读完你能得到三样东西状态栏在 Dialog 里的两种加法及选型理由、一份可直接拷贝的最小实现、还有我踩过的几个翻车点。整个过程不需要改 rc 资源文件也不需要继承 CFrameWnd纯代码操作。2. 先立住理论为什么 Dialog 没有状态栏以及两种加法的取舍2.1 状态栏的本质一个挂在窗口底部的子窗口MFC 的 CStatusBar 并不是一个“控件”它本质上是一个窗口类内部由多个 pane窗格组成每个 pane 用来显示一行文本或一个图标。它工作时依赖两样东西一是父窗口的客户区底边位置二是消息循环里的 WM_SIZE 通知。框架窗口CFrameWnd里状态栏由框架自己管理布局和刷新都是现成的。但 CDialog 没有这套机制——Dialog 的资源模板里只认控件Control而状态栏不归控件管辖所以你在资源编辑器里一辈子也拖不出来。要想在 Dialog 里加状态栏常见做法有两种。第一种直接在 OnInitDialog 里 new 一个 CStatusBar然后调用 Create 把它挂到 Dialog 上再手动处理布局。第二种把 Dialog 的客户区“腾”出一块空间自己写 WM_SIZE 响应把状态栏钉在底部。第一种简单直接代码量小第二种可控性强适合窗口大小会变的场景。先讲第一种因为它覆盖大多数工具型 Dialog 的需求。2.2 我为什么没用资源编辑器而是纯代码创建网上不少老代码用资源编辑器加一条静态文本伪装状态栏或者用对话框模板的底边画一条线。这些做法的致命问题是“只有形没有神”状态栏的窗格分割、文本更新、宽窄自适全都得自己模拟一旦 Dialog 尺寸变了伪状态栏不是飞了就是被控件盖住。纯代码创建的好处是能拿到一个真正的 CStatusBar 对象操作窗格、显示文本、设置宽度都直接走 MFC 的封装省心得多。具体实现上我在 Dialog 的类定义里加一个成员// 在 Dialog 类的头文件里例如 CMyDlg.h class CMyDlg : public CDialogEx { protected: CStatusBar m_wndStatusBar; // 状态栏对象 public: afx_msg void OnSize(UINT nType, int cx, int cy); DECLARE_MESSAGE_MAP() };然后在 OnInitDialog 里创建它。注意顺序状态栏必须在控件初始化之后创建否则它会盖住 Dialog 底部的控件或者被 Dialog 的 OnPaint 刷掉了。创建代码如下BOOL CMyDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 创建状态栏第一个参数是父窗口指针第二个是控件 ID随便给一个 // 状态栏默认会趴在父窗口底部不需要手动指定位置 if (!m_wndStatusBar.Create(this, ID_VIEW_STATUS_BAR)) { TRACE(_T(状态栏创建失败\n)); return FALSE; } // 把状态栏分成两格第一格显示提示信息第二格显示时间或状态 static UINT indicators[] { ID_DEFAULT_PANE, // 第一格显示普通提示文本 ID_TIME_PANE // 第二格显示时间或临时信息 }; m_wndStatusBar.SetIndicators(indicators, sizeof(indicators) / sizeof(indicators[0])); // 设置第一格宽度为 150 像素第二格占满剩余空间 m_wndStatusBar.SetPaneInfo(0, ID_DEFAULT_PANE, SBPS_STRETCH, 150); m_wndStatusBar.SetPaneInfo(1, ID_TIME_PANE, SBPS_NORMAL, 120); return TRUE; }代码逻辑拆开说Create 是 CStatusBar 的创建入口父窗口填 this也就是 Dialog 自己ID 是资源 ID随便定义一个不冲突的值就行不需要在 rc 文件里占位。SetIndicators 传入一个 UINT 数组每个元素对应一个窗格的资源 ID资源 ID 本身用来关联窗格文本如果在资源文件里找不到对应字符串MFC 会忽略文本只保留窗格结构。SetPaneInfo 四个参数分别是窗格下标、窗格 ID、样式、宽度。第一格用了 SBPS_STRETCH它会在父窗口变宽时自动扩展把剩余的宽度吃掉第二格用 SBPS_NORMAL固定 120 像素专门放时间。这里有个容易忽略的细节indicators 数组里的 ID 必须在程序的资源文件里存在哪怕是个空字符串也行。如果某个 ID 不存在SetIndicators 不会报错但那个窗格的文本永远是空的你往里面 SetPaneText 也没反应。我一般会在 resource.h 里定义两个常量然后偷懒把字符串表里对应的条目写成空串,这样既满足 ID 有效性又不影响显示内容。2.3 手动布局方案什么时候不该走捷径第一种方案的缺点在 Dialog 可缩放时会暴露状态栏虽然会自动趴在底部但 Dialog 右边界拉宽时SBPS_STRETCH 窗格会跟着变宽而固定窗格的右边界不会跟着变化导致状态栏右侧出现空隙或超出。如果你做的是一个用户可调整大小的 Dialog或者视觉上要求状态栏始终贴边就得自己接管布局。这种情况下我一般会在 Dialog 上重写 OnSize每次尺寸变化后手动移动或调整状态栏void CMyDlg::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); // 如果状态栏已经创建就把它拉到客户区底部 if (m_wndStatusBar.GetSafeHwnd() ! NULL) { // 用 MoveWindow 把状态栏撑满底部高度用固定值 30 像素 // 这里的 30 也可以换成从 GetWindowRect 里算出的精确高度 CRect rcClient; GetClientRect(rcClient); m_wndStatusBar.MoveWindow(0, rcClient.bottom - 30, rcClient.Width(), 30); } }手写布局的好处是状态栏尺寸全由你控制不依赖 MFC 默认行为坏处是每次 Dialog 尺寸变化都要同步处理而且 MoveWindow 的硬编码高度在不同 DPI 缩放下会出问题。比较稳妥的做法是创建后用 GetWindowRect 算出状态栏的真实高度再把它当作固定值存起来避免在 OnSize 里二次计算。不过话说回来大多数工具型 Dialog 是固定尺寸的用户不会去拉大拉小。这种情况下第一种方案完全够用手写布局纯属给自己加戏。我自己的习惯是固定大小 Dialog 一律用 Create SetIndicators SetPaneInfo可缩放 Dialog 才劳心劳力去重写 OnSize。3. 从零跑通最小实现在 Dialog 底部亮起一条能显示文字的状态栏3.1 初始化顺序和资源 ID 的坑现在假设你已经在 Visual Studio 里建好了一个基于 Dialog 的 MFC 工程用的是对话框模板类向导生成的 CMyDlg。往里面加一个状态栏最稳的路径是先在头文件里声明 CStatusBar 成员再在 OnInitDialog 里 Create然后在需要时往窗格里塞文本。上节贴的代码就是最小骨架但你直接编译大概率会有编译错误错误点通常在资源 ID 上。resource.h 里需要加两个常量。例如#define ID_DEFAULT_PANE 1001 #define ID_TIME_PANE 1002这两个值不要跟 Dialog 上已有控件的 ID 冲突默认的 IDC_STATIC 占用了 -1Dlg 里按钮等控件的 ID 范围一般从 IDC_BUTTON1 开始用 1001 和 1002 基本是安全的。不要嫌麻烦没有它们在资源 ID 表里占位SetIndicators 虽然不报错但窗格的文本操作会失效表现出来就是状态栏一片空白SetPaneText 怎么调都没反应。创建成功后往窗格写文字是另一套逻辑。SetPaneText 是 CStatusBar 提供的接口第一个参数是窗格下标从 0 开始第二个参数是 CString。注意这里传的是下标不是 ID。很多人习惯性传 ID结果发现只有第一个窗格能改第二个窗格纹丝不动因为没有重载函数接受 ID 作为第一参数——实际上有但容易混淆。// 往状态栏第一格写一行提示 m_wndStatusBar.SetPaneText(0, _T(就绪)); // 往状态栏第二格写当前时间 m_wndStatusBar.SetPaneText(1, CTime::GetCurrentTime().Format(_T(%H:%M:%S)));状态栏窗格文本更新的时机直接在业务代码里调用就行不需要向窗口发送消息因为 SetPaneText 内部会触发窗格区域的重绘。有一点要说清楚SetPaneText 默认会立即重绘所以高频更新比如每秒刷新时间时会造成窗口底部区域闪烁。要缓解的话可以在 SetPaneText 之前调用 SetRedraw(FALSE)全部更新完再 SetRedraw(TRUE)但大多数场景下频率不高没必要做这个优化。3.2 分格、宽度和样式的参数语义状态栏窗格的样式参数是很多人迷迷糊糊的地方。SetPaneInfo 的第三个参数常用的有三个SBPS_NORMAL、SBPS_STRETCH、SBPS_NOBORDERS。它们的含义分别是普通窗格、自动伸展窗格和无边框窗格。SBPS_STRETCH 通常在指示器数组里只出现一次放在最左侧或最右侧的窗格上用来吸收窗口伸缩带来的空白SBPS_NORMAL 就是普通窗格宽度固定由 SetPaneInfo 的第四个参数决定。宽度参数的单位是像素这个像素值是窗格的初始宽度。如果窗口 DPI 是 96100% 缩放150 像素大致能显示十几个中文字如果 DPI 是 144150% 缩放同样 150 像素只够显示七八个中文字。所以写死像素值会带来 DPI 适配问题。简单的做法是先用 GetSystemMetrics(SM_CXSMICON) 或者用 Dialog 本身的 DC 计算字符串宽度来动态设定// 根据第一格要显示的最长字符串来设置宽度 CClientDC dc(this); CFont* pOldFont dc.SelectObject(GetFont()); CSize sz dc.GetTextExtent(_T(正在处理文件路径较长的那个文件)); m_wndStatusBar.SetPaneInfo(0, ID_DEFAULT_PANE, SBPS_STRETCH, sz.cx 20); dc.SelectObject(pOldFont);GetTextExtent 拿到的像素宽度会随 DPI 变化而变化这样状态栏宽度就能适配不同的屏幕缩放。如果嫌麻烦也可以用固定像素加一个经验系数我在 150% 缩放的机器上一般把 150 改成 220能凑合但不算雅观。追求稳妥还是走 GetTextExtent 路线。3.3 更新时机什么时候塞文本进去往状态栏写文字本身只是一个赋值动作但什么时候写、在哪里写决定了这个状态栏看起来像不像真的。最常见的用法是程序启动时在 OnInitDialog 里写“就绪”和当前时间业务操作开始前写“正在处理…”操作完成后写“完成共耗时 X 秒”。这个更新逻辑往往散落在各个按钮的响应函数里状态栏对象是 Dialog 的成员所以访问起来没有任何障碍。顺便说一下如果你把状态栏写成一个公开接口比如用 GetStatusBar() 返回 CStatusBar 引用那么其他模块也能直接改状态栏文本这在单文档架构 migrate 到 Dialog 工程时特别实用。不过接口设计得克制一点窗口对象暴露越多越容易乱我一般只在 Dialog 顶层做状态栏更新子控件之间不共享状态栏访问权。// 在按钮处理函数里更新状态栏 void CMyDlg::OnBnClickedBtnProcess() { m_wndStatusBar.SetPaneText(0, _T(正在处理请稍候...)); // 模拟耗时操作 CWaitCursor wait; Sleep(2000); m_wndStatusBar.SetPaneText(0, _T(处理完成)); }Sleep(2000) 只是为了演示真实场景里如果耗时操作是在工作线程里跑状态栏更新就不应该在 UI 线程里阻塞。工作线程里更新 MFC 控件是危险动作正确的姿势是用 PostMessage 给 Dialog 发一个自定义消息让 Dialog 在主线程里处理完成后的状态栏刷新。这个细节很多人踩过坑线程里直接 SetPaneText界面不定时崩溃。4. 状态栏添加中的避坑清单从一片空白到布局错位4.1 状态栏显示空白SetPaneText 完全没有效果现象Dialog 底部出现一条灰条窗格也能看到分割线但不管怎么调 SetPaneText窗格里一个字都不显示。原因indicators 数组里的资源 ID 在 rc 文件里对应的字符串表项是空的或者干脆没有这些 ID 的字符串条目。MFC 的 CStatusBar 在创建时会把窗格的默认文本初始化为资源 ID 对应的字符串如果没有这个字符串窗格文本就永远是空后续 SetPaneText 虽然改了内部数据但因为初始绘制资源缺失重绘时拿不到有效字体表现就是空白。解决把两个 ID 在字符串表里各加一条记录内容可以为空字符串也可以写默认文本比如 IDS_PANE_READY 对应“就绪”。更聪明一点的办法直接用自定义消息 SetPaneText不依赖指示器数组的初始文本。但稳妥起见还是在资源里加上字符串条目成本最低。4.2 状态栏颜色跟 Dialog 背景不搭灰底突兀现象Dialog 用了深色背景或自定义背景色状态栏还是默认的灰色视觉上像贴了一块膏药。原因CStatusBar 默认使用系统的 3D 面颜色作为背景它不会自动继承父窗口的画刷。解决处理状态栏的 WM_CTLCOLOR 消息或者更直接一点在 Dialog 的 OnCtlColor 里判断控件类型对状态栏返回自定义画刷。HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor); if (pWnd-GetDlgCtrlID() ID_VIEW_STATUS_BAR) { pDC-SetBkColor(RGB(240, 240, 240)); pDC-SetTextColor(RGB(0, 0, 0)); return (HBRUSH)::GetStockObject(LTGRAY_BRUSH); } return hbr; }注意 GetDlgCtrlID 判断的是状态栏创建时传入的 ID不是窗格 ID。这个 ID 我起的值是 ID_VIEW_STATUS_BAR你也可以用任何未占用的常量。OnCtlColor 里返回的画刷每隔一段时间会被系统调用一次所以不要在里面创建 GDI 对象而不释放用 GetStockObject 最省事。4.3 Dialog 尺寸改变之后状态栏宽度没跟上现象用户拖大了 Dialog结果状态栏右侧露出白色空隙或者状态栏宽度超过客户区宽度被裁剪掉。原因CStatusBar 在首次布局时按创建那一刻的父窗口尺寸计算宽度之后除非收到 WM_SIZE 消息否则不会自更新。而 Dialog 改变了尺寸WM_SIZE 传递到了 Dialog但 CStatusBar 没有自己注册过消息映射所以它不会响应。解决在 Dialog 的 OnSize 里主动 MoveWindow 状态栏或调用 SetWindowPos 让它重新贴合客户区底部。更省心的方式把状态栏的 Dock 行为交给 MFC 的 Resize 机制——如果你的 Dialog 继承自 CDialogEx并且开着“自动调整子控件大小”的布局管理器可以给状态栏设置一个布局锚点锚定底部和左右两侧这样尺寸变化时状态栏会自动跟随。但 MFC 的布局管理器很笨我一般不用它直接在 OnSize 里手算更可靠。4.4 状态栏盖住了底部控件按钮被遮掉一半现象状态栏创建以后Dialog 底部的按钮或者进度条被压住了部分区域显示不出来。原因状态栏是创建的窗口它会占据父窗口底部的一块矩形区域如果你原本的控件布局把底部空间占满了状态栏一出现下面的控件就被遮住了。解决创建状态栏之前根据状态栏的高度把 Dialog 客户区撑大或者把底部控件往上移动。一个常见做法是在 OnInitDialog 里先创建状态栏再读取它的窗口位置把它的顶部坐标作为控件的布局下边界。这样可以避免写死像素值导致不同 DPI 下控件被盖住。// 创建状态栏后获取其顶部位置再调整底部控件的布局 CRect rcStatus; m_wndStatusBar.GetWindowRect(rcStatus); ScreenToClient(rcStatus); CRect rcBtn; GetDlgItem(IDC_BTN_OK)-GetWindowRect(rcBtn); ScreenToClient(rcBtn); // 把按钮底部挪到状态栏上方 10 像素处 rcBtn.bottom rcStatus.top - 10; GetDlgItem(IDC_BTN_OK)-MoveWindow(rcBtn);这套逻辑本质是把状态栏当布局参照物而不是靠脑补固定坐标。实际工程里控件很多的时候更好的办法是干脆把 Dialog 的高度加大让状态栏不跟任何控件交叠从根源上避开。4.5 状态栏上图标或位图显示模糊现象在窗格里显示图标图像发虚缩放时明显有锯齿。原因SetPaneIcon 或自定义绘制时用的 HICON 尺寸不匹配或者用了旧式的 LoadIcon 加载大图标。解决用 LoadImage 加载小尺寸图标16x16 或 20x20并明确指定 SM_CXSMICON 相关参数。不要用 LoadIcon那个加载出来是 32x32 的大图标在小窗格会被压缩。若你用的是 CStatusBar 的 SetPaneIcon注意这个函数的图标句柄需要自己 Release否则每次更新都会泄漏 GDI 句柄这也是很多 MFC 程序跑一段时间后界面花掉的原因。5. 进阶玩法让状态栏变成真正的状态信息中枢5.1 在状态栏里滚动显示文件处理进度前面说的是静态文本真正实用的是把状态栏当成一个动态信息条。比如你在做一个文件批处理工具需要在底部显示“正在处理第 3 个文件共 12 个耗时 4 秒”这时候你需要一个统一的更新入口。我自己的习惯是封装一个 UpdateStatus(int paneIndex, const CString text) 方法专门做字符串格式化后再塞进状态栏同时配合 CTime 计算耗时。这样业务代码里写起来很干净void CMyDlg::UpdateStatus(LPCTSTR lpszText) { if (!::IsWindow(m_wndStatusBar.GetSafeHwnd())) return; m_wndStatusBar.SetPaneText(0, lpszText); }为什么要先做 IsWindow 判断因为这个接口很可能在工作线程的回调里被调用而工作线程结束时状态栏可能已经被销毁了。虽然前面说过跨线程更新控件不安全但某些历史代码免不了会碰那至少用 IsWindow 挡一下避免访问已销毁的 HWND 直接崩溃。5.2 让状态栏背上定时刷新时间、CPU ID、内存占用状态栏第二格放时间是最常见的需求实现方式是在 Dialog 的 OnInitDialog 里启动一个定时器然后在 OnTimer 里更新时间。这个套路同样适合放 CPU ID、内存占用率等信息。当年我把 CPU ID 和内存占用塞进 Dialog 状态栏的时候就遇到了一个头疼的问题获取 CPU ID 需要执行 CPUID 指令或读注册表这个操作如果放在 UI 线程里每秒钟跑一次界面会明显卡顿。做法是开一个工作线程定时采集数据把结果放到一个共享变量里UI 线程只负责读共享变量然后刷新显示。时间显示每 30 秒刷一次就够了不需要 1 秒一次CPU ID 是静态信息启动时查一次全缓存起来内存占用可以 2 秒一次数据量不大但也要注意线程安全和 UI 刷新频率。下面是一段在状态栏显示内存占用率的示例用全局内存状态结构体只读取不分配避免频繁 GDI 操作void CMyDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent ID_TIMER_STATUS) { MEMORYSTATUSEX ms; ms.dwLength sizeof(ms); GlobalMemoryStatusEx(ms); CString strInfo; strInfo.Format(_T(内存使用率%d%%), (int)ms.dwMemoryLoad); m_wndStatusBar.SetPaneText(1, strInfo); } CDialogEx::OnTimer(nIDEvent); }注意 GlobalMemoryStatusEx 里 dwMemoryLoad 表示当前内存使用百分比0 到 100 的整数。这个值可以直接显示不需要再做转换。如果你要显示的是本机 CPU 型号或 CPU ID需要提前查一次不要在 OnTimer 里反复查——CPUID 指令的调用虽然快但配合 WMI 查询就慢了WMI 查询一次能卡几十毫秒放在定时器里会一卡一卡的。5.3 把状态栏和控件消息联动Hover 提示与按钮状态高端的玩法是让状态栏扮演一个“上下文提示条”。比如鼠标悬停在某个按钮上时状态栏显示该按钮的功能说明鼠标离开时恢复默认文本。MFC 里做这个也不复杂只要响应 WM_MOUSEMOVE 并判断当前鼠标坐标在哪个控件上即可。常见做法是为对话框主窗口处理 WM_MOUSEMOVE调用 ChildWindowFromPoint 找到鼠标所在子窗口再根据子窗口 ID 查表切换状态栏文本。void CMyDlg::OnMouseMove(UINT nFlags, CPoint point) { CWnd* pChild ChildWindowFromPoint(point); if (pChild ! NULL pChild-GetDlgCtrlID() IDC_BTN_PROCESS) { m_wndStatusBar.SetPaneText(0, _T(点击此按钮开始处理文件此操作不可撤销)); } CDialogEx::OnMouseMove(nFlags, point); }这里有个性能细节OnMouseMove 刷新频率极高如果每次移动都 SetPaneText状态栏会疯狂重绘导致整条状态栏闪烁甚至 CPU 占用飙高。我一般会加一个“当前提示文本”的缓存只有当文本真的变化了才去更新static CString strLast; CString strNow _T(点击此按钮开始处理文件); if (strNow ! strLast) { m_wndStatusBar.SetPaneText(0, strNow); strLast strNow; }这种防抖处理在做 Hover 提示时是必要的不然一条状态栏能把一个好好的工具拖成卡顿。5.4 状态栏字体和高度调优让显示不糊不挤CStatusBar 默认使用的字体是系统状态栏字体在中文系统上一般还凑合但如果你在状态栏里显示中文和数字混排字体间距会忽大忽小视觉上很糟心。可以在创建后调用 SetFont 替换成 Dialog 的默认字体或者更大一点的字体让文字更清晰。字体设置的方式是CFont* pFont GetFont(); // 取 Dialog 本身字体 m_wndStatusBar.SetFont(pFont);字体变化会影响状态栏的期望高度。如果你发现状态栏里的文字被上下裁掉多半是状态栏高度不够。CStatusBar 的高度跟着窗格内文本的字体高度走换了大字体之后高度会自动变大。但如果没变可以手动指定窗格高度m_wndStatusBar.SetPaneInfo(0, ID_DEFAULT_PANE, SBPS_STRETCH, 0); CRect rcPane; m_wndStatusBar.GetItemRect(0, rcPane);GetItemRect 拿到的是窗格矩形高度就是状态栏内可用高度如果有裁切说明状态栏整体高度不够需要用 MoveWindow 手动把高度加八九个像素。这种情况在自定义字体后比较常见只能微调。6. 验证这套方案的三个信号和一条经验想确认状态栏真的加对了可以看三个信号。第一Dialog 启动瞬间状态栏立即出现没有闪烁跳变这是创建成功后 OnInitDialog 正常返回的表现。第二窗口移动或缩放时状态栏保持在客户区底部不盖控件、不漏空隙。第三用 SPY 或 SpyLite 挂上 Dialog 的窗口句柄能看到一个类名为 msvctls_statusbar32 的子窗口——这是 CStatusBar 的真实窗口类名也是判断这条状态栏是否为真状态栏的硬标准。我在项目里出现过一次状态栏文字显示正常但窗口类名对不上后来发现那是自己用 CStatic 画的伪状态栏虽然好看但根本没有窗格分割能力等于白做。我个人的习惯是写状态栏相关代码时所有 SetPaneText 调用都先判断一下 m_wndStatusBar.GetSafeHwnd() 是否为空。这个习惯救过我两次一次是 Dialog 关闭过程中定时器还在触发状态栏窗口已被销毁另一次是工作线程还未结束Dialog 先被用户关了。加一层判断虽然不能根治生命周期问题但至少能把崩溃点挡住让你有日志可查。总的来说MFC Dialog 里加状态栏不是拖一个控件的事它是在另一个窗口体系里“搭桥”。只要把位置、尺寸、刷新这三个点管住这条状态栏就能稳定跑很多年。希望帮到你。本文还有配套的精品资源点击获取
返回列表