我是否允许在油漆循环外使用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上绘画是否有效?
在绘画周期中,似乎有两种方法可以让DC进行绘画:
dc = GetDC(hWnd);
调用BeginPaint(安培; PAINTSTRUCT);
还有第三种方法,但它似乎是我开发的Borland Delphi的一个错误。
在WM_PAINT处理过程中,德尔福认为wParam是一个DC,并继续绘制它。而MSDN表示WM_PAINT消息的wParam未使用。
我对HDC的真正目标is to try to keep a persistent GDI+ Graphics object,这样我就可以使用GDI +的一些性能更好的功能,这些功能依赖于持续的DC。
在WM_PAINT消息处理期间,我想将一个GDI +图像绘制到画布上。以下nieve版本非常慢:
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);
}
性能提升来自于创建CachedBitmap一次,所以在程序初始化时:
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 i将与我的窗口的DC相同。这意味着它可以通过以下方式存活:
我发现在MSDN中没有任何内容可以保证只要窗口存在就会将相同的DC用于特定窗口。
注意:我没有使用双缓冲,because i want to be a good developer, and do the right thing。有时这意味着双缓冲是不好的。
答案 0 :(得分:5)
有一些例外,但一般情况下,每次拨打GetDC
或BeginPaint
时,您都可能会获得不同的DC。因此,您不应该尝试在DC中保存状态。 (如果你必须为了性能而这样做,你可以为一类窗口或特定的窗口实例创建特殊的DC,但它听起来并不像你真正需要或想要的那样。)
然而,大多数情况下,这些DC都是兼容的。它们将代表相同的图形模式,因此即使您获得不同的DC,兼容的位图也应该有效。
有些Windows消息可以告诉您图形模式何时发生变化,例如WM_DISPLAYCHANGE
和WM_PALETTECHANGED
。您可以监听这些,并重新创建缓存的位图。由于这些是罕见的事件,因此您不必担心在此时重新创建缓存位图对性能的影响。
您还可以获取主题更改等内容的通知。那些不改变图形模式 - 它们是更高级别的概念 - 所以你的缓存位图应该仍然与你得到的任何DC兼容。但是,如果您想在主题更改时更改位图,您也可以收听WM_THEMECHANGED
。
答案 1 :(得分:5)
我知道的唯一方法可能(或可能不)做你想要的是用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标志混淆:
分配一个设备上下文以供类中的所有窗口共享。由于窗口类是特定于进程的,因此应用程序的多个线程可以创建同一类的窗口。线程也可以尝试同时使用设备上下文。发生这种情况时,系统只允许一个线程成功完成其绘图操作。
如果所有其他方法都失败了,只有reconstruct CachedBitmap。
构造CachedBitmap对象时,必须将Graphics对象的地址传递给构造函数。如果在构建缓存的位图后,与该Graphics对象关联的屏幕的位深度发生了更改,则DrawCachedBitmap方法将失败,您应该重新构建缓存的位图。或者,您可以挂钩显示更改通知消息并在那时重新构建缓存的位图。
我并不是说CS_OWNDC是一个完美的解决方案,但它 朝着更好的解决方案迈出了一步。
修改强>
在使用CS_OWNDC标志进行屏幕分辨率/位深度更改测试期间,示例程序似乎保留了相同的DC,但是,当删除该标志时,DC的不同(Window 7 64位Ultimate)(应该在不同的操作系统版本上工作相同......虽然测试没有坏处。)
<强> EDIT2 强>
此示例不调用GetUpdateRect来检查是否需要在WM_PAINT期间绘制窗口。这是一个错误。
答案 2 :(得分:2)
你可以在任何一个窗口上画画。他们都有效。一个窗口不只有一个可以一次代表它的直流。因此,每次调用GetDC - 并且BeginPaint在内部执行此操作时,您将获得一个新的,唯一的直流,但仍然代表相同的显示区域。 当你完成它们时,只需ReleaseDC(或EndPaint)。在Windows 3.1的时代,设备上下文是有限的或非常昂贵的系统资源,因此鼓励应用程序永远不要抓住它们,而是从GetDC缓存中检索它们。如今,在窗口创建时创建一个dc是完全可以接受的,并在窗口的生命周期内缓存它。
唯一的“问题”是,当处理WM_PAINT
时,BeginPaint返回的dc将被剪切到无效的rect,而保存的dc则不会。
但我不了解你试图用gdiplus实现的目标。通常,如果一个物体被长时间选择为直流,则该直流是存储器直流,而不是窗口直流。
每次调用GetDC时,您都会获得一个新的HDC,表示具有自己状态的不同设备上下文。因此,在一个DC上设置的对象,背景颜色,文本模式等不会影响通过对GetDC或BeginPaint的不同调用检索到的另一个DC的状态。
系统不能随机使客户端检索到的HDC无效,并且实际上在后台做了大量工作以确保在显示模式切换之前检索到HDC,继续运行。即使改变位深度,技术上使得DC不兼容,也不会以任何方式阻止应用程序继续使用hdc进行blit。
也就是说,明智地看看WM_DISPLAYCHANGE,释放所有缓存的DC和设备位图,并重新创建它们。