Link to home
Start Free TrialLog in
Avatar of Lucidity
Lucidity

asked on

intercept WM_NCPAINT

What is the best way to intercept a WM_NCPAINT message going to a window. I want to find out when Non client area are going to be drawn so I can draw my own and stop the target process from getting the WM_NCPAINT. Should I hook DLL functions or use something in the message pump? and how do I tell if target is a full window, text box or a button given that I have the HWND.
Avatar of Madshi
Madshi

Do we talk about a window in your own application or do we talk about a window that belongs to a different application or do we even talk about system wide stuff?
Avatar of Lucidity

ASKER

yes, system wide. I'm sorry
Then I think the best way would be to use SetWindowsHookEx with either WH_CALLWNDPROC or WH_GETMESSAGE (you will have to try which one works better). You must put the callback function in a dll.

Regards, Madshi.
Madshi, why do you not use the "Answer" button?
Madshi,because what Madshi said is just
a comment.
Hi Nick...  :-)

Well, I can't give Lucidity any C++ sources, you know, I'm working with Delphi. So I'm not sure if my comments help him enough to earn those 300 points. If Lucidity is satisfied with my comments, he can accept them as the answer...
yes, source would be nice :-) too many people just cut and paste from MSDN. If that actually helped me I would never have to post here.
ASKER CERTIFIED SOLUTION
Avatar of NickRepin
NickRepin

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hehe...  :-)  Nick, I've an idea. Why not doing the following:

(1) Use your code, but with CALLWNDPROC instead.
(2) When receiving a NCPAINT message, *subclass* the receiving window (by calling SetWindowLong(GWL_WNDPROC, ...).
(3) In the wndProc of the subclassed window, handle the NCPAINT message, do your own drawing and don't give the message to the original wndProc.
(4) At the end of the subclassed wndProc call SetWindowLong(GWL_WNDPROC, ...) again to restore the old wndProc.

This should work funny, but it may slow down the performance a bit...

Regards, Madshi.
It's really a good idea. But I'm not sure that Windows will use the new proc address.
Lucidity have to try.
well. I didn't get it to work, did you? It did appear to catch any messages.
I tested this code before I post it here. It works fine.

I copiled it from the command-line

cl /LD hook.cpp user32.lib
cl main.cpp hook.lib user32.lib
Once you press Enter, the hook will be uninstalled.
Madshi is right. It's possible to intercept the sent message by subclassing in the hook proc.
Here is the code.

Madshi, 200 points to you from me.

//----------
// Main.cpp
//----------

//cl %1 %2 %3 %4 /LD hook.cpp user32.lib
//cl main.cpp hook.lib user32.lib

#include <windows.h>
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>

bool setHook(DWORD threadId);
void removeHook();

void main(int argc,char* argv[])
{
   if(setHook(0)) {
      cout<<"Press Enter to quit"<<endl;
      _getch();
   }  
   removeHook();
}

//----------
// Hook.dll
//----------

#include <windows.h>

#pragma data_seg(".shared")
HHOOK hHook1=0;
HHOOK hHook2=0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:.shared,RWS")

HINSTANCE hDll;

LPCSTR prop="TestPropName";

LRESULT CALLBACK wndProc(HWND hwnd,UINT uMsg,WPARAM wP,LPARAM lP)
{
   WNDPROC oldp=(WNDPROC)GetProp(hwnd,prop);
   if(uMsg==WM_NCPAINT) return 0;
   return CallWindowProc(oldp,hwnd,uMsg,wP,lP);
}

void onNCPaint1(CWPSTRUCT& msg)
{
   WNDPROC oldp=(WNDPROC) GetWindowLong(msg.hwnd,GWL_WNDPROC);
   if(SetProp(msg.hwnd,prop,HANDLE(oldp)))
      SetWindowLong(msg.hwnd,GWL_WNDPROC,LONG(wndProc));
}

void onNCPaint2(CWPRETSTRUCT& msg)
{
   WNDPROC oldp=(WNDPROC)GetProp(msg.hwnd,prop);
   if(oldp) {
      if(SetWindowLong(msg.hwnd,GWL_WNDPROC,LONG(oldp)))
         RemoveProp(msg.hwnd,prop);
   }
   // Here you can paint the non-client area yourself.
}


LRESULT CALLBACK hookProc1(int code,WPARAM wParam,LPARAM lParam)
{
   if(code==HC_ACTION) {
      CWPSTRUCT* msg=(CWPSTRUCT*)(lParam);
      if(msg->message==WM_NCPAINT) {
         onNCPaint1(*msg);
      }
   }
   return CallNextHookEx(hHook1,code,wParam,lParam);
}

LRESULT CALLBACK hookProc2(int code,WPARAM wParam,LPARAM lParam)
{
   if(code==HC_ACTION) {
      CWPRETSTRUCT* msg=(CWPRETSTRUCT*)(lParam);
      if(msg->message==WM_NCPAINT) {
         onNCPaint2(*msg);
      }
   }
   return CallNextHookEx(hHook2,code,wParam,lParam);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID)
{
   if(fdwReason==DLL_PROCESS_ATTACH)
      hDll=hinstDLL;
   return TRUE;
}

_declspec(dllexport) bool setHook(DWORD threadId)
{
   if(hHook1==0)
      hHook1=SetWindowsHookEx(WH_CALLWNDPROC,hookProc1,hDll,threadId);
   if(hHook2==0)
      hHook2=SetWindowsHookEx(WH_CALLWNDPROCRET,hookProc2,hDll,threadId);
   return hHook1 && hHook2;
}

_declspec(dllexport) void removeHook()
{
   if(hHook1) UnhookWindowsHookEx(hHook1);
   if(hHook2) UnhookWindowsHookEx(hHook2);
}
Madshi, which topic area do you prefer?
Delphi?
is this the begining of the hook.cpp file?

//----------
// Hook.dll
//----------

I am trying to compile it now and I get  fatal error C1034 : windows.h: no include path set. Probably just cuz I am getting confused :)
is this the begining of the hook.cpp file?

//----------
// Hook.dll
//----------

I am trying to compile it now and I get  fatal error C1034 : windows.h: no include path set. Probably just cuz I am getting confused :)
OK, I got it to work.. and it works well. Ocasionally the minimize,maximize and close will not show up but show up later when you click where the title bar was or is supposed to be. Is there a way to make it more consistant, either so that the buttons are always drawn or not.

I've also noticed that sometimes the bar and frame will get drawn in. I don't actually know why yet. any ideas.. I'll keep increasing the points as long as people are still contributing, then make sure I open some more token questions for the others.
Code marked //hook.dll must reside in a separate .cpp file and must be compiled as dll.

To allow the default NCPAINT action (buttons will always be drawn), use the code in my original answer, not in the comment below the answer.

The code in the comment shows how to prevent passing WM_NCPAINT to the target window.

The title bar (including buttons) can be drawn in response on WM_NCACTIVATE and WM_SETTEXT messages. Try to intercept these messages also, especially WM_NCACTIVATE (add additional if() in the hookProc).

Hi Nick, yes, my prefered topic area is Delphi programming, because there I can post my code. Here in the Windows forum, I can only say how to do it. Most people here don't understand Delphi sources...
On the other hand the questions here are often more interesting, because they're generally a bit more low level than the questions in the Delphi forum.
listening...
Well, I have been playing with this code and its looking promising. I have added a GetClassName call to the message intercepter and I get some interesting results from that, I can let scrollbars and edit controls maintain their frames while all other windows get the frame ripped.

Is there a way to find out if a window is of CMainFrame(?) or CDialog origin, those are the only windows I want to redraw myself.

Oh, and any time you right click on the process in the system tray the window get title bar gets drawn most times. I don't understand that..

<<right click on the process in the system tray >>
WM_SYSCOMMAND can also affect repainting.
As I said, it's hard to get the reliable result in your case.

Most dialogs has WC_DIALOG window class.

If under CMainFrame you mean the top-level window,
then you can check the following:
1) Window has no parent (GetParent()==0)
2) Window style is WS_POPUP or WS_OVERLAPPED and not WS_CHILD
3) Window has WS_CAPTION style



If your CMainFrame has min/max buttons or the system menu, check WS_MINIMIZEBOX/WS_MAXIMIZEBOX/WS_SYSMENU styles.
OK, I guess I've gotten enough.. it works good except for its not quite consistant. but it is a start
Thanks for points.
I don't think it will be consistent. It seems Windows doesn't expect that somebody will handle WM_NCPAINT globally.
Because Windows performs non-client painting "behind" WM_NCPAINT.
there must be a way to catch the other way it is drawing as well. I will try to figure it out or post another q.
Yes, you have to do it yourself. Use spy++ to check messages.