The requirement is to draw my information in side of another application's window.
To take care of z order and so forth hooking WH_GETMESSAGE and draw on WM_PAINT seem good.
However some WM_PAINT are intended for the window area of my concern, but other WM_PAINT are for something completely different, like a context menu or button.
Example Notepad is hooked with an overlay writing "Hello" into the Notepad screen. This works fine. However when right clicking Notepad the context menu gets overlay with Hello. Basically the context menu is destroyed.
Is there an elegant way of determining what WM_PAINT is context menu?
LRESULT CALLBACK overlayHook(int code, WPARAM wParam, LPARAM lParam) {
//Try and be the LAST responder to WM_PAINT messages;
LRESULT retCode = CallNextHookEx(hhk, code, wParam, lParam);
//Per GetMsgProc documentation, don't do anything fancy
if(code < 0) {
return retCode;
}
//Assumes that target application only draws when WM_PAINT message is
//removed from input queue.
if(wParam == PM_NOREMOVE) {
return retCode;
}
MSG* message = (MSG*)lParam;
if(message->message != WM_PAINT) {
//Ignore everything that isn't a paint request
return retCode;
}
PAINTSTRUCT psPaint;
BeginPaint(message->hwnd, &psPaint);
HDC hdc = psPaint.hdc;
RECT r = psPaint.rcPaint;
TextOut(hdc, 10, 10, "Hello", 4);
EndPaint(message->hwnd, &psPaint);
return retCode;
}
It is not enough to test for the draw update region, because the context menu could be anywhere and contain the area of my concern.
I don't know of any elegant way to do this, but you could use GetWindowLong() to get the window's style or GetClassName() to get its class name, and then base your filtering decisions on those values.
Related
I have a program that paints to the client area about 60hz using Direct3D 9, and the mouse is interfering, so I want to get rid of it only when it moves across the client area.
I thought that calling ShowCursor(false) in WM_MOUSEMOVE and calling ShowCursor(true) when WM_NCMOUSEMOVE is called by the system would work, but it results in a poor behavior.
So I found that TrackMouseEvent() would make the job, but I'm calling it in the following way:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static bool g_fMouseTracking = false;
switch (message)
{
case WM_MOUSEMOVE:
if (!g_fMouseTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_NONCLIENT;
tme.dwHoverTime = HOVER_DEFAULT;
tme.hwndTrack = hWnd;
g_fMouseTracking = TrackMouseEvent(&tme);
}
break;
case WM_NCMOUSEHOVER:
ShowCursor(true);
break;
...
and WM_NCMOUSEHOVER is never called. I don't know why.
Anyway, this is only one piece of code, to do what I want I know I need more code, but if it's not calling WM_NCMOUSEMOVE I can't start doing more advanced mouse hovering tricks.
When you want to track WM_NCMOUSEHOVER you must use TrackMouseEvent in WM_NCMOUSEMOVE.
I am using GDI* plus to do custom drawing, but I have a drawing error when my window gets drawn under a windows explorer window, it looks like this:
As u can see just under the explorer window.. the colors are weird.. the top right are buttons and the checkbox is also a button .. "Are you.." is a static control.. they are all inherited and implemented as custom controls.. when receiving WM_PAINT. I also use a buffered image in WM_PAINT.. anyway.. I can't explain this, any ideas? It works fine when not under windows explorer window, as u can see in the left site of the window.
The checkbox is a button control, use a MSG_OCM_DRAWITEM(OnPaintImpl) handler in which I get the DC as so:
LRESULT OnPaintImpl(UINT ctrlID, LPDRAWITEMSTRUCT lpDIS)
{
ATLASSERT(GdiPlus::IsInitialized());
OnPaintGdiPlus(lpDIS->hDC, lpDIS->rcItem, lpDIS->itemState);
return S_OK;
}
and in my OnPaintGdiPlus(HDC hDC, CRect rc, UINT nState) I do this:
CMemoryDC dcMem(hDC, rc);
Graphics graphics(dcMem);
Rect rcClient = GdiPlus::GetRect(rc);
Everything else is just calling basic drawing functions from the graphics.
In the dialog I get WM_PAINT and handle it here:
LRESULT OnPaintImpl(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(&ps);
if (ps.rcPaint.right || ps.rcPaint.bottom) // draw rect is defined
{
if (GdiPlus::IsInitialized())
OnPaintGdiPlus(hDC, ps, GetClientRect(m_hWnd));
else
::MessageBox(m_hWnd, L"Graphics mode not initialized properly!", L"Graphics", MB_OK | MB_ICONWARNING);
}
EndPaint(&ps);
return S_OK;
}
Based on this hDC I create a Graphics object and paint using it.
Any other stuff I should add here?
I asked a related question earlier and realised that not calling ValidateRect in the application as response to WM_PAINT causes tremendous slowdown.
Why is this? How could this affect a DirectX application in such a manner?
// ----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
switch(msg) {
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;
case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
Your application is slowing down because it is rendering far more than necessary. It is receiving a furious stream of WM_PAINT messages.
Windows sends a WM_PAINT message to a window when it thinks the window should be painted. It makes this determination based on whether a window has been invalidated. Once you have rendered the window completely, you must tell Windows that this is the case by validating the window’s client area. As long as there is some invalidated area, Windows will continue to send your window WM_PAINT messages.
Before DirectX, application windows were validated when they called EndPaint. You can still call BeginPaint and EndPaint, wrapping whatever DirectX rendering you’re doing, or you can simply call ValidateRect when you’re done rendering.
I created a window with the following flags to overlay a d3d application:
WS_EX_TOPMOST | WS_EX_COMPOSITED | WS_EX_TRANSPARENT | WS_EX_LAYERED
I proceeded with colorkeying the window for transperacy and all worked well.
However once I began drawing on it using GDI an unforeseen problem occurred:
For some reason the mouse events (especially movement) are not passed correctly through the window when WM_PAINT is in progress, and so it appears as though the mouse and the keyboard for that matter lag. the FPS is fine, this is some API problem, I suspect that for some reason the keyboard/mouse messages are not handled as they should while the WM_PAINT is in progress, because the slower the timer is set to the less jerking there is.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
{
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
break;
}
case WM_CREATE:
{
SetTimer(hwnd, ID_TIMER, 10, NULL);
break;
}
case WM_TIMER:
{
InvalidateRect(hwnd, 0, 1);
break;
}
case WM_PAINT:
{
paint(hwnd);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
and
void paint (HWND hwnd)
{
PAINTSTRUCT Ps;
HDC hdc = BeginPaint(hwnd, &Ps);
SetBkColor(hdc, RGB(0,0,0));
SetBkMode(hdc, TRANSPARENT);
LOGBRUSH log_brush;
log_brush.lbStyle = BS_NULL;
HBRUSH handle_brush = CreateBrushIndirect(&log_brush);
SelectObject(hdc, handle_brush);
..........................................
DeleteObject(font);
DeleteObject(pen);
DeleteObject(handle_brush);
EndPaint(hwnd, &Ps);
}
Thank you for any help you may be able to give.
WM_PAINT messages are never delivered to your window unless someone calls UpdateWindow or there are no keyboard or mouse messages in your input queue.
Once you begin processing WM_PAINT, if a keyboard or mouse message arrives, it just sits in your queue until you are done with WM_PAINT. So What you are describing isn't possible.
If your WM_PAINT code takes a long time to execute, that could cause jerkiness, but you say that's not a problem so perhaps it's your handling of WM_ERASEBKGND? I don't see that code, but I do see that when you InvalidateRect, you are passing TRUE as the last parameter which means that you want the background to be erased.
If you don't handle WM_ERASEBKGND, then DefWindowProc will do it for you erasing your entire window with the brush from from your window class. This could result in windows thinking that no part of your window is transparent.
If you want mouse messages to pass through your window, a more reliable way is to handle the WM_NCHITTEST message and return HTTRANSPARENT where you want the mouse to pass through.
This is basically what how the WS_EX_TRANSPARENT style works. like this
case WM_NCHITTEST:
{
lRet = DefWindowProc(hwnd, uMsg, wParam, lParam);
if (HTCLIENT == lRet)
lRet = HTTRANSPARENT;
}
If your window has no non-client area, then you can skip the call to DefWindowProc.
WndProc() is not always re-entrant. I believe with the main message pump, the mouse and keyboard messages are queued up and waiting for you to finish the prior WM_PAINT message. Conversely, if you were to call SendMessage() from WndProc(), then you are looking at re-entrace. The other case is PostMessage() which would add the message to the queue. Maybe look at using DirectInput for mouse and keyboard input if this is an issue. Otherwise, look for ways to speed up your drawing.
I have a very simple Win32 application that uses CAtlExeModuleT. The module simply creates a class CTestWindow derived from CWindowImpl. It just has a single message handler for WM_PAINT. After I create the window and display it, the OnPaint method (WM_PAINT message) is called infinitely and there by consumes 100% CPU.
The code that creates the window is very simple:
m_pMainWnd = new CTestWindow();
if(NULL == m_pMainWnd->Create(NULL, CWindow::rcDefault, _T("Test Window"), WS_OVERLAPPEDWINDOW, 0, hMenu)){
DWORD dwErr = GetLastError();
return E_FAIL;
}
m_pMainWnd->ShowWindow(nShowCmd);
The OnPaint message handler is very simple as well (it doesn't do anything):
LRESULT CTestWindow::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// TODO: Add your message handler code here and/or call default
return 0;
}
My guess is that you are not validating the window in your paint handler.
An application must call BeginPaint
and EndPaint in response to WM_PAINT
messages, or pass the message to the
DefWindowProc function to validate the
window. DefWindowProc validates the
update region; it can send the
WM_ERASEBKGND message if the window
background needs to be erased.
This would mean the OS will think the window still needs to be painted, and call you again.