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

时间:2010-01-15 19:42:03

标签: windows gdi+ gdi paint device-context

我是否允许在油漆循环外使用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进行绘画:

  1. dc = GetDC(hWnd);

  2. 调用BeginPaint(安培; PAINTSTRUCT);

  3. 还有第三种方法,但它似乎是我开发的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。有时这意味着双缓冲是不好的。

3 个答案:

答案 0 :(得分:5)

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

然而,大多数情况下,这些DC都是兼容的。它们将代表相同的图形模式,因此即使您获得不同的DC,兼容的位图也应该有效。

有些Windows消息可以告诉您图形模式何时发生变化,例如WM_DISPLAYCHANGEWM_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和设备位图,并重新创建它们。