防止在 C++ 中重新绘制窗口
我正在编写一个全局钩子 DLL,它需要在窗口上使用 GDI+ 进行一些绘图以响应事件。我的问题是正在绘制的窗口不断重新绘制自身,因此我绘制的内容在我想要的之前就被删除了。有什么办法可以阻止窗户在我需要的时间内绘制任何东西吗?
我的钩子当前是一个 WH_CALLWNDPROC
钩子。绘图是使用 GDI+ 响应消息 WM_SIZING
完成的。我使用 GDI+ 在窗口的 DC
上绘图(即 GetWindowDC
)。我正在绘制的内容是正确绘制的,但随着窗口客户端区域被重新绘制,几乎立即被擦除。创建我正在使用的窗口的程序是记事本。当光标闪烁时,我绘制的内容就会被删除。
有谁知道我可以暂时暂停窗户绘画的方法吗?
谢谢!
I am writing a global hook DLL that needs to do some drawing using GDI+ on a window in response to an event. My problem is that the window that is being drawn keeps repainting itself, so what I draw gets erased before I want it to. Is there any way I can prevent the window from painting anything for as long as I need to?
My hook currently is a WH_CALLWNDPROC
hook. The drawing is done using GDI+ in response to the message WM_SIZING
. I drawing using GDI+ onto the window's DC
(i.e. GetWindowDC
). What I am drawing is drawn correctly, but gets erased almost instantly as the window client area gets repainted. The program that created the window I am drawing on is Notepad. As the cursor blinks, what I have drawn gets erased.
Does anyone know of a way I can temporarily suspend painting of the window?
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我建议将图形放在与目标窗口重叠的分层窗口中。这似乎是最干净的方式。毕竟,在某种程度上,这就是窗口管理器的目的:)
I would suggest putting your graphics in a layered window overlapping the target window. That seems to be the cleanest way. After all, at some level of conception, this is the purpose of the window manager :)
我认为 tenfour 的答案是最好,但他/她还必须解释如何做到这一点,而不仅仅是 > 告诉做什么,所以现在我将解释如何:
注意:如果您想要 >主要思想在我的解决方案中,然后您可以跳到下面的最后一步9(开始从下到上搜索,直到找到它)。
很抱歉,如果我的回答夸大了,解释和详细了太多,但我保证,如果您正确阅读,您会理解并感到满意。
第 1 步: 创建新的
WNDCLASSEX
结构,然后使用将其
关键字,并将cbSize
成员设置为此结构的大小(以字节为单位) >sizeoflpszClassName
成员设置为您想要的任何内容。同时将 hbrBackground 成员设置为 eitherGetStockObject
函数的返回值,以获取 either 黑色、or 白色< strong>或灰色画笔,ORCreateSolidBrush
函数来获取你想要的任何颜色的画笔。选择所有绘图都不使用的颜色非常重要!不仅仅是为了您的快乐!例如:
第2步:定义一个新函数,它是一个窗口过程,然后创建它的新函数指针。然后将
wcex
的lpfnWndProc
成员设置为该函数指针。第3步:在新的窗口过程函数定义中,添加
return 0;
,这是最后代码行。在其上方切换uMsg参数,并至少添加
WM_PAINT
案例。在这种情况下,定义新变量
PAINTSTRUCT
,并调用BeginPaint
和EndPaint
函数。在这两个函数中,如果ps
是PAINTSTRUCT
变量的名称,则可以引用&ps
等变量。调用
BeginPaint
函数后,在目标窗口上添加所有你想要的绘图函数,但要确保所有绘图函数中的HDC
都是不属于目标窗口,而是属于您在窗口过程的first参数中拥有其句柄的窗口,即hWnd
! !首先调用GetDC或
GetWindowDC
函数来检索hWnd的hDC。注意:您根本不需要检索目标窗口的 hDc!
不要忘记在
switch
语句块内添加以下代码行:default: return DefWindowProc(hWnd, uMsg, wParam, lParam);
。第 4 步:使用
RegisterClassEx
函数注册类。例如:
RegisterClassEx(&wcex);
第 5 步:在您的应用程序中,使用
CreateWindowEx< 创建新的分层窗口/代码> 功能。
非常重要,将此函数的first参数设置为
WS_EX_LAYERED
,以声明您创建的新窗口是分层的.将第二参数设置为新注册类的名称,例如
“myNewClassName”
,忽略第三参数(将其设置为NULL
),并在第四个参数中设置以下标志:WS_POPUP | WS_VISIBLE
注意:
WS_POPUP
将创建没有边框的新窗口。这就是我忽略wcex
的hIcon
成员以及将 third 参数设置为NULL
的原因您可以将
WS_POPUP
替换为WS_POPUPWINDOW
。结果将是相同的,但WS_POPUPWINDOW
还会绘制轮廓灰色矩形,整个客户区域的厚度只有 1 个像素。与WS_POPUPWINDOW
不同,WS_POPUP
不会不绘制任何内容,只是删除边框。我还设置了 WS_VISIBLE 标志来显示窗口。如果不设置此标志,则必须在
CreateWindowEx
之后调用ShowWindow
函数,才能显示分层窗口。第6步:在
CreateWindowEx
之后调用SetLayeredWindowAttributes
函数。将first参数设置为从CreateWindowEx
函数返回的新创建的分层窗口的句柄。将第二参数设置为窗口客户端的背景颜色,即存储在示例的
background_brush
变量中的实心画笔的颜色,您用于设置wcex 的 hbrBackground 成员,其类型为WNDCLASSEX
。注意该参数的数据类型是
COLORREF
,而不是HBRUSH
,即它不接受实心画笔,但只是一种颜色!如果您知道在
GetStockObject
函数中选择的实心画笔的颜色,那么您就知道如何创建其COLORREF
结构。如果您改为调用 CreateSolidBrush 函数,则在调用它之前,定义 COLORREF 结构的新变量,该变量将在将来存储实心画笔的颜色和 crKey。稍后您可以在以下两个函数中使用该变量:
CreateSolidBrush
和SetLayeredWindowAttributes
(在第二个参数中,即crKey
)。如果您遵循我上面的示例,并且您仅拥有类型为
HBRUSH
的background_brush
变量,用于存储 wcex 的 hbrBackground 成员的实心画笔,并且您没有为crKey
参数提供任何正确的 COLORREF,那么:为了获取类型为
HBRUSH
的实心画笔的颜色> 到COLORREF
结构,然后:Pravin 对那些必须知道如何获取
HBRUSH
的COLORREF
作为实体画笔的人的回答。以下是更多信息的链接:
http://forums.codeguru.com /showthread.php?423605-Color-of-HBRUSH
如果您想要另一种方式来检索
SetLayeredWindowAttributes 的
函数,然后您可以在先检索新创建的分层窗口的crKey
参数的正确COLORREF
结构hDc
后尝试GetBkColor
函数。或调用
SelectObject
函数。将first参数设置为新分层窗口的dc,并将second参数设置为wcex或bakcground_brush
的hbrBackground
成员上面的例子。然后,只需调用
GetDCBrushColor
函数即可获取SetLayeredWindowAttributes
函数的crKey
参数的COLORREF
结构。忽略第三参数(将
bAlpha
设置为NULL
),并在第四参数中仅设置LWA_COLORKEY
标志。crKey是hbrBackground的颜色,分层窗口的整个客户端不会被绘制,除了颜色不是
crKey
的其他像素。现在您所要做的就是将分层窗口绑定到目标窗口的客户端上。
第7步:调用任一
FindWindow
或FindWindowEx
函数来检索窗口句柄目标窗口(如果您还没有)。第 8 步:编写消息循环(或选择并复制下面的代码段并将其粘贴到您的代码中):
第 9 步(最后):在消息循环中(在while 循环的块)无论在哪里(TranslateMessage 之前或 DispatchMessage 之后或它们之间),编写另一段代码,每时每刻将分层窗口绑定到目标窗口的客户端:
首先创建新的 POINT 结构,并将其坐标设置为 (0; 0)
(将其
x
和y
成员设置为 0)。然后调用
ClientToScreen
函数。将first参数设置为目标窗口的句柄(从FindWindow
或FindWindowEx
函数返回),并在second< /strong> 参数引用您之前创建的 POINT 结构。调用
ClientToScreen
函数后,您的 POINT 结构存储目标窗口最左上边缘的坐标(在屏幕坐标中,以像素为单位测量)。现在您必须检索目标窗口客户端的大小。为此,接下来定义新变量,其类型为
RECT
结构。然后调用
GetClientRect
函数。将first参数设置为目标窗口的句柄,并在second参数中引用类型为RECT
结构的变量。调用
GetClientRect
函数后,RECT
变量存储目标窗口客户端的宽度和高度(以像素为单位)。right
成员存储宽度,bottom
成员存储高度。忽略left
和top
类型为
RECT
结构的变量的成员。它们没有被使用和设置,并且总是有它们的默认值,即仅在这种情况下为 0。获得目标窗口客户端的位置(位置和大小)后,现在可以通过调用 任一
MoveWindow
将分层窗口绑定到目标窗口的客户端 或SetWindowPos
函数,如下所示:如果您决定使用
MoveWindow
函数,并且遇到在目标上看不到您的绘画的问题窗口,这是因为分层窗口位于目标窗口下方。您必须更改对 SetWindowPos 函数的调用才能解决此问题。在第二个参数中,我调用了系统,希望将有界分层窗口放置在目标窗口的上方。如果仍然不起作用,您可以更改此参数并将其设置为任一
HWND_TOP
或HWND_TOPMOST
标志。我确信之后它应该可以正常工作。
有时不要忘记使分层窗口无效或重绘或更新以更新目标窗口上的绘图。您可以调用
InvalidateRect
或RedrawWindow
或UpdateWindow
函数来做到这一点。例如:(
您也可以选择并复制上面的示例代码,然后将其粘贴到您的代码中):
示例代码可以位于消息循环中,这意味着分层窗口将每时每刻 >,但是如果您不想要每时每刻,但每次您做某事或每次发生某事时,目标窗口不属于您的应用程序(例如记事本),那么您将拥有调用
SetWindowsHookEx
函数。您可以在 MSDN 站点和网络上的其他站点上获取有关该功能的更多信息。如果您希望这种情况每时每刻都发生,但不是在消息循环中发生,那么您可以在其他线程的其他 while 循环中执行此操作,但您必须为其创建一个函数并调用 CreateThread 函数。同样,您还可以在 MSDN 站点和网络上的其他站点上获取有关该函数的信息
确保当您的应用程序进程退出或<时,线程的 while 循环结束/strong> 终止。
您还可以在 while 循环条件中尝试
IsWindow
或IsWindowVisible
函数,以确定当分层窗口或/和目标窗口时停止 被摧毁或 关闭(已完成,不会被最小化或成为标志性的)。tenfour's answer is best in my opinion, but he/she also had to explain how to do it, not just to tell what to do, so now I will explain how:
NOTE: If you want the main idea in my solution, then you can skip to the last step 9 much below (begin search for it from bottom to top until you find it).
Sorry if I exaggerated with my answer and explained and detailed too much, but I promise if you will read properly, you will understand and be satisfied.
Step 1: Create new
WNDCLASSEX
structure, and then set itscbSize
member to the size of this structure, in bytes, by using thesizeof
keyword, and setlpszClassName
member to whatever you want. Also set the hbrBackground member to the return value of eitherGetStockObject
function to obtain either the black, or white or gray brush, ORCreateSolidBrush
function to obtain a brush of any color you want. It is very important to choose the color that all your drawings do not use at all!!! And not just for your pleasure!For example:
Step 2: Define a new function that is a window procedure, and then create new function pointer of it. Then set the
lpfnWndProc
member ofwcex
to that function pointer.Step 3: In the new window procedure function definition, add
return 0;
which is the last line of code.Above it switch uMsg parameter, and at least add the
WM_PAINT
case.In that case, define new variable of
PAINTSTRUCT
, and call theBeginPaint
andEndPaint
functions. In both functions, you reference the variable like&ps
, ifps
is the name of thePAINTSTRUCT
variable.After you call
BeginPaint
function, add all the drawing functions you want on the target window, but make sure that theHDC
in all drawing functions are not belong to the target window, but to the window which you have its handle in the first parameter of the window procedure, i.e.hWnd
!!!Call either
GetDC
orGetWindowDC
function to retrieve hWnd's hDC first of all.NOTE: You don't need to retrieve the target window's hDc at all!
Don't forget to add the following code line:
default: return DefWindowProc(hWnd, uMsg, wParam, lParam);
inside the block of theswitch
statement.Step 4: Register the class using the
RegisterClassEx
function.For example:
RegisterClassEx(&wcex);
Step 5: In your application, create new layered window by using the
CreateWindowEx
function.It is very important that you set the first parameter of this function to
WS_EX_LAYERED
to declare that the new window that you create is layered.Set the second parameter to the name of the new registered class, for example
"myNewClassName"
, ignore the third parameter (set it toNULL
), and in the fourth parameter, set the following flags:WS_POPUP | WS_VISIBLE
NOTE:
WS_POPUP
will create the new window without border. This is the reason I ignored thehIcon
member ofwcex
and also the third parameter set toNULL
You can replace
WS_POPUP
withWS_POPUPWINDOW
instead. The result will be the same, butWS_POPUPWINDOW
also draws outlined gray rectangle, thin with only 1 pixel thickness all over the client area.WS_POPUP
does not draw anything in contrast toWS_POPUPWINDOW
, just removes the border like it.I also set the
WS_VISIBLE
flag to show the window. If you don't set this flag, then you will have to callShowWindow
function afterCreateWindowEx
, in order to show the layered window.Step 6: Call
SetLayeredWindowAttributes
function afterCreateWindowEx
. Set the first parameter to the handle of the new created layered window returned from theCreateWindowEx
function.Set the second parameter to the background color of your window's client, i.e. to the color of your solid brush stored in the
background_brush
variable of the example, which you used to set the hbrBackground member of the wcex whose type isWNDCLASSEX
.Note that the data type of this parameter is
COLORREF
, and notHBRUSH
, i.e. it does not accept solid brush, but simply a color!If you know the color of the solid brush you chose in the
GetStockObject
function, then you know how to create itsCOLORREF
structure.If you called
CreateSolidBrush
function instead, then before you call it, define new variable ofCOLORREF
structure that will store the solid brush's color and the crKey in the future. Later you can use the variable in both functions:CreateSolidBrush
andSetLayeredWindowAttributes
(in the second parameter, i.e.crKey
).If you followed my example above, and you only have the
background_brush
variable whose type isHBRUSH
that stores the solid brush for the hbrBackground member of wcex, and you do not have any proper COLORREF for thecrKey
parameter, then:In order to obtain the color of the solid brush whose type is
HBRUSH
toCOLORREF
structure then:Pravin's answer to someone that had to know how to get
COLORREF
ofHBRUSH
as solid brush.Here is the link for more information:
http://forums.codeguru.com/showthread.php?423605-Color-of-HBRUSH
If you want another way to retrieve the proper
COLORREF
structure for thecrKey
parameter ofSetLayeredWindowAttributes
function, then you can try theGetBkColor
function after you have retrieved thehDc
of the new created layered window first.or call
SelectObject
function instead. Set first parameter to the new layered window's dc, and set second parameter to thehbrBackground
member of wcex orbakcground_brush
of the example above.Then just call the
GetDCBrushColor
function to get theCOLORREF
structure for thecrKey
parameter of theSetLayeredWindowAttributes
function.Ignore the third parameter (set
bAlpha
toNULL
), and in the fourth parameter set only theLWA_COLORKEY
flag.crKey is the color of hbrBackground, the entire client of the layered window won't be drawn, except other pixels that their color is not
crKey
.Now all what you have to do is to bound the layered window on the target window's client.
Step 7: Call either
FindWindow
orFindWindowEx
function to retrieve the handle of the target window, if you don't have it yet.Step 8: Write the message loop (or select and copy the piece of code below and paste it to yours):
Step 9 (last): In the message loop (inside the block of the while loop) no matter where (before TranslateMessage or after DispatchMessage or between them), write another piece of code that will every moment bound the layered window to the target window's client:
First of all create new POINT structure, and set its coordinates to (0; 0)
(Set its
x
andy
members to 0).Then call
ClientToScreen
function. Set the first parameter to the handle of the target window (returned from theFindWindow
orFindWindowEx
function), and in the second parameter reference the POINT structure, which you created before.After the call to
ClientToScreen
function, your POINT structure stores the coordinates of the most LEFT TOP edge of the target window (in screen coordinates, measured in pixels unit).Now you have to retrieve the size of the target window's client. To do so next define new variable whose type is the
RECT
structure.Then call
GetClientRect
function. Set the first parameter to the handle of the target window, and in the second parameter reference the variable whose type is theRECT
structure.After the call to
GetClientRect
function, theRECT
variable stores the width and the height of the target window's client, measured in pixels unit. Theright
member stores the width, and thebottom
member stores the height. Ignore theleft
andtop
members of the variable whose type is the
RECT
structure. They are not used and not set, and always will have their default value, i.e. 0 only in this case.After you have the position of the target window's client (the location and the size), now you can bound your layered window to the target window's client by calling either
MoveWindow
orSetWindowPos
function, like this:If you decided to use the
MoveWindow
function, and you have problem that you don't see your paintings on the target window, that is because the layered window is below the target window. You will have to change the call toSetWindowPos
function instead to solve this problem then.In the second parameter I invoked the system that I want to place the bounded layered window above the target window. If it still doesn't work, you can change this parameter and set it to either
HWND_TOP
orHWND_TOPMOST
flag.I am sure after that it should work fine.
Don't forget sometimes to invalidate or redraw or update the layered window to update your drawings on the target window. You can call either
InvalidateRect
orRedrawWindow
orUpdateWindow
function to do that.For example:
(You can select and copy the example code above and paste it to yours too):
The example code can be in the message loop, it means that the layered window will be bound and updated every moment, but if you do not want every moment, but everytime you do something, or everytime something is happening, with the target window, which not belongs to your application, like Notepad, then you'll have to make call to the
SetWindowsHookEx
function. You can get more information about that function on MSDN site and other sites on the web.If you want that to be happen every moment, but not in the message loop, then you can do it in other while loop of other thread, but you will have make a function for it and call
CreateThread
function. Again, you can also get information about that function on MSDN site and other sites on the webMake sure that the while loop of the thread ends when the process of your application is either exited or terminated.
You can also try either
IsWindow
orIsWindowVisible
function in the while loop condition to determine to stop when the layered window or/and the target window is/are being either destroyed or closed (finished, not going to be minimized or iconic).不。
相反,为什么不挂钩
WM_PAINT
,这样你就可以在他们绘制时绘制呢?No.
Instead, why not hook
WM_PAINT
, so you draw when they draw?您可以尝试向窗口发送 WM_SETREDRAW 消息,将
wParam
设置为FALSE
以暂停绘画,然后设置为TRUE
以恢复正常行为。不过,这是一个相当侵入性的解决方案。
You can try sending WM_SETREDRAW messages to the window, with
wParam
set toFALSE
to suspend painting, then set toTRUE
to restore normal behavior.That's quite an intrusive solution, though.