书籍:《Visual C++ 2017从入门到精通》的4.2 按钮控件

环境:visual studio 2022

内容:[例 4.2]制作图片按钮

说明:以上内容大部分来自腾讯元宝。

在 MFC 中,CDC::FromHandle() 是连接 Windows 原生设备上下文(HDC)与 MFC 设备上下文类(CDC)的核心接口。以下从功能、实现机制、使用场景及注意事项等方面进行详细分析:


​一、功能概述​

CDC::FromHandle(HDC hDC) 的作用是将 Windows 原生设备上下文句柄(HDC)封装为 MFC 的 CDC 类对象,提供对设备上下文的面向对象操作接口。
​核心价值​​:

  1. ​封装性​​:隐藏底层 GDI 函数调用细节,通过 MFC 成员函数(如 DrawTextRectangle)简化绘图操作。
  2. ​资源管理​​:自动维护 HDC 与 CDC 的映射关系,避免直接操作句柄带来的风险
  3. ​兼容性​​:支持从不同来源(如窗口、内存 DC)获取 HDC 并转换为 CDC。

​二、实现原理​

1. ​​源码分析​​(基于搜索结果)
CDC* CDC::FromHandle(HDC hDC)
{
    CHandleMap* pMap = afxMapHDC(TRUE);  // 获取全局句柄映射表
    CDC* pDC = (CDC*)pMap->FromHandle(hDC);
    ASSERT(pDC == NULL || pDC->m_hDC == hDC);
    return pDC;
}
  • ​关键步骤​​:
    • 通过全局 CHandleMap 管理 HDC 与 CDC 的映射关系。
    • 优先从永久映射表m_permanentMap)查找,若不存在则创建临时 CDC 对象并存入临时映射表m_temporaryMap)。
    • 返回的 CDC 对象与 HDC 绑定,但需手动释放资源。
2. ​​映射管理机制​
​映射类型​ ​生命周期​ ​存储位置​ ​典型场景​
​永久映射​ 与窗口/控件绑定 m_permanentMap 对话框或控件的 CDC 对象
​临时映射​ 仅在当前作用域有效 m_temporaryMap 动态创建的 CDC(如内存 DC)

​三、使用场景​

1. ​​窗口绘制​​(基于搜索结果)
void CMyWnd::OnPaint()
{
    CPaintDC dc(this);  // 自动调用 BeginPaint
    CDC* pDC = CDC::FromHandle(dc.m_hDC);  // 转换为 CDC 对象
    
    pDC->SetTextColor(RGB(255,0,0));
    pDC->DrawText(_T("Hello MFC"), &dc.GetClientRect(), DT_CENTER);
}
  • ​优势​​:通过 CDC 的封装,避免直接调用 TextOut 等 GDI 函数。
2. ​​内存绘图​
HDC hMemDC = CreateCompatibleDC(hdc);
CDC* pMemDC = CDC::FromHandle(hMemDC);  // 封装内存 DC

CBitmap bitmap;
bitmap.CreateCompatibleBitmap(hdc, 100, 100);
pMemDC->SelectObject(&bitmap);

pMemDC->FillSolidRect(0,0,100,100, RGB(0,255,0));  // 绘制到内存 DC
  • ​用途​​:实现双缓冲绘图,避免屏幕闪烁。
3. ​​控件自定义绘制​
void CMyButton::PreSubclassWindow()
{
    ModifyStyle(0, BS_OWNERDRAW);
    CButton::PreSubclassWindow();
}

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    pDC->SetBkMode(TRANSPARENT);
    pDC->DrawText(_T("Custom Button"), &lpDrawItemStruct->rcItem, DT_CENTER);
}
  • ​实现自绘按钮​​:通过 DrawItem 处理 WM_DRAWITEM 消息。

​四、注意事项​

1. ​​资源释放​
  • ​HDC 必须手动释放​​:
    HDC hDC = ::GetDC(hWnd);
    CDC* pDC = CDC::FromHandle(hDC);
    // ... 绘图操作 ...
    ::ReleaseDC(hWnd, hDC);  // 必须显式释放
  • ​避免重复释放​​:同一 HDC 被多个 CDC 对象共享时,需确保仅释放一次。
2. ​​线程安全​
  • ​跨线程限制​​:HDC 仅能在创建它的线程中使用。若需跨线程操作,需使用 ::DuplicateDC 复制 HDC。
3. ​​错误处理​
HDC hDC = ::GetDC(hWnd);
if (hDC == NULL) {
    TRACE(_T("Failed to get DC! Error: %d\n"), GetLastError());
    return;
}
CDC* pDC = CDC::FromHandle(hDC);
ASSERT(pDC != nullptr);  // 检查映射是否成功
4. ​​与智能指针结合​
// 使用 CHandle 管理 HDC 生命周期
CHandle hDC = ::GetDC(hWnd);
CDC* pDC = CDC::FromHandle(hDC);
// 当 hDC 超出作用域时自动调用 ReleaseDC

​五、与同类函数的对比​

​函数​ ​用途​ ​返回值类型​ ​生命周期管理​
CDC::FromHandle HDC → CDC 封装 CDC* 需手动释放 HDC
CWnd::GetDC 获取窗口客户区 DC CDC* 自动调用 ReleaseDC
CClientDC 构造函数 封装客户区 DC(自动管理资源) CClientDC 析构时自动 ReleaseDC
::CreateCompatibleDC 创建内存 DC HDC 需手动调用 DeleteDC

​六、调试技巧​

  1. ​验证映射关系​​:

    CDC* pDC = CDC::FromHandle(hDC);
    ASSERT(pDC->m_hDC == hDC);  // 检查是否成功绑定
  2. ​跟踪 DC 操作​​:

    pDC->SetMapMode(MM_LOMETRIC);  // 设置映射模式为 0.1mm 单位
    TRACE(_T("Current Map Mode: %d\n"), pDC->GetMapMode());
  3. ​内存泄漏检测​​:

    • 使用 Visual Studio 的内存分析工具,监控 HDC 和 CDC 对象的创建/销毁。

​七、扩展应用​

1. ​​多显示器支持​
HDC hMonitorDC = ::CreateDC(_T("\\\\.\\DISPLAY2"), NULL, NULL, NULL);
CDC* pMonitorDC = CDC::FromHandle(hMonitorDC);
pMonitorDC->FillSolidRect(0,0,800,600, RGB(0,0,255));  // 在第二屏幕绘制
::DeleteDC(hMonitorDC);
2. ​​打印预览​
CPrintDialog printDlg(FALSE);
if (printDlg.DoModal() == IDOK) {
    HDC hPrinterDC = printDlg.GetPrinterDC();
    CDC printerDC(hPrinterDC);
    DOCINFO di = { sizeof(DOCINFO), _T("Print Job") };
    printerDC.StartDoc(&di);
    printerDC.StartPage();
    // 打印内容...
    printerDC.EndPage();
    printerDC.EndDoc();
}

​总结​

CDC::FromHandle() 是 MFC 中连接底层 GDI 与高层图形操作的关键桥梁,其核心价值在于​​封装 HDC 的复杂性​​并提供安全的资源管理。开发者需注意:

  1. ​严格配对资源操作​​(如 GetDC/ReleaseDC
  2. ​避免跨线程直接传递 HDC​
  3. ​优先使用 MFC 封装类​​(如 CClientDC)以简化代码

通过合理运用此函数,可显著提升图形代码的可维护性和健壮性。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐