InvalidateRect is not repainting the window - c++

I got a polygon that is drawn whenever I press a button.
I wanted to repaint the polygon as I press the button again, but when I press it, it just paints another polygon without erasing the other one:
//header
#define CREATETRIANGLE 1
//WM_COMMAND
case 2:
PAINTPROCEDURE = CREATETRIANGLE;
InvalidateRect(hwnd, NULL, TRUE);
break;
//WM_PAINT
case WM_PAINT:
switch(PAINTPROCEDURE){
case 0:{
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd,&ps);
}
break;
case 1:
RedrawWindow(hwnd, &rect, NULL, RDW_NOCHILDREN); //I tried this function, but it did nothing
TriangleDC = BeginPaint(hwnd, &tps);
SelectPen(TriangleDC, CreatePen(PS_SOLID, 2, RGB(256,256,256)));
SelectBrush(TriangleDC, CreateSolidBrush(RGB(0,192,192)));
{
POINT vertices[] = {{baseX,baseY}, {(baseX-(triangle.sideB)),baseY}, {baseX,(baseY-triangle.sideC)}};
Polygon(TriangleDC, vertices, 3);
}
EndPaint(hwnd,&ps);
PAINTPROCEDURE = 0;
break;
I also tried to get the polygon out of its scope, but it did nothing as well.

it just paints another polygon without erasing the other one
When you call BeginPaint, the system will normally call WM_ERASEBKGND before returning. The WM_ERASEBKGND handler is then given a chance to "erase" the window. The default handler (from DefWndProc) will fill the area with the brush from the WNDCLASS for this window.
It sounds like the erasing isn't happening which can mean: (1) you've provided WM_ERASEBKGND handler that doesn't actually erase the screen, or (2) the hbrBackground in the WNDCLASS is set wrong or set to the null brush.
In either of those cases, what should happen is that the fErase field of the PAINTSTRUCT you got from BeginPaint will be set to something other than 0, which tells you that you [the WM_PAINT handler] still have to erase the window first.
Now sometimes people play games with their own custom WM_ERASEBKGND handler when trying to optimize painting (or in a miguided attempt to reduce flicker). That can result in the fErase flag being 0 even if you do need to erase first.
My advice is to let DefWndProc handle the WM_ERASEBKGND and to make sure you've got a proper value set for hbrBackground in the WNDCLASS. Once that works, you can experiment with other approaches.

Related

Why when I paint, and the mouse is moving in the window, the painting is not done at the same time everywhere? (WinAPI)

After asking this question, I changed my code. It works, but when WM_PAINT paints the window and the cursor is moving in it, the painting is not done at the same time everywhere. Here you have a video to see it better. This is my WM_PAINT:
//TV is a struct with the dimensions of the window.
//BitmapBuffer is the bitmap containing the painting.
static int cont;
cont++;
HDC HDc;
PAINTSTRUCT ps;
HDc = BeginPaint(identifier, &ps);
Gdiplus::Graphics lienzo (HDc);
AlphaBlend(HDc, 0, 0, TV.width+4, TV.height+4, buffer, 0, 0, TV.width+4, TV.height+4, CopyInfo);
EndPaint(identifier, &ps);
Since the problem is when moving the mouse, maybe the WM_NCHITTEST message has something to do with it:
case WM_NCHITTEST:
POINTS point1 = MAKEPOINTS(lParam);
POINT point2;
point2.x = point1.x;
point2.y = point1.y;
ScreenToClient(hwnd, &point2);
if (PtInRegion(region, point2.x, point2.y))
{
if (inWindow == false) //inWindow is true when the cursor is in the window
{
inWindow = true;
TrackMouseEvent(&structure);
Repaint(); //this function repaint the buffer and after call invalidrect
}
return HTCLIENT;
}
else
{
if (inWindow == true)
{
inWindow = false;
Repaint();
}
return HTTRANSPARENT;
}
break;
Does anyone have an idea why this happens?
It's hard to see the problem, because your code doesn't define Repaint. However, you should be using InvalidateRect to tell Windows which area is being updated.
THAT SAID...
Windows is hard to program for updating images with out using a double buffering technique. This is when you draw to a memory (bitmap) then bitblt the bitmap to the screen.
You find this answer useful Reduce flicker with GDI+ and C++

In C++ Win32, how do I prevent the window from flickering?

For my own amusement, I have been creating a 3D graphics program in C++ with Win32 that does not use any floating point numbers. For some reason, the window flickers sometimes when window is updated. I believe this to be caused by the window updating after what was previously shown in the window was erased and before what will be drawn next is drawn. I have found several web pages and questions with answers about what appears to be the same issue, except the solutions do not work.
Some examples{
Do not use CS_HREDRAW|CS_VREDRAW. (No noticeable effect)
Return 1 for the window message 'WM_ERASEBKGND'. (I need to erase what was previously drawn. This stops the flicker by preventing it from erasing.)
Instead of erasing, draw a rectangle in the case of WM_PAINT. (This made it worse)
}
How do I actually prevent window flicker?
// compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
boolean Draw;
//Trigonometry tables in increments of 1/256 of a rotation and multiplied by 65536
const int Total=3,Sine[256]={0,1608,3216,4821,6424,8022,9616,11204,12785,14359,15924,17479,19024,20557,22078,23586,25080,26558,28020,29466,30893,32303,33692,35062,36410,37736,39040,40320,41576,42806,44011,45190,46341,47464,48559,49624,50660,51665,52639,53581,54491,55368,56212,57022,57798,58538,59244,59914,60547,61145,61705,62228,62714,63162,63572,63944,64277,64571,64827,65043,65220,65358,65457,65516,65536,65516,65457,65358,65220,65043,64827,64571,64277,63944,63572,63132,62714,62228,61705,61145,60547,59914,59244,58538,57798,57022,56212,55368,54491,53581,52639,51665,50660,49624,48559,47464,46341,45190,44011,42806,41576,40320,39040,37736,36410,35062,33692,32303,30893,29466,28020,26558,25080,23586,22078,20557,19024,17479,15924,14359,12785,11204,9616,8022,6424,4821,3216,1608,0,-1608,-3216,-4821,-6424,-8022,-9616,-11204,-12785,-14359,-15924,-17479,-19024,-20557,-22078,-23586,-25080,-26558,-28020,-29466,-30893,-32303,-33692,-35062,-36410,-37736,-39040,-40320,-41576,-42806,-44011,-45190,-46341,-47464,-48559,-49624,-50660,-51665,-52639,-53581,-54491,-55368,-56212,-57022,-57798,-58538,-59244,-59914,-60547,-61145,-61705,-62228,-62714,-63162,-63572,-63944,-64277,-64571,-64827,-65043,-65220,-65358,-65457,-65516,-65536,-65516,-65457,-65358,-65220,-65043,-64827,-64571,-64277,-63944,-63572,-63162,-62714,-62228,-61705,-61145,-60547,-59914,-59244,-58538,-57798,-57022,-56212,-55368,-54491,-53581,-52639,-51665,-50660,-49624,-48559,-47464,-46341,-45190,-44011,-42806,-41576,-40320,-39040,-37736,-36410,-35062,-33692,-32303,-30893,-29466,-28020,-26558,-25080,-23586,-22078,-20557,-19024,-17479,-15924,-14359,-12785,-11204,-9616,-8022,-6424,-4821,-3216,-1608},Cosine[256]={65536,65516,65457,65358,65220,65043,64827,64571,64277,63944,63572,63132,62714,62228,61705,61145,60547,59914,59244,58538,57798,57022,56212,55368,54491,53581,52639,51665,50660,49624,48559,47464,46341,45190,44011,42806,41576,40320,39040,37736,36410,35062,33692,32303,30893,29466,28020,26558,25080,23586,22078,20557,19024,17479,15924,14359,12785,11204,9616,8022,6424,4821,3216,1608,0,-1608,-3216,-4821,-6424,-8022,-9616,-11204,-12785,-14359,-15924,-17479,-19024,-20557,-22078,-23586,-25080,-26558,-28020,-29466,-30893,-32303,-33692,-35062,-36410,-37736,-39040,-40320,-41576,-42806,-44011,-45190,-46341,-47464,-48559,-49624,-50660,-51665,-52639,-53581,-54491,-55368,-56212,-57022,-57798,-58538,-59244,-59914,-60547,-61145,-61705,-62228,-62714,-63162,-63572,-63944,-64277,-64571,-64827,-65043,-65220,-65358,-65457,-65516,-65536,-65516,-65457,-65358,-65220,-65043,-64827,-64571,-64277,-63944,-63572,-63162,-62714,-62228,-61705,-61145,-60547,-59914,-59244,-58538,-57798,-57022,-56212,-55368,-54491,-53581,-52639,-51665,-50660,-49624,-48559,-47464,-46341,-45190,-44011,-42806,-41576,-40320,-39040,-37736,-36410,-35062,-33692,-32303,-30893,-29466,-28020,-26558,-25080,-23586,-22078,-20557,-19024,-17479,-15924,-14359,-12785,-11204,-9616,-8022,-6424,-4821,-3216,-1608,0,1608,3216,4821,6424,8022,9616,11204,12785,14359,15924,17479,19024,20557,22078,23586,25080,26558,28020,29466,30893,32303,33692,35062,36410,37736,39040,40320,41576,42806,44011,45190,46341,47464,48559,49624,50660,51665,52639,53581,54491,55368,56212,57022,57798,58538,59244,59914,60547,61145,61705,62228,62714,63162,63572,63944,64277,64571,64827,65043,65220,65358,65457,65516};
int HorizontalAxis,VerticalAxis,SemiHorizontalAxis,SemiVerticalAxis,Speed=1,Phi=64,Theta,VelX,VelY,VelZ,SinTheta,CosTheta=65536,SinPhi,CosPhi=-65536,RegX,RegY,RegZ,Quadrilaterals[3][6]={{0,1,2,3,1,0},{0,3,4,5,0,1},{6,7,8,9,1,2}};
LONG64 AxAv,X,Y,Z,XCoordinates[10]={-5,-5,5,5,5,-5,60,60,120,120},YCoordinates[10]={-5,5,5,-5,-5,-5,80,140,140,80},ZCoordinates[10]={10,10,10,10,30,30,100,100,200,200},q$,w$,e$,r$,t$,y$,u$,i$;
POINT Points[3][4]={{{0,0},{0,0},{0,0},{0,0}}};
RECT WindowRect;
const HPEN Pens[2]={CreatePen(PS_NULL,0,0),CreatePen(PS_SOLID,1,RGB(0,0,0))};
const HBRUSH Brushes[3]={CreateSolidBrush(RGB(0,0,0)),CreateSolidBrush(RGB(128,128,128)),CreateSolidBrush(RGB(255,255,255))};
HINSTANCE hInst;
HWND hWnd;
PAINTSTRUCT ps;
HDC hdc;
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam){
switch(message){
case WM_KEYDOWN:
if(GetAsyncKeyState(16)){Speed=1+3*Speed/2;}
if(GetAsyncKeyState(17)){
Speed=2*Speed/3-1;
if(Speed<1){Speed=1;}
}if(GetAsyncKeyState(82)){
X=0;Y=0;Z=0;Theta=0;Phi=0;RegX=0;RegY=0;RegZ=0;
SinTheta=0;CosTheta=65536;SinPhi=0;CosPhi=65536;
}return 0;
case WM_SIZE:
GetWindowRect(hWnd,&WindowRect);
HorizontalAxis=WindowRect.right-WindowRect.left;
SemiHorizontalAxis=HorizontalAxis/2;
VerticalAxis=WindowRect.bottom-WindowRect.top-100;
SemiVerticalAxis=VerticalAxis/2;
AxAv=(HorizontalAxis+VerticalAxis)/2;
return 0;
case WM_PAINT:
hdc=BeginPaint(hWnd,&ps);
if(GetAsyncKeyState(83)){
if(Phi>0){
Phi--;
SinPhi=Cosine[Phi];CosPhi=-Sine[Phi];
}
}if(GetAsyncKeyState(87)){
if(Phi<128){
Phi++;
SinPhi=Cosine[Phi];CosPhi=-Sine[Phi];
}
}if(GetAsyncKeyState(65)){
Theta--;
if(Theta<0){Theta=255;}
SinTheta=Sine[Theta];CosTheta=Cosine[Theta];
}if(GetAsyncKeyState(68)){
Theta++;
if(Theta>255){Theta=0;}
SinTheta=Sine[Theta];CosTheta=Cosine[Theta];
}if(GetAsyncKeyState(39)){VelZ=-SinTheta*Speed;VelX=-CosTheta*Speed;}
if(GetAsyncKeyState(38)){VelZ+=CosTheta*Speed;VelX-=SinTheta*Speed;}
if(GetAsyncKeyState(37)){VelZ+=SinTheta*Speed;VelX+=CosTheta*Speed;}
if(GetAsyncKeyState(40)){VelZ-=CosTheta*Speed;VelX+=SinTheta*Speed;}
if(GetAsyncKeyState(81)){VelY=65536*Speed;}
if(GetAsyncKeyState(69)){VelY=-65536*Speed;}
RegX+=VelX;RegY+=VelY;RegZ+=VelZ;
X+=RegX/65536;Y+=RegY/65536;Z+=RegZ/65536;
RegX%=65536;RegY%=65536;RegZ%=65536;
VelX=0;VelY=0;VelZ=0;
q$=0;
while(q$<Total){
Draw=false;
w$=Quadrilaterals[q$][0];
e$=65536*(XCoordinates[w$]-X);
r$=65536*(YCoordinates[w$]-Y);
t$=65536*(ZCoordinates[w$]-Z);
y$=(CosTheta*e$+SinTheta*t$)/65536;
e$=(CosTheta*t$-SinTheta*e$)/65536;
u$=(CosPhi*r$+SinPhi*e$)/65536;
i$=(CosPhi*e$-SinPhi*r$)/65536;
if(i$<0){
Draw=true;
Points[q$][0].x=SemiHorizontalAxis+AxAv*y$/i$;
Points[q$][0].y=SemiVerticalAxis+AxAv*u$/i$;
}w$=Quadrilaterals[q$][1];
e$=65536*(XCoordinates[w$]-X);
r$=65536*(YCoordinates[w$]-Y);
t$=65536*(ZCoordinates[w$]-Z);
y$=(CosTheta*e$+SinTheta*t$)/65536;
e$=(CosTheta*t$-SinTheta*e$)/65536;
u$=(CosPhi*r$+SinPhi*e$)/65536;
i$=(CosPhi*e$-SinPhi*r$)/65536;
if(i$<0){
Draw=true;
Points[q$][1].x=SemiHorizontalAxis+AxAv*y$/i$;
Points[q$][1].y=SemiVerticalAxis+AxAv*u$/i$;
}w$=Quadrilaterals[q$][2];
e$=65536*(XCoordinates[w$]-X);
r$=65536*(YCoordinates[w$]-Y);
t$=65536*(ZCoordinates[w$]-Z);
y$=(CosTheta*e$+SinTheta*t$)/65536;
e$=(CosTheta*t$-SinTheta*e$)/65536;
u$=(CosPhi*r$+SinPhi*e$)/65536;
i$=(CosPhi*e$-SinPhi*r$)/65536;
if(i$<0){
Draw=true;
Points[q$][2].x=SemiHorizontalAxis+AxAv*y$/i$;
Points[q$][2].y=SemiVerticalAxis+AxAv*u$/i$;
}w$=Quadrilaterals[q$][3];
e$=65536*(XCoordinates[w$]-X);
r$=65536*(YCoordinates[w$]-Y);
t$=65536*(ZCoordinates[w$]-Z);
y$=(CosTheta*e$+SinTheta*t$)/65536;
e$=(CosTheta*t$-SinTheta*e$)/65536;
u$=(CosPhi*r$+SinPhi*e$)/65536;
i$=(CosPhi*e$-SinPhi*r$)/65536;
if(i$<0){
Draw=true;
Points[q$][3].x=SemiHorizontalAxis+AxAv*y$/i$;
Points[q$][3].y=SemiVerticalAxis+AxAv*u$/i$;
}if(Draw){
SelectPen(hdc,Pens[Quadrilaterals[q$][4]]);
SelectBrush(hdc,Brushes[Quadrilaterals[q$][5]]);
Polygon(hdc,Points[q$],4);
}q$++;
}EndPaint(hWnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:return DefWindowProc(hWnd,message,wParam,lParam);
}
}
int CALLBACK WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nCmdShow){
GetWindowRect(GetDesktopWindow(),&WindowRect);
HorizontalAxis=WindowRect.right;
SemiHorizontalAxis=HorizontalAxis/2;
VerticalAxis=WindowRect.bottom-100;
SemiVerticalAxis=VerticalAxis/2;
AxAv=(HorizontalAxis+VerticalAxis)/2;
WNDCLASSEX wcex;
wcex.cbSize=sizeof(WNDCLASSEX);
wcex.style=CS_HREDRAW|CS_VREDRAW;
wcex.lpfnWndProc=WndProc;
wcex.cbClsExtra=0;
wcex.cbWndExtra=0;
wcex.hInstance=hInstance;
wcex.hIcon=LoadIcon(hInstance,IDI_APPLICATION);
wcex.hCursor=LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName=NULL;
wcex.lpszClassName=_T("DesktopApp");
wcex.hIconSm=LoadIcon(wcex.hInstance,IDI_APPLICATION);
if(!RegisterClassEx(&wcex)){
MessageBox(NULL,_T("RegisterClassEx failed!"),_T("Something"),NULL);
return 1;
}hInst=hInstance;
hWnd=CreateWindow(_T("DesktopApp"),_T("Something Application"),WS_OVERLAPPEDWINDOW,0,0,HorizontalAxis,VerticalAxis,NULL,NULL,hInstance,NULL);
if(!hWnd){
MessageBox(NULL,_T("CreateWindow failed!"),_T("Something"),NULL);
return 1;
}ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
Sleep(10);
InvalidateRect(hWnd,NULL,true);
}return(int)msg.wParam;
}
This is the core of most, but not all, of the noticeable flicker:
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
Sleep(10);
InvalidateRect(hWnd,NULL,true);
}return(int)msg.wParam;
You are putting a Sleep statement and an InvalidateRect call not just between every WM_PAINT call, but behind EVERY windows message. That's going to cause all kinds of problems. Further, your InvalidateRect is passing true for the erase flag. Here's an improvement:
Create a timer either in WM_CREATE or right before you start pumping messages.
SetTimer(hWnd, 999, 33, NULL); // 999 is just a random timer id, "33" is the milliseconds to wait between WM_TIMER messages
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}return(int)msg.wParam;
And then handle the timer as follows in your WndProc.
case WM_TIMER:
{
InvalidateRect(hWnd, NULL, FALSE);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:return DefWindowProc(hWnd, message, wParam, lParam);
}
Notice that I changed the redraw interval from 10 milliseconds to 33 milliseconds. I have a 4k monitor that can't go past 60 frames/second. A 10 millisecond interval is pushing that to go 100 frames/second. That's another element of flicker. So I reduced it to 30fps. We can try to push that back up after we get through some other fixes.
And since we've bought into InvalidateRect not clearing the whole screen, we can go ahead and add this to the WndProc as well so that all other redraw events won't erase it either.
case WM_ERASEBKGND:
{
return 1;
}
At this point with all the changes I've made so far, there's just a minimal amount of flicker left, especially when I resize your window down from full screen to a quarter of my 4k screen. Also, when I build your code for Release instead of Debug, it's even further reduced. That almost proves that all the math code between BeginPaint/Endpaint is taking too long and not drawing the frame within 1 frame tick.
One other minor optimization that I sometimes use, but can't definitively say if this will help is to not use the HDC provided by the BeginPaint call which might have a clipping region associated with it. It's sometimes faster to just get an unclipped HDC and redraw with that. You could try this mod to WM_PAINT. YMMV:
case WM_PAINT:
// validate that HWND
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
// fetch the unclipped DC so that subsequent drawing operations don't get boundary checked
hdc = GetDC(hWnd);
...
}if (Draw) {
SelectPen(hdc, Pens[Quadrilaterals[q$][4]]);
SelectBrush(hdc, Brushes[Quadrilaterals[q$][5]]);
Polygon(hdc, Points[q$], 4);
}q$++;
}
ReleaseDC(hWnd, hdc);
hdc = NULL;
return 0;
As I said above, most, but not all of the flickering is eliminated. Now to get rid of the rest, follow the instructions in the link provided by Alan Birtles. That is, in your WM_PAINT handler, create an offscreen memory DC and paint to that. Then blit from that offscreen DC to the real window DC. Your modified WM_PAINT should paint the entire space the memory DC as well including the background. When you do that, you can likely increase the framerate.

Can't change background of window

I develop a win32 app and create a color chooser using this example. I update if statement like this to change the background of my app when user click to "Ok" in color dialog box, but nothing change. Where is my mistake?
if (ChooseColor(&cc) == TRUE) {
HBRUSH hbrush = CreateSolidBrush(cc.rgbResult);
rgbCurrent = cc.rgbResult;
SetClassLongPtr(hWnd, GCLP_HBRBACKGROUND, (LONG)hbrush);
}
The following code would work.
First, SetClassLongPtr() returns the previous value, which is, in this case, the HBRUSH previously set to the window class(hWnd). You should delete the object to avoid memory leak.
After that, calling InvalidateRect() brings the color change into effect. Because the newly created brush will be used when the window needs to be repainted.
InvalidateRect() sends WM_ERASEBKGND to the window.
if (ChooseColor(&cc) == TRUE) {
HBRUSH hbrush = CreateSolidBrush(cc.rgbResult);
HBRUSH hOldBrush = (HBRUSH)SetClassLongPtr(hWnd, GCLP_HBRBACKGROUND, (LONG_PTR)hbrush);
DeleteObject(hOldBrush);
InvalidateRect(hWnd, NULL, 1);
}

C++ Win32 GDI double buffering

Could you give the simplest way to achieve double buffering for this example code (to prevent flickering):
HWND hwnd = FindWindow(0, "Untitled - Notepad");
HDC hDC_Desktop = GetDC(hwnd);
...
while( )
{
RECT rect = { 10, 10, 10 + 50, 10 + 50 };
FillRect(hDC_Desktop, &rect, ColorBrush);
InvalidateRect (hwnd, NULL, TRUE);
}
The reason it's "flickering" is because the target window is getting invalidated and it is being redrawn. Since it's not your window - you don't necessarily have control over that.
If this was your own window there is a simple strategy to speed up your drawing speed and reduce flicker: Use a Memory DC to draw on and capture WM_ERASEBKGND to suppress background redraws.
In depth explanation and strategy for fixing it (in your application's window): http://www.catch22.net/tuts/win32/flicker-free-drawing
If your intent is to draw on another application, might I suggest creating a window on top of that application and draw on that.

C++ window draggable by its menu bar

So I'm using Visual C++, and I have created a draggable, borderless window. Anyway, there is a toolbar along the top, and i want to be able to drag the window by that toolbar. I still want the toolbar to be functional, but i have no earthly idea how to be able to drag the window by it. This is my current window (see the toolbar on the top):
And this is my current code to make it draggable:
case WM_NCHITTEST: {
LRESULT hit = DefWindowProc(hWnd, message, wParam, lParam);
if(hit == HTCLIENT) hit = HTCAPTION;
return hit;
}
break;
You are on the right track with hooking WM_NCHITTEST. Now, you need to make change what constitutes a client hit versus a caption hit. If I understand your code right now, anywhere you click within the client area of the window (everything but the border) will allow you to drag the window elsewhere. This will make interacting with your application very difficult. Instead, you should be returning HTCAPTION only after you have determined that the hit was within the menubar area. Specifically, the area of the menubar that does not hold the File/Edit/Help buttons.
case WM_NCHITTEST: {
LRESULT hit = DefWindowProc(hWnd, message, wParam, lParam);
if (hit == HTCLIENT) { // The hit was somewhere in the client area. Don't know where yet.
// Perform your test for whether the hit was in the region you would like to intercept as a move and set hit only when it is.
// You will have to pay particular attention to whether the user is actually clicking on File/Edit/Help and take care not to intercept this case.
// hit = HTCAPTION;
}
return hit;
break;
}
Some things to keep in mind here:
This can be very confusing to a user that wants to minimize, close, or move your application. Menubars do not convey to the user that you can move the window by dragging them.
If you are concerned with vertical pixels you may consider doing what other applications on Windows are starting to do -- moving the menubar functionality to a single button that is drawn in the titlebar. (See recent versions of Firefox/Opera or Windows explorer in Windows 8 for some idea to move things to the titlebar.
In one of my applications I also wanted to make the window what I call "client area draggable". Unfortunately the mentioned solution (replacing HTCLIENT with HTCAPTION)
does have a serious flaws:
Double-clicking in the client area now shows the same behaviour as
double-clicking the caption (i.e. minimizing/maximizing the window)!
To solve this I did the following in my message handler (excerpt):
case WM_MOUSEMOVE:
// Move window if we are dragging it
if (mIsDragging) // variable: bool mIsDragging;
{
POINT mousePos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
mIsDragging = (ClientToScreen(hWnd, &mousePos) &&
SetWindowPos(hWnd,
NULL,
mDragOrigin.left + mousePos.x - mDragPos.x,
mDragOrigin.top + mousePos.y - mDragPos.y,
0,
0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE));
}
break;
case WM_LBUTTONDOWN:
// Check if we are dragging and safe current cursor position in case
if (wParam == MK_LBUTTON)
{
POINT mousePos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
if (ClientToScreen(hWnd, &mousePos) &&
DragDetect(hWnd, mousePos))
{
// Check if the cursor is pointing to your new caption here!!!!
mIsDragging = true;
mDragPos = mousePos;
GetWindowRect(hWnd, &mDragOrigin);
SetCapture(hWnd);
}
}
break;
// Remove this case when ESC key handling not necessary
case WM_KEYDOWN:
// Restore original window position if ESC is pressed and dragging active
if (!mIsDragging || wParam != VK_ESCAPE)
{
break;
}
// ESC key AND dragging... we restore original position of window
// and fall through to WM_LBUTTONUP as if mouse button was released
// (i.o.w. NO break;)
SetWindowPos(hWnd, NULL, mDragOrigin.left, mDragOrigin.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
case WM_LBUTTONUP:
ReleaseCapture();
break;
case WM_CAPTURECHANGED:
mIsDragging = false;
break;
The (pseudo) code omits the return values (default: 0) and variable definitions but
should make the procedure clear anyway!? (If not drop me a line and I'll add more
or all code).
ps: I just found another comprehensive description which also explains the differences
of these two solutions: http://tinyurl.com/bqtyt3q