Win32:一个窗口在整个生命周期中是否具有相同的HDC?

我可以在涂料周期之外使用DC吗? 我窗口的DC是否保证永久有效?

我想弄清楚我的控件的设备上下文(DC)有效多久。

我知道我可以打电话:

GetDC(hWnd); 

获取我的控件窗口的设备上下文,但这是允许的吗?

Windows向我发送WM_PAINT消息时,我应该调用BeginPaint / EndPaint来正确地确认我已经绘制了它,并在内部清除无效区域:

 BeginPaint(hWnd, {out}paintStruct); try //Do my painting finally EndPaint(hWnd, paintStruct); end; 

但是调用BeginPaint也会在PAINTSTRUCT结构中返回一个DC。 这是我应该画的DC。

在说BeginPaint()返回的DC与我从GetDC()得到的DC相同的文档中,我找不到任何东西。

尤其是现在,在桌面组合的日子里,在BeginPaint之外获得的DC上绘制它是否有效?

似乎有2种方法可以在绘画周期中获得DC绘画:

  1. dc = GetDC (hWnd);

  2. 调用BeginPaint(&PAINTSTRUCT);

还有第三种方法,但它似乎是与我开发的Borland Delphi的一个bug。

在WM_PAINT处理期间,Delphi认为wParam是一个DC,然后继续绘制它。 而MSDN说WM_PAINT消息的wParam是未使用的。

为什么

我真正的目标是试图保持一个持久的GDI +graphics对象与一个HDC,所以我可以使用一些更好的执行function的GDI +依赖于持久性DC。

在WM_PAINT消息处理期间,我想绘制一个GDI +图像到canvas。 以下的版本非常慢:

 WM_PAINT: { PAINTSTRUCT ps; BeginPaint(m_hwnd, ps); Graphics g = new Graphics(ps.hdc); g.DrawImage(m_someBitmap, 0, 0); g.Destroy(); EndPaint(h_hwnd, ps); } 

GDI包含一个更快的执行位图,一个CachedBitmap。 但是没有思考的使用就没有任何性能优势:

 WM_PAINT: { PAINTSTRUCT ps; BeginPaint(m_hwnd, ps); Graphics g = new Graphics(ps.hdc); CachedBitmap bm = new CachedBitmap(m_someBitmap, g); g.DrawCachedBitmap(m_bm, 0, 0); bm.Destroy(); g.Destroy(); EndPaint(h_hwnd, ps); } 

性能增益来自创buildCachedBitmap一次,因此在程序初始化时:

 m_graphics = new Graphics(GetDC(m_hwnd)); m_cachedBitmap = new CachedBitmap(b_someBitmap, m_graphcis); 

而现在在油漆周期:

 WM_PAINT: { PAINTSTRUCT ps; BeginPaint(m_hwnd, ps); m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0); EndPaint(h_hwnd, ps); } 

除了现在我相信,程序初始化后获得的DC,只要应用程序正在运行,我的窗口的DC就是相同的DC。 这意味着它通过:

  • 快速的用户切换
  • 组合启用/禁用
  • 主题切换
  • 主题禁用

我发现MSDN中没有任何东西可以保证只要窗口存在,相同的DC将用于特定的窗口。

注意:我不使用双缓冲, 因为我想成为一名优秀的开发人员,并且做正确的事情 。 有时这意味着你双缓冲是不好的。

有一些例外,但是一般情况下,每次调用GetDCBeginPaint时可能会得到不同的DC。 所以你不应该试图在DC中保存状态。 (如果你必须这样做才能提高性能,那么你可以为一类窗口或特定窗口实例创建特殊的DC,但听起来不像你真正需要或想要的那样。)

然而,大多数时候,这些区议会是兼容的。 它们将代表相同的图形模式,所以即使您获得不同的DC,您的兼容位图也应该可以工作。

Windows消息会告诉您何时更改图形模式,如WM_DISPLAYCHANGEWM_PALETTECHANGED 。 您可以听取这些,并重新创建您的缓存位图。 由于这些是罕见的事件,因此您不必担心在此时重新创建缓存位图的性能影响。

您还可以获取有关主题更改的通知。 那些不改变图形模式 – 他们是一个更高层次的概念 – 所以你的缓存的位图应该仍然与你得到的任何DC兼容。 但是,如果您想在主题更改时更改位图,则还可以侦听WM_THEMECHANGED

我知道的唯一方法可能(或不可能)做你正在寻找的是用CS_OWNDC类风格创建窗口。

所做的是为类中的每个窗口分配一个唯一的设备上下文。

编辑

从链接的MSDN文章:

设备上下文是应用程序用于在其窗口的客户区中绘图的一组特殊值。 系统需要显示器上每个窗口的设备上下文,但在系统如何存储和处理该设备上下文方面允许一些灵活性。

如果没有明确给出设备上下文样式,则系统假设每个窗口使用从系统维护的上下文池中检索的设备上下文。 在这种情况下,每个窗口都必须在绘画之前检索和初始化设备上下文,并在绘画之后将其释放。

为了避免每次需要在窗口内绘画时检索设备上下文,应用程序可以为窗口类指定CS_OWNDC样式。 这种类风格指示系统创建私有设备上下文 – 也就是为类中的每个窗口分配唯一的设备上下文。 应用程序只需要检索一次上下文,然后将其用于所有后续的绘制。

Windows 95/98 / Me:尽管CS_OWNDC风格很方便,但请谨慎使用它,因为每个设备上下文都使用64K GDI堆的重要部分。

也许这个例子会更好地说明CS_OWNDC的使用:

 #include <windows.h> static TCHAR ClassName[] = TEXT("BitmapWindow"); static TCHAR WindowTitle[] = TEXT("Bitmap Window"); HDC m_hDC; HWND m_hWnd; LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static PAINTSTRUCT ps; switch (msg) { case WM_PAINT: { BeginPaint(hWnd, &ps); if (ps.hdc == m_hDC) MessageBox(NULL, L"ps.hdc == m_hDC", WindowTitle, MB_OK); else MessageBox(NULL, L"ps.hdc != m_hDC", WindowTitle, MB_OK); if (ps.hdc == GetDC(hWnd)) MessageBox(NULL, L"ps.hdc == GetDC(hWnd)", WindowTitle, MB_OK); else MessageBox(NULL, L"ps.hdc != GetDC(hWnd)", WindowTitle, MB_OK); RECT r; SetRect(&r, 10, 10, 50, 50); FillRect(m_hDC, &r, (HBRUSH) GetStockObject( BLACK_BRUSH )); EndPaint(hWnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hWnd, msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASSEX wcex; wcex.cbClsExtra = 0; wcex.cbSize = sizeof(WNDCLASSEX); wcex.cbWndExtra = 0; wcex.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH ); wcex.hCursor = LoadCursor( NULL, IDC_ARROW ); wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wcex.hIconSm = NULL; wcex.hInstance = hInstance; wcex.lpfnWndProc = WndProc; wcex.lpszClassName = ClassName; wcex.lpszMenuName = NULL; wcex.style = CS_OWNDC; if (!RegisterClassEx(&wcex)) return 0; DWORD dwExStyle = 0; DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE; m_hWnd = CreateWindowEx(dwExStyle, ClassName, WindowTitle, dwStyle, 0, 0, 300, 300, NULL, NULL, hInstance, NULL); if (!m_hWnd) return 0; m_hDC = GetDC(m_hWnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } 

CS_OWNDC标志不要与CS_CLASSDC标志混淆:

分配一个设备上下文以由该类中的所有窗口共享。 由于窗口类是特定于进程的,因此应用程序的多个线程可以创建相同类的窗口。 线程也可以尝试同时使用设备上下文。 发生这种情况时,系统只允许一个线程成功完成绘图操作。

如果所有其他的失败只是重建 CachedBitmap。

构建CachedBitmap对象时,必须将Graphics对象的地址传递给构造函数。 如果与该Graphics对象关联的屏幕在构造缓存位图后更改了位深度,则DrawCachedBitmap方法将失败,您应该重新构建缓存的位图。 或者,您可以挂钩显示更改通知消息,并在此时重新构建缓存的位图。

我并不是说CS_OWNDC是一个完美的解决方案,但它迈向更好解决方案的一步。

编辑

在使用CS_OWNDC标志进行屏幕分辨率/位深度更改测试期间,示例程序似乎保留了相同的DC,但是当该标志被移除时,DC不同(Window 7 64位Ultimate)( 应该在不同的操作系统版本…虽然不会伤害测试)。

EDIT2

这个例子不调用GetUpdateRect来检查WM_PAINT期间是否需要绘制窗口。 这是一个错误。

你可以绘制到任何一个视窗dc请你。 他们都是有效的。 一个窗口并不只有一个可以代表它的dc。 所以每次你调用GetDC,并且BeginPaint在内部都这样做,你会得到一个新的,独特的dc,但它代表了相同的显示区域。 只要释放DC(或EndPaint),当你完成它们。 在Windows 3.1设备上下文是一个有限的,或非常昂贵的系统资源的日子,所以鼓励应用程序永远不能坚持他们,而是从GetDC缓存中检索它们。 现在它完全可以接受在创建窗口时创建一个dc,并在窗口的生命周期中缓存它。

唯一的问题是在处理WM_PAINT ,BeginPaint返回的dc会被剪切成无效的rect,保存的不会。


我不明白你想用gdiplus实现什么。 通常情况下,如果一个对象被长时间选择到一个dc中,那么这个dc是一个内存dc,而不是一个窗口dc。


每次GetDC被调用时,都会得到一个新的HDC,代表一个独立的设备上下文和自己的状态。 因此,在一个DC上设置的对象,背景颜色,文本模式等不会影响通过对GetDC或BeginPaint的不同调用检索到的另一个DC的状态。

系统不能随机使客户端检索到的HDC无效,实际上在后台做了大量的工作,以保证在显示模式切换之前检索HDC,继续运行。 即使改变位深度,技术上使得dc不兼容,也不会以任何方式阻止应用程序继续使用hdc blit。

也就是说,最好在WM_DISPLAYCHANGE上注意,释放所有缓存的DC和设备位图,然后重新创建它们。