7

我正在编写一个全局钩子 DLL,它需要在窗口上使用 GDI+ 进行一些绘图以响应事件。我的问题是正在绘制的窗口不断地重新绘制自己,所以我画的东西在我想要它之前就被删除了。只要我需要,有什么办法可以防止窗户画任何东西?

我的钩子目前是一个WH_CALLWNDPROC钩子。绘图是使用 GDI+ 响应消息完成的WM_SIZING。我使用 GDI+ 在窗口DC(即GetWindowDC)上绘图。我正在绘制的内容是正确绘制的,但在重新绘制窗口客户区域时几乎立即被删除。创建我正在绘制的窗口的程序是记事本。当光标闪烁时,我绘制的内容会被删除。

有谁知道我可以暂时暂停画窗的方法吗?

谢谢!

4

4 回答 4

5

我建议将您的图形放在与目标窗口重叠的分层窗口中。这似乎是最干净的方法。毕竟,在某种概念上,这是窗口管理器的目的 :)

于 2010-11-23T21:59:50.320 回答
4

tenfour 的答案在我看来是最好的,但他/她必须解释如何去做,而不仅仅是告诉做什么,所以现在将解释如何:

注意:如果您想要我的解决方案中的主要思想,那么您可以跳到下面的最后一步 9(从下到上开始搜索,直到找到它)。

对不起,如果我的回答夸大了,解释和详细太多了,但我保证如果你能正确阅读,你会理解并感到满意。

第 1 步:创建新WNDCLASSEX结构,然后cbSize使用关键字将其成员设置为此结构的大小(以字节为单位),sizeof并将lpszClassName成员设置为您想要的任何内容。还将 hbrBackground 成员设置为 GetStockObject一函数的返回值,以获得黑色、白色或灰色画笔函数以获取您想要的任何颜色的画笔。选择所有图纸根本使用的颜色非常重要!!!而不仅仅是为了您的快乐! CreateSolidBrush

例如:

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpszClassName = "myNewClassName";
HBRUSH background_brush = GetStockObject(BLACK_BRUSH);
wcex.hbrBackground = background_brush;

第二步:定义一个新函数,即窗口过程,然后为其创建新的函数指针。然后将lpfnWndProc成员设置wcex为该函数指针。

第三步:在新的窗口过程函数定义中,添加return 0;最后一行代码。

在它上面切换 uMsg 参数,并至少添加WM_PAINT大小写。在这种情况下,定义 的新变量PAINTSTRUCT,并调用BeginPaintEndPaint函数。在这两个函数中,您都引用变量&ps,例如 ifpsPAINTSTRUCT变量的名称。

调用函数BeginPaint,在目标窗口上添加你想要的所有绘图函数,但要确保HDC所有绘图函数属于目标窗口,而是属于你在窗口的第一个参数中有句柄的窗口程序,即hWnd!!!首先调用其中一个 GetDC GetWindowDC函数来检索 hWnd 的 hDC 。

注意:您根本不需要检索目标窗口的 hDc!

不要忘记在语句default: return DefWindowProc(hWnd, uMsg, wParam, lParam);块内添加以下代码行。switch

第四步:RegisterClassEx使用函数注册类。

例如:RegisterClassEx(&wcex);

第 5 步:在您的应用程序中,使用该功能创建新的分层窗口。将此函数的第一个参数设置为声明您创建的新窗口是分层CreateWindowEx的,这一点非常重要。WS_EX_LAYERED

第二个参数设置为新注册类的名称,例如"myNewClassName",忽略第三个参数(设置为NULL),并在第四个参数中设置以下标志:WS_POPUP | WS_VISIBLE

注意WS_POPUP将创建没有边框的新窗口。这就是我忽略的hIcon成员wcex以及设置为 You can replace with的第三个参数的原因。结果将是相同的,但还会绘制轮廓灰色矩形,整个客户区只有 1 个像素的厚度。不绘制任何与 形成对比的东西,只是像它一样删除边框。NULLWS_POPUPWS_POPUPWINDOWWS_POPUPWINDOWWS_POPUPWS_POPUPWINDOW

我还设置了WS_VISIBLE标志以显示窗口。如果你不设置这个标志,那么你必须在ShowWindow之后调用函数CreateWindowEx,以显示分层窗口。

第6步:调用SetLayeredWindowAttributes函数后CreateWindowEx。将第一个CreateWindowEx参数设置为从函数返回的新创建的分层窗口的句柄。

第二个参数设置为窗口客户端的背景颜色,即存储在background_brush示例变量中的实心画笔的颜色,用于设置类型为 的 wcex 的 hbrBackground 成员WNDCLASSEX

注意这个参数的数据类型是COLORREF,而不是HBRUSH,即它不接受实心画笔,而只是一种颜色!

如果您知道在GetStockObject函数中选择的实心画笔的颜色,那么您就知道如何创建它的COLORREF结构。

如果您CreateSolidBrush改为调用函数,则在调用它之前,定义新的COLORREF结构变量,该变量将存储实体画笔的颜色和将来的 crKey。稍后您可以在两个函数中使用该变量:CreateSolidBrushSetLayeredWindowAttributes(在第二个参数中,即crKey)。

如果您按照上面的示例进行操作,并且您只有background_brush类型为HBRUSH存储 wcex 的 hbrBackground 成员的实体画笔的变量,并且您没有任何适当的 COLORREFcrKey参数,那么:

为了获得类型为结构的实心画笔的颜色,HBRUSH然后COLORREF

可以使用 GetObject 函数获取画笔的 LOGBRUSH 信息,其中的 lbColor 分量会给出 COLORREF 中的颜色值。您也可以使用 GetRValue、GetGValue 和 GetBValue 函数来获取各个颜色分量。

普拉文对必须知道如何获得坚实刷子COLORREF的人的回答。HBRUSH

以下是更多信息的链接:

http://forums.codeguru.com/showthread.php?423605-Color-of-HBRUSH

如果您想要另一种方法来检索函数参数的正确COLORREF结构,那么您可以先检索新创建的分层窗口的 ,然后尝试该函数。改为调用函数。将第一个参数设置为新分层窗口的 dc,并将第二个参数设置为 wcex 或上述示例的成员。crKeySetLayeredWindowAttributesGetBkColorhDcSelectObjecthbrBackgroundbakcground_brush

然后只需调用GetDCBrushColor函数即可获取函数参数的COLORREF结构。crKeySetLayeredWindowAttributes

忽略第三个参数(设置bAlphaNULL),在第四个参数中只设置LWA_COLORKEY标志。crKey 是 hbrBackground 的颜色,分层窗口的整个客户端不会被绘制,除了它们颜色不是的其他像素crKey。现在您所要做的就是将分层窗口绑定到目标窗口的客户端。

第 7 步:如果您还没有目标窗口的句柄,则调用其中一个 FindWindow 或函数来检索目标窗口的句柄。 FindWindowEx

第 8 步:编写消息循环(或选择并复制下面的代码并将其粘贴到您的代码中):

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

第 9 步(最后):在消息循环中(在 while 循环的块内),无论在哪里(在 TranslateMessage 之前或在 DispatchMessage 之后或它们之间),编写另一段代码,将每时每刻将分层窗口绑定到目标窗口的客户:

首先创建新的 POINT 结构,并将其坐标设置为 (0; 0)(将其xy成员设置为 0)。

然后调用ClientToScreen函数。将第一个参数设置为目标窗口的句柄(从FindWindoworFindWindowEx函数返回),并在第二个参数中引用您之前创建的 POINT 结构。

调用ClientToScreen函数后,您的 POINT 结构存储目标窗口最左上边缘的坐标(以屏幕坐标为单位,以像素为单位测量)。

现在您必须检索目标窗口客户端的大小。为此,接下来定义RECT类型为结构的新变量。

然后调用GetClientRect函数。将第一个参数设置为目标窗口的句柄,并在第二个参数中引用类型为RECT结构的变量。

调用GetClientRect函数后,RECT变量存储目标窗口客户端的宽度和高度,以像素为单位。right成员存储宽度,成员bottom存储高度。忽略类型为结构的变量的lefttop 成员。RECT它们不使用也不设置,并且总是有它们的默认值,即仅在这种情况下为 0。

在获得目标窗口客户端的位置(位置和大小)之后,现在可以通过调用或函数将分层窗口绑定到目标窗口的客户端 如下所示MoveWindow SetWindowPos

MoveWindow(hWnd, point.x, point.y, rect.right, rect.bottom, TRUE);
//If you want, you can set the last parameter to FALSE.
//OR
SetWindowPos(hWnd, hTargetWnd, point.x, point.y, rect.right, rect.bottom, NULL);
//If you want, you can specify some flags in the last parameter.
//Where hWnd is the handle of the layered window returned from CreateWindowEx function, and hTargetWnd is the handle of the target window returned from either FindWindow or FindWindowEx function.

如果您决定使用该MoveWindow功能,并且您在目标窗口上看不到您的绘画,那是因为分层窗口位于目标窗口下方。您将不得不更改对SetWindowPos函数的调用来解决此问题。在第二个参数中,我调用了我想将有界分层窗口放置在目标窗口上方的系统。如果它仍然不起作用,您可以更改此参数并将其设置为要么 HWND_TOP HWND_TOPMOST标志。

我确信在那之后它应该可以正常工作。有时不要忘记使分层窗口无效或重绘或更新分层窗口以更新目标窗口上的图形。您可以调用or or函数来执行此操作 InvalidateRect RedrawWindow UpdateWindow

例如:

POINT point;
ClientToScreen(hTargetWnd, &point);

RECT rect;
GetClientRect(hTargetWnd, &rect);

MoveWindow(hWnd, point.x, point.y, rect.right, rect.bottom, TRUE);
//OR
SetWindowPos(hWnd, hTargetWnd, point.x, point.y, rect.right, rect.bottom, NULL);

InvalidateRect(hWnd, NULL, TRUE);
//If you want, you can set the third parameter to FALSE.

(您可以选择并复制上面的示例代码并将其粘贴到您的代码中):

示例代码可以在消息循环中,表示分层窗口每时每刻都会被绑定和更新但如果你不想每时每刻,而是每次你做某事,或者每次发生某事时,与目标窗口,不属于您的应用程序,例如记事本,那么您必须调用该SetWindowsHookEx函数。您可以在 MSDN 站点和 Web 上的其他站点上获得有关该功能的更多信息。

如果您希望每时每刻都发生这种情况,但不是在消息循环中,那么您可以在其他线程的其他 while 循环中执行此操作,但您将为它创建一个函数并调用CreateThread函数。同样,您还可以在 MSDN 站点和 Web 上的其他站点上获取有关该功能的信息

确保当您的应用程序进程退出或终止时线程的 while 循环结束。

您还可以在 while 循环条件中尝试其中一个IsWindowIsWindowVisible函数,以确定在分层窗口或/和目标窗口被销毁或关闭已完成,不会被最小化或图标)时停止。

于 2014-08-24T20:36:41.300 回答
2

不。

相反,为什么不 hook WM_PAINT,所以你在他们画的时候画?

于 2010-11-23T21:41:27.150 回答
2

您可以尝试向窗口发送WM_SETREDRAWwParam消息,设置FALSE为暂停绘画,然后设置TRUE为恢复正常行为。

不过,这是一个相当侵入性的解决方案。

于 2010-11-23T21:44:49.103 回答