6

我可以在油漆周期之外使用直流电吗?我的窗口的 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);

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

WM_PAINT处理期间,Delphi 认为 wParam 是一个 DC,并继续在其上进行绘制。而 MSDN 说 WM_PAINT 消息的 wParam 未使用。

为什么

我的真正目标是尝试针对 HDC 保留持久 GDI+ 图形对象,以便我可以使用依赖于持久 DC 的 GDI+ 的一些性能更好的功能。

在 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 将与我的窗口相同。这意味着它可以通过以下方式生存:

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

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

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

4

3 回答 3

6

我知道的唯一可能(或可能不会)做你正在寻找的方法是创建具有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-bit Ultimate)(应该在不同的操作系统上工作相同)版本......虽然测试不会有什么坏处)。

编辑2

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

于 2010-01-15T22:44:35.997 回答
5

也有例外,但一般来说,您每次致电GetDC或时可能会获得不同的 DC BeginPaint。因此,您不应该尝试在 DC 中保存状态。(如果您为了性能必须这样做,您可以为一类窗口或特定窗口实例创建特殊的 DC,但听起来这并不是您真正需要或想要的。)

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

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

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

于 2010-01-15T22:41:20.450 回答
2

你可以在任何你喜欢的窗口上画画。它们都是有效的。一个窗口不只有一个 dc 可以一次代表它。因此,每次您调用 GetDC - 并且 BeginPaint 在内部这样做时,您都会获得一个新的、唯一的 dc,但它仍然代表相同的显示区域。完成后只需 ReleaseDC(或 EndPaint)。在 Windows 3.1 时代,设备上下文是一种有限或非常昂贵的系统资源,因此鼓励应用程序永远不要保留它们,而是从 GetDC 缓存中检索它们。如今,在创建窗口时创建一个 dc 并在窗口的生命周期内缓存它是完全可以接受的。

唯一的“问题”是,在处理时WM_PAINT,BeginPaint 返回的 dc 将被裁剪为无效的矩形,而保存的则不会。


然而,我不明白你试图用 gdiplus 实现什么。通常,如果一个对象被...长时间选择到一个 dc 中,则该 dc 是一个内存 dc,而不是一个窗口 dc。


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

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

也就是说,明智的做法是至少观察 WM_DISPLAYCHANGE,释放所有缓存的 DC 和设备位图,然后重新创建它们。

于 2010-01-15T20:13:14.200 回答