InvalidateRect, Drawn Objects and 'Bleeding'

Hey all,
Currently working on a program that draws a number of shapes. however, when you draw a rectangle, and move the mouse inside it's borders while still drawing, the old rectangle remains, and as it refreshes the borders of the rectangle, the old drawing remains. I don't know how to fix this.
here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;         // Handle to a device context, for drawing
    PAINTSTRUCT ps;  // Extra information useful when painting
    RECT Window;
    static RECT DrawArea;
    static BOOL Drawing=0;        
    switch (message)                  /* handle the messages */
    {
        //-------------------------------------------
        case WM_CREATE:
             
        return 0;

        //-------------------------------------------        
        case WM_PAINT:
             hdc = BeginPaint( hwnd, &ps );
             if( Drawing = 1 )
             {
                 Rectangle( hdc, DrawArea.left, DrawArea.top, DrawArea.right, DrawArea.bottom );
                 
             }
             EndPaint( hwnd, &ps );           
             return 0;
        
        case WM_LBUTTONDOWN:
             InvalidateRect( hwnd, 0, 0 );
             DrawArea.left = LOWORD( lParam );
             DrawArea.top = HIWORD( lParam );
             DrawArea.right = LOWORD( lParam );
             DrawArea.bottom = HIWORD( lParam );
             Drawing = 1;
        return 0;
        
        case WM_MOUSEMOVE:
             if( wParam & MK_LBUTTON )
             {
             DrawArea.right = LOWORD( lParam );
             DrawArea.bottom = HIWORD( lParam );
             InvalidateRect( hwnd, 0, 0 );
             }
        return 0;
             
       //-------------------------------------------
        case WM_DESTROY:
            
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
        return 0;
        
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}


Thanks in advance for your help.
One immediate error that jumps out is --

if( Drawing = 1 )

it should be --

if( Drawing == 1 )

Notice the difference in the equal signs.

You might also set the last parameter in InvalidateRect() to TRUE.
Last edited on
I don't remember where in the windowing chain of events (construction of the window class included) is where you can set automatic background repainting. I don't see your code repainting the background, so I must ask if you set up automatic background repainting for your window or not. I think it is done by adding a brush to the window class definition. Don't take my word for it, though.

Also, to avoid flickering on complex drawings, you should use double buffering, unless, of course, the new drawing mechanisms do not require this anymore. I learned this stuff prior to GDI+ and I never use it.
I think he's handling the painting in the DrawArea() function, and then InvalidateRect() will cause it to show up. His second parameter indicates that the entire window is to be redrawn.
InvalidateRect() only posts a WM_PAINT message in the thread's queue of messages. That's it. The invalidated rectangle will continue to show whatever it had unless automatic background repainting has been enabled.

I think the purpose of the code is to draw a rectangle as the user drags the mouse on the window. InvalidateRect() is being called to post WM_PAINT messages, which in turn run the code for painting the rectangle.

I wouldn't do this like this. I would call a Refresh() method or a DrawDraggedRectangle() method instead of InvalidateRect(). Painting is valid any time, not just during WM_PAINT messages. You don't have to invalidate a rectangle in order to be allowed to paint. Besides, WM_PAINT is only fetched if it is the only message in the queue. WM_PAINT is only processed if there is nothing better to do.
Last edited on
Well, you are a far more experienced programmer then I, but processing WM_PAINT messages in conjunction with InvalidateRect() is what Petzold uses in his examples in his book.

Without seeing all the OP's code, we don't know, but one thing we do know is the conditional error I pointed out above. That has to be fixed regardless.
Last edited on
Ah, Petzold. He IS the man, isn't he? Amazing.

Yes, Petzold is strict in his coding. However, if you followed the rule 100% of the time, you wouldn't be able to display a moving counter or progress bar in a single-threaded application. For example, I take you back to the days of Visual Basic 5 or 6. A classic loop that counts from 1 to 100 and the counter is displayed in a label. Classic textbook example.

If you don't add label.Refresh() to the loop, the counter is displayed only after the loop has finished.

If you were the label programmer and you followed the WM_PAINT approach, you would probably define Refresh() like this:

1
2
3
4
5
6
7
STDMETHODIMP Label::Refresh()
{
    RECT rect;
    ZeroMemory(&rect, sizeof(RECT));
    GetClientRect(myWnd, &rect);
    InvalidateRect(myWnd, &rect, TRUE);
}


So, what is happening? A VB application is counting from 1 to 100, and wants to display the counter in your label object. What do you do in response (to the call to Refresh())? Invalidate the client area of the label. What does this generate? A WM_PAINT message. Will this message be processed before the counting loop advances to the next number? No because the thread is not in the message pump, it is busy counting from 1 to 100. Will a multithreaded scenario be better? Better yes, but not good enough because WM_PAINT has the lowest priority of all messages.

The moral of the story: WM_PAINT is the way to go if you are not in a hurry for refreshing the window. If you have to have it, just draw.

UPDATE: I realized I had my arguments wrong in InvalidateRect(). I looked the function up in MSDN and noticed that what is needed is the 3rd parameter to be set to TRUE! That erases the background. But it works only if you pass the correct rectangle to the function. NerdyOgre254 is not passing the correct rectangle.
Last edited on
Yes, I noted earlier that he should set his third parameter to TRUE in InvalidateRect().

He also still needs to fix the conditional loop I noted also. Whether those two items will give him what he wants remains to be seen.
1. It's supposed to draw more than one rectangle. If I set the thrid parameter to TRUE in InvalidateRect() it'll clear everything. while this is good for making just one rectangle, it's not suitable for multiple rectangles.

2. I attempted to use an array of POINT structs, but that hasn't worked.

3. The conditional loop has been fixed.

Our lecturer (keep in mind I have only been doing Win32 programming for about 6weeks) suggested a few options:
1. Use InvalidateRect(hwnd, 0,0)
2. Use an array or vector to and every time you call create a rectangle store it in that array/vector
3. Use double buffering to keep saving the images.

I haven't got the foggiest idea how to do #3, but I do have a rough idea how to do the first two.

Also, the above program is the code that I have written out. the rest is the basic Win32 code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "WindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nFunsterStil)

{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WndProc;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default color as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

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

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);

    /* 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;
}
Last edited on
I'm already worn out for today. Almost midnight and I just emailed a new beta version of a software with a very tight deadline and schedule, so I will explain roughly:

-What do you have to do? Draw rectangles that depict the move of the mouse while the left button is depressed.

-What do you need? To detect the mouse down event (message) so you can start "monitoring" the mouse movement, and also to know one of the corners for the rectangles, and then monitor the mouse movement, generating a rectangle each time the mouse moves. Because Windows won't remember your rectangles, you also need to save them in case the window needs repainting. Finally, you need to detect when the mouse button is released so you can stop generating rectangles.

-More technically speaking: On WM_MOUSEDOWN, verify that the button depressed is the left button (unless any button will do). If the correct button has been depressed, save the current mouse coordinates (relative to the client area of your window) somewhere so you can use them repeatedly.
Then on WM_MOUSEMOVE, you need to capture the mouse coordinates because those will become the other corner of a new rectangle. What is the most natural structure available that can represent a rectangle? A RECT structure. I would declare a std::vector() of type RECT where I can add a new RECT structure for every WM_MOUSEMOVE. After saving the new RECT structure, I would call for a Refresh() method to draw the new RECT (and all previous ones). Refresh() should have access to my collection of RECT structs (the std::vector() variable).
Finally, on WM_MOUSEUP I would "forget" the value of the original corner (saved on WM_MOUSEDOWN) and I would signal a variable ("Drawing" in your case) so further WM_MOUSEDOWN events don't generate any more rectangles.
On WM_PAINT messages, which are issued by the OS whenever your window needs repainting, I would call for Refresh(), hence reusing the drawing code and avoiding the need for duplicating code.

As for double buffering, it is the technique of drawing the contents of a window in system RAM instead of video RAM, and once the "painting" is complete, you blit it to video RAM in one operation. It is recommended to avoid automatic background repainting when using double buffering because it is just plain unnecessary.

NOTE: The following is from the top of my head. I don't feel like googling right now.

To explain double buffering I must make sure you know how regular painting works. The process of painting on a window requires a call to BeginPaint(), which returns the window's device context (this would be the "canvaas" where you draw), then painting whatever you need to paint, and then the operation is finished with a call to EndPaint(). This is the "single-buffered" approach, the standard approach.

To double buffer, you don't paint in the "canvas" given by BeginPaint(). Instead you create a canvas in the system RAM. How? With a call to GetDeviceContext() (or is it CreateDeviceContext()?). This function call will provide a memory "canvas" called (surprisingly) a memory device context. Now do your painting over this memory DC (Device Context).

After you finished painting over the memory DC, you BitBlit (that's a function name) the contents of the memDC to the contents of the window DC and call EndPaint(). That's it. Break a leg.
Also, just an additional note. You don't have to repaint the entire window with InvalidateRect(). You could set the second parameter to draw only where you want to draw.
I remembered some function names, I think: CreateCompatibleDC(), CreateDC(), CreateCompatibleBitmap().
Topic archived. No new replies allowed.