Win32:窗口的整个生命周期内是否都有相同的HDC?

Ian*_*oyd 5 windows gdi+ gdi paint device-context

我可以在油漆循环外使用DC吗?我的窗户的DC是否保证永久有效?

我想弄清楚我的控件的设备上下文(DC)有效多长时间.

我知道我可以打电话:

GetDC(hWnd);
Run Code Online (Sandbox Code Playgroud)

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

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

BeginPaint(hWnd, {out}paintStruct);
try
   //Do my painting
finally
   EndPaint(hWnd, paintStruct);
end;
Run Code Online (Sandbox Code Playgroud)

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

我在文档中找不到任何说明BeginPaint()返回的DC与我从GetDC()获得的DC相同的内容.

特别是现在,在桌面组合的时代,我在BeginPaint之外获得的DC上绘画是否有效?

在涂装周期中,似乎有两种方法可以让DC涂漆:

  1. dc = GetDC(hWnd);

  2. 调用BeginPaint(&PAINTSTRUCT);

还有第三种方法,但它似乎是我开发的Borland Delphi的一个错误.

WM_PAINT处理期间,Delphi认为wParam是一个DC,并继续绘制它.而MSDN表示WM_PAINT消息的wParam未使用.

为什么

我的真正目标是尝试将持久的GDI + Graphics对象与HDC保持一致,这样我就可以使用一些更好的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);
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

性能增益来自于创建CachedBitmap一次,所以在程序初始化时:

m_graphics = new Graphics(GetDC(m_hwnd));
m_cachedBitmap = new CachedBitmap(b_someBitmap, m_graphcis);
Run Code Online (Sandbox Code Playgroud)

现在在油漆周期:

WM_PAINT:
{
   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0);
   EndPaint(h_hwnd, ps);
}        
Run Code Online (Sandbox Code Playgroud)

除了现在我相信只要应用程序正在运行,程序初始化后获得的DC i对于我的窗口将是相同的DC.这意味着它可以通过以下方式存活:

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

我发现MSDN中没有任何内容可以保证只要窗口存在就会将相同的DC用于特定窗口.

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

Adr*_*thy 5

但也有例外,但在一般情况下,你可以在每次调用时得到不同的DC GetDCBeginPaint.因此,您不应该尝试在DC中保存状态.(如果你必须为了性能而这样做,你可以为一类窗口或特定的窗口实例创建特殊的DC,但它听起来并不像你真正需要或想要的那样.)

但是,大多数情况下,这些DC都是兼容的.它们将代表相同的图形模式,因此即使您获得不同的DC,兼容的位图也应该可以工作.

有些Windows消息可以告诉您图形模式何时发生变化,例如WM_DISPLAYCHANGEWM_PALETTECHANGED.您可以监听这些,并重新创建缓存的位图.由于这些是罕见事件,因此您不必担心在此时重新创建缓存位图会对性能产生影响.

您还可以获取主题更改等内容的通知.那些不改变图形模式 - 它们是更高级别的概念 - 所以你的缓存位图应该仍然与你得到的任何DC兼容.但是如果你想在主题改变时改变位图,你也可以听WM_THEMECHANGED.

  • 在应用程序的生命周期内保持DC通常是不好的形式,尽管你可能可以在更新版本的Windows上使用它.对于旧版本,"常见"DC是有限的资源.如果保留一个,可以强制操作系统耗尽.如果你真的需要在一个窗口的生命周期中保留一个,那么使用`CS_OWNDC`.请参见http://blogs.msdn.com/oldnewthing/archive/2006/06/01/612970.aspx (2认同)

小智 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;
}
Run Code Online (Sandbox Code Playgroud)

该CS_OWNDC标志与CS_CLASSDC标志,该标志相混淆:

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

如果所有其他方法都失败了,只需重新构建 CachedBitmap.

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

我不是说CS_OWNDC是完美的解决方案,但它朝着更好的解决方案迈出的一步.

编辑

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

EDIT2

此示例不调用GetUpdateRect来检查是否需要在WM_PAINT期间绘制窗口.那是一个错误.