Click here to Skip to main content
15,881,709 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi all,

My query relates to the WM_PAINT function in WIN32 programs written in C++.

First, for those interested, a little history:

Me:
Electrical engineer who studied basic C programming at Uni, taught myself basic C++ from there. Also dabbled in JAVA and some machine code (anyone remember Xilinx??). Most programs I wrote were during Uni, were console apps in C++, mostly simple calculation / mathematics type programs (which was around 10 years ago). Also occasionally worked on a friends website written in ColdFusion.

Current project:
Having not programmed for 5+ years, I decided to try write a program in WIN32; while I won't give away too many details, the premise is basically a program which shows and deletes certain shapes (upto 30 at one time) on the same window, based on an ongoing algorithm.

So, what I have got so far is a very simple piece of code which calls a function called TEST, which in turn calls RECT1 (this rectangle DOES show). TEST then also calls RECT2, which is where I am lost, as this rectangle DOES NOT show...

I have read through intro / tutorials by the functionx site, as well as Code Projects Paul M Watt, as well as others.

Code is below - I have put in Sleep() functions to test whether the code enters the function, where you see 'busy' cursor (primative, I know - I am open to better ways to do this). Code does in fact enter the RECT2 function, but does not carry out drawing the rectangle.


So the question basically is, can two or more separate functions call the WM_PAINT message? If it can be called my more than 1 function, how is the HWND and HDC it uses affected? As you can see in the code, I have passed the 'hWnd' variable onto each function. I have tried to do the same for 'hdc' but I get errors (not shown)...basically I have to create a HDC variable of 'hdc' in each function - or so I think??


Appreciate any help you are willing to offer.

Thanks

#include <windows.h>

/*  Make the class name into a global variable  */

LPCTSTR ClassName = "BasicApp";
LPCTSTR WindowName = "A Simple Window";


/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
VOID TEST (HWND);
VOID RECT1 (HWND);
VOID RECT2 (HWND);

INT WINAPI WinMain (
                   HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow
                   )                
                
                    
{
    HWND hWindow;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX WndClsEx;

	WndClsEx.cbSize        = sizeof(WNDCLASSEX);
	WndClsEx.style         = CS_HREDRAW | CS_VREDRAW;
	WndClsEx.lpfnWndProc   = WindowProcedure;
	WndClsEx.cbClsExtra    = 0;
	WndClsEx.cbWndExtra    = 0;
	WndClsEx.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	WndClsEx.hCursor       = LoadCursor(NULL, IDC_ARROW);
	WndClsEx.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	WndClsEx.lpszMenuName  = NULL;
	WndClsEx.lpszClassName = ClassName;
	WndClsEx.hInstance     = hInstance;
	WndClsEx.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);


    /* Register the window class, and if it fails quit the program */
	RegisterClassEx(&WndClsEx);


    /* The class is registered, let's create the program*/
    hWindow = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           ClassName,         /* Classname */
           WindowName,       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           CW_USEDEFAULT,                 /* The programs width */
           CW_USEDEFAULT,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

// Find out if the window was created
	if( !hWindow ) // If the window was not created,
		return 0; // stop the application


    /* Make the window visible on the screen */
    ShowWindow (hWindow, SW_SHOWNORMAL);
    UpdateWindow(hWindow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
} //int WINAPI WinMain



LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{

    switch(Msg)
    {
    case WM_PAINT: 

         TEST(hWnd);
         break;
         
    case WM_DESTROY:
        PostQuitMessage(WM_QUIT);
        break;

   default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);

    }

}



VOID TEST (HWND hWnd)
{
     RECT1 (hWnd);
//     Sleep(2000);
     RECT2 (hWnd);
     return;    
}


VOID RECT1 (HWND hWnd)
{
    HDC	hdc;
    PAINTSTRUCT ps;
	
    hdc = BeginPaint(hWnd, &ps);
	Rectangle (hdc, 10, 10, 50, 50);
	EndPaint(hWnd, &ps);
	
	return;	
}



VOID RECT2 (HWND hWnd)
{
//    Sleep(2000); 
    HDC	hdc;
    PAINTSTRUCT ps;
	
    hdc = BeginPaint(hWnd, &ps);
	Rectangle (hdc, 110, 110, 150, 150);
	EndPaint(hWnd, &ps);
    Sleep(2000);	
	return;	
}
Posted
Comments
Sergey Alexandrovich Kryukov 17-Mar-15 16:44pm    
There is no such thing as "call a message". This message is very special. You should not normally send it. It will be correctly send if you use invalidation.
—SA

I can see a number of issues, I'll address them one by one.

Firstly, the Rectangle function doesn't just draw a border, but it also _fills_ the rect, using the current brush, which in this case is the background brush.

Next, you only call BeginPaint/EndPaint once per WM_PAINT message.

You need to pay attention to the expected return values if you process messages, failing to do so can mean that the message is sent over and over while the system waits to be informed that it has been handled. For WM_PAINT, you should return 0.

I've fixed these oversights and added a new function, which takes both the HWND and the HDC. This is designed to draw a border 10 pixels in from the edge of the client area. You can see that if you put it after the other two in the onPaint function, it appears as though they haven't worked - this is simply because it draws over the top of them.

Here you go:
#include <windows.h>

/*  Make the class name into a global variable  */
LPCTSTR ClassName = "BasicApp";
LPCTSTR WindowName = "A Simple Window";

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

void onPaint(HWND hwnd);
void myRect1(HDC hdc);
void myRect2(HDC hdc);
void myRect3(HWND hwnd, HDC hdc);


INT WINAPI WinMain (
                   HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow
                   )


{
    HWND hWindow;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX WndClsEx;

	WndClsEx.cbSize        = sizeof(WNDCLASSEX);
	WndClsEx.style         = CS_HREDRAW | CS_VREDRAW;
	WndClsEx.lpfnWndProc   = WindowProcedure;
	WndClsEx.cbClsExtra    = 0;
	WndClsEx.cbWndExtra    = 0;
	WndClsEx.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	WndClsEx.hCursor       = LoadCursor(NULL, IDC_ARROW);
	WndClsEx.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	WndClsEx.lpszMenuName  = NULL;
	WndClsEx.lpszClassName = ClassName;
	WndClsEx.hInstance     = hInstance;
	WndClsEx.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);


    /* Register the window class, and if it fails quit the program */
	RegisterClassEx(&WndClsEx);


    /* The class is registered, let's create the program*/
    hWindow = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           ClassName,         /* Classname */
           WindowName,       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           CW_USEDEFAULT,                 /* The programs width */
           CW_USEDEFAULT,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

// Find out if the window was created
	if( !hWindow ) // If the window was not created,
		return 0; // stop the application


    /* Make the window visible on the screen */
    ShowWindow (hWindow, SW_SHOWNORMAL);
    UpdateWindow(hWindow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
} //int WINAPI WinMain



LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{

    switch(Msg)
    {
        case WM_PAINT:
            onPaint(hWnd);
            return 0;
             //TEST(hWnd);
             //break;

        case WM_DESTROY:
            PostQuitMessage(WM_QUIT);
            return 0;
        //break;
       default:
            return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
}

void onPaint(HWND hwnd)
{
    HDC hdc;
    PAINTSTRUCT ps;

    hdc = BeginPaint(hwnd, &ps);

        myRect3(hwnd, hdc);
        myRect1(hdc);
        myRect2(hdc);

    EndPaint(hwnd, &ps);
}

void myRect1(HDC hdc)
{
    Rectangle(hdc, 10, 10, 50, 50);
}

void myRect2(HDC hdc)
{
	Rectangle (hdc, 110, 110, 150, 150);
}

void myRect3(HWND hwnd, HDC hdc)
{
    RECT clientRect;
    GetClientRect(hwnd, &clientRect);
    InflateRect(&clientRect, -10, -10);
    Rectangle(hdc, clientRect.left, clientRect.top, clientRect.right, clientRect.bottom);
}
 
Share this answer
 
Comments
danielmarcos85 17-Mar-15 16:17pm    
Hi enhzflep,

Thanks for your comments - your solution has explained a lot!

This will likely be the first among many questions on this project, so thank you for getting it off to a positive start!

Cheers!
enhzflep 18-Mar-15 3:56am    
Howdy,
you're welcome, the WinApi can be a bit confusing at first but it all tends to make sense with time.
Just fire away when you need to, people are generally only too willing to help answer a well-written and considered question.

:)
Please see my comment to the question. Not only there is no such thing as "call a message", you should not send it directly. This is not how things work.

The triggering of this message is quite a complex thing. You can see that you method will be fired when any part of your window covered by any other window exposes part of the previously covered surface. So it happens on motion of some windows, on Alt+TAB, on refresh &many; cases.

Now, how to trigger it automatically? This is very important for any kind of animation. Now, what's so special about WM_PAINT? The processing of this event is very cunning. It does not go through a regular Windows message queue. Instead, it is highly optimized. Imagine you have two invalidates coming one after another, with second one called before your re-rendering (through the WM_PAINT event handler) is not yet complete. Do you think your event handler is called twice? No way! The messages will be merged together in one in the optimization process, and there invalidated regions will be ORed together. Interesting mechanism, isn't it?

Basically, your handler should render some graphics bases on some data describing graphics. It could be some vector graphics model, pure data. When data is changed, you have to do additional invalidation, to notify the system and cause rendering. You don't have to re-render the whole scene, you can improve performance by invalidating only some rectangle or region. This is how:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145005%28v=vs.85%29.aspx[^],
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145167%28v=vs.85%29.aspx[^],
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145002%28v=vs.85%29.aspx[^],
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145195%28v=vs.85%29.aspx[^].

—SA
 
Share this answer
 
Comments
danielmarcos85 18-Mar-15 8:09am    
Thanks Sergey,

A lot of information to take in! I had to read it a few times to fully comprehend it.

I had come across the InvalidateRect function, just wasn't sure how to use it. I will keep this in mind.


Cheers
Sergey Alexandrovich Kryukov 18-Mar-15 8:27am    
You are welcome.
Will you accept this answer formally, too?
—SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900