News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Difference in WM_PAINT messages ?

Started by gelatine1, June 29, 2014, 04:48:03 AM

Previous topic - Next topic

gelatine1

As some may know I am currently writing my text editor but I got once again a problem I am not sure of how to solve.

Currently if the user is currently writing on line 3 then I simply make sure my program only repaints line 3. Line 1 and 2 still remain on the screen. But once the user resizes the window then there is also a WM_PAINT message getting sent but this time the bErase is true which causes the whole window to getting cleared. After that line 3 is painted again on the screen but 1 and 2 are gone.

Now my question is how should I determine whether the WM_PAINT message was sent by a keypress (using the InvalidateRect function) or by the resize of my window?

Thanks in advance,
Jannes

dedndave

WM_PAINT is very tricky   :(

you will find it challenging to learn, at first
one of the things you'll learn about is double-buffering (drawing to a memory DC, then blitting to the display DC)
this prevents flicker - nice smooth transitions

that having been said......

when you InvalidateRect (or InvalidateRegion/ValidateRect/ValidateRegion)
or when windows needs to update the window (sized/moved or some other window removed from on top),
the "update region" is modified

when windows gets around to sending a WM_PAINT message....
you call BeginPaint and EndPaint, and windows clears the update region for your window

you can see what the update region is by examining the RECT in the PAINTSTRUCT that is filled by BeginPaint
other than that, there is no way to know where the update requirement came from

dedndave

here is what i suggest when you get a WM_PAINT message....

1 ) BeginPaint - this fills in a PAINTSTRUCT for you
2 ) create a "compatible" DC (memory DC) - it should be compatible with the display DC - CreateCompatibleDC
3 ) create a compatible bitmap (also compatible with the display DC) - CreateCompatibleBitmap
make it the same size as the client area of your window
4 ) select the compatible bitmap into the compatible DC - SelectObject - save the old value that's returned in EAX
5 ) do all your drawing into this memory DC
you can update the entire client area (memory DC updates are usually very fast)
6 ) use the PAINTSTRUCT rcPaint RECT to determine what part of the client area to update
you convert the RECT coordinates from <left,top,right,bottom> into <left,top,width,height>
width = right-left and height = bottom-top
7 ) blitter the update area from the memory DC into the display DC - BitBlt
updating the display DC is not as fast as updating a memory DC
but, you are only updating the area required by rcPaint

now - you have to do some cleanup....

8 ) undo step 4 by selecting the original bitmap back into the memory DC (SelectObject again)
9 ) undo step 3 by deleting the bitmap - DeleteObject
10 ) undo step 2 by deleting the memory DC - DeleteDC
11 ) undo step 1 by calling EndPaint

there are many ways to make improvements, but you will find this works pretty well
whenever you change the properties of a DC, you want to restore them before deleting it
so - you SelectObject, save the old value and restore it before deleting the DC
this also applies to fonts, background colors, etc
you can simplify that whole "setup/restore" process with SaveDC and RestoreDC (works sort of like PUSH/POP)

dedndave

i don't like to create LOCAL's inside WndProc, so i usually write a paint proc and create them there
this is not tested, but should work   :P

at beginning of program....

WndProc PROTO :HWND,:UINT,:WPARAM,:LPARAM
wmPaint PROTO :HWND


then in the .CODE section, WndProc....

WndProc PROC hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

    mov     eax,uMsg
    .if eax==WM_PAINT
        INVOKE  wmPaint,hWnd
        xor     eax,eax

    .elseif eax==

;......

    .else
        INVOKE  DefWindowProc,hWnd,uMsg,wParam,lParam

    .endif
    ret

WndProc ENDP


and wmPaint...

wmPaint PROC USES EBX ESI EDI hWnd:HWND

;--------------------------------

    LOCAL   ps              :PAINTSTRUCT
    LOCAL   rcClient        :RECT
    LOCAL   nSavedDC        :UINT
    LOCAL   hbmpMem         :HBITMAP

;--------------------------------

    INVOKE  BeginPaint,hWnd,addr ps
    INVOKE  CreateCompatibleDC,eax
    xchg    eax,esi                                                          ;ESI = hdcMem
    INVOKE  GetClientRect,hWnd,addr rcClient
    INVOKE  SaveDC,esi
    mov     nSavedDC,eax
    INVOKE  CreateCompatibleBitmap,ps.hdc,rcClient.right,rcClient.bottom
    mov     hbmpMem,eax
    INVOKE  SelectObject,esi,eax

;do all your selecting and drawing into hdcMem (ESI)

;.....

;now, update the display

    mov     edi,ps.rcPaint.left
    mov     ebx,ps.rcPaint.top
    mov     ecx,ps.rcPaint.right
    mov     edx,ps.rcPaint.bottom
    sub     ecx,edi                                                          ;ECX = width
    sub     edx,ebx                                                          ;EDX = height
    INVOKE  BitBlt,ps.hdc,edi,ebx,ecx,edx,esi,edi,ebx,SRCCOPY

;and clean-up

    INVOKE  RestoreDC,esi,nSavedDC
    INVOKE  DeleteObject,hbmpMem
    INVOKE  DeleteDC,esi
    INVOKE  EndPaint,hWnd,addr ps
    ret

wmPaint ENDP


you actually don't have to save nSavedDC in this case
you can just call

    INVOKE  RestoreDC,esi,-1

and it will use the most recently saved DC


BeginPaint
http://msdn.microsoft.com/en-us/library/dd183362%28v=vs.85%29.aspx

PAINTSTRUCT
http://msdn.microsoft.com/en-us/library/dd162768%28v=vs.85%29.aspx

EndPaint
http://msdn.microsoft.com/en-us/library/dd162598%28v=vs.85%29.aspx

GetClientRect
http://msdn.microsoft.com/en-us/library/windows/desktop/ms633503%28v=vs.85%29.aspx

SaveDC
http://msdn.microsoft.com/en-us/library/dd162945%28v=vs.85%29.aspx

RestoreDC
http://msdn.microsoft.com/en-us/library/dd162929%28v=vs.85%29.aspx

CreateCompatibleDC
http://msdn.microsoft.com/en-us/library/dd183489%28v=vs.85%29.aspx

DeleteDC
http://msdn.microsoft.com/en-us/library/dd183533%28v=vs.85%29.aspx

CreateCompatibleBitmap
http://msdn.microsoft.com/en-us/library/dd183488%28v=vs.85%29.aspx

SelectObject
http://msdn.microsoft.com/en-us/library/dd162957%28v=vs.85%29.aspx

DeleteObject
http://msdn.microsoft.com/en-us/library/dd183539%28v=vs.85%29.aspx

BitBlt
http://msdn.microsoft.com/en-us/library/dd183370%28v=vs.85%29.aspx

gelatine1

Wow, thank you very much Dave! I like how you're giving me so much explanation, examples and even references!
Once again so much new information but I'll try to process it :)

Just one more question, What in fact is a DC ? It's an device context right ? I am not sure what it actually is...  :redface:

dedndave

right - a device context

and - you can read documentation until you turn blue and still not know the answer to that   :lol:

i have seen some analogies that compare it to a "canvas" for painting
i think it's more like the easel   :P

there are 2 primary types of DC's - memory DC's and device DC's
the display DC is very different from the memory DC
another one is a printer DC - again, very different from a memory DC
the display and printer are real physical devices that have specific properties
the memory DC has the properties that you assign to it

but - the DC is essentially a structure that is hidden somewhere in memory, used by the OS
it holds all sorts of information about the device attributes, what has been selected into it, and so on

at this point, try not to be too concerned with knowing what it is
after you see how DC's are used in different ways, you will grow to have a better understanding

Tedd

You don't strictly need to know where the update came from, you only need to know WHAT should be redrawn.
Each time you receive WM_PAINT, it is because there is an invalidated region that needs to updated. Calling GetUpdateRect will give you the smallest rectangle that covers the invalidated region; and that's all you need to redraw.
So, when a text line is modified, instead of invalidating the entire window, you can call InvalidateRect with just the rectangle that covers the line itself. Then when WM_PAINT arrives, you only need to draw the part that's changed, and the other lines are left alone (unless you're wrapping lines, and then the following lines may get shifted along/down.)

As for what is a DC, consider it as a box that holds the resources (pens, brushes, fonts, color palette, canvas, settings, etc) to be used with its associated device. When windows draws to a display device (screen, printer, holographic projector) it uses the tools in the associated box (DC) for drawing operations on that device; other devices have their own box which may contain a different set of tools.
Potato2

gelatine1

Right now I have implemented the codes as you explained. My code compiled, it doesn't crash, but it still doesn't actually work as I exptected it to work  :(

Basically what I do now is once the WM_PAINT message is received then I create this memoryDC and I write all lines into this memoryDC then the next I do is I blitter this memoryDC to the DisplayDC (only the Update Region) like you told me to do.

Although once I resize the screen it doesn't work anyway. All previous lines get removed and only the currently is getting displayed.

Another thing I noticed is that I am still getting some flickering effect when I keep pressing the backspace key the background of the currentline is flashing white and black. (windows background is in fact white but the texts background is black)

So I was wondering whether I used this memoryDC correctly or if I am just missing something?

My full code in an attachment. (it was a .rar file and I just changed the extension to .zip since the forum doesn't allow .rar ? If you can't extract it just change the extension again)

My WM_PAINT code:

cmp eax,WM_PAINT
jnz __cont0

invoke BeginPaint,hWnd, ADDR ps
mov    hdc,eax

invoke CreateCompatibleDC,hdc
mov memdc,eax
invoke GetClientRect,hWnd, offset rect
invoke CreateCompatibleBitmap,hdc,rect.right,rect.bottom
mov hbm,eax
invoke SelectObject,memdc,eax
mov hOld,eax

;paint into memoryDC
invoke SelectObject,memdc,hFontMsg
mov hFontHdc,eax

invoke SetBkColor,memdc,0
invoke SetTextColor,memdc,00FFFFFFh

mov edx,[FirstLine]
__NextLine:
mov eax,edx
mov ecx,[BytesPerLine]
mov ebx,eax
shl ebx,7
push edx
mul [LineHeight]
pop edx
;mov edx,[CurrentLine]
add ebx,[pmem]
push edx
invoke ExtTextOut, memdc,3,eax,ETO_OPAQUE or ETO_NUMERICSLATIN,addr rect,ebx,[ecx+4*edx],NULL
pop edx
inc edx
mov eax,[LastLine]
cmp eax,edx
jns __NextLine


invoke SelectObject,memdc,hFontHdc

mov     edi,ps.rcPaint.left
mov     ebx,ps.rcPaint.top
mov     ecx,ps.rcPaint.right
mov     edx,ps.rcPaint.bottom
sub     ecx,edi                                                          ;ECX = width
sub     edx,ebx
invoke BitBlt,hdc,edi,ebx,ecx,edx,memdc,edi,ebx,SRCCOPY

;clean up
invoke SelectObject,memdc,hOld
invoke DeleteObject,hbm
invoke DeleteDC,memdc

invoke EndPaint,hWnd, ADDR ps

jmp __cont


Oh and one more questionn, You used BitBlt like this:
invoke BitBlt,hdc,edi,ebx,ecx,edx,memdc,edi,ebx,SRCCOPY

But I was not really sure about those the 2 parameters before the last. It says the upper left-corner of the source rectangle. isn't this always just (0,0) ?

Thanks in advance,
Jannes

EDIT: for some reason I am quite convinced that the error is somewhere in this BitBlt function. If I debug this code in OllyDbg then I see that the ExtTextOut function is executed exactly the equal amount of times as the number of lines I currently have.

dedndave

my fault for not testing the code, first

what i normally do is to use WNDCLASSEX.hbrBackground = NULL
this prevents the background from being re-drawn each time WM_PAINT is sent

in WM_CREATE, i create a brush of the desired background color and store the handle in a global dword
        INVOKE  CreateSolidBrush,0FFFFFFh ;white
        mov     hbrBackGnd,eax


http://msdn.microsoft.com/en-us/library/dd183518%28v=vs.85%29.aspx

you should delete that object in WM_DESTROY
        INVOKE  DeleteObject,hbrBackGnd

then, before i draw things into the memory DC, i fill it with the background brush

    INVOKE  SelectObject,esi,hbrBackGnd
    xor     ecx,ecx
    INVOKE  PatBlt,esi,ecx,ecx,rcClient.right,rcClient.bottom,PATCOPY


pattern blit is very fast into a memory DC
http://msdn.microsoft.com/en-us/library/dd162778%28v=vs.85%29.aspx

next - i update the entire window in the memory DC
i.e., you have to draw all visible text

this process, combined with double-buffering, totally eliminates flicker

when it comes time to BitBlt, you use the update coordinates from rcPaint for both the source and destination
let's say that windows wants to update the RECT <10,15,20,25>  (10x10 pixels, upper left = 10,15)
you want to use the 10x10 section from the memory DC, starting at 10,15
and draw it to the window DC, starting at 10,15

when you do it this way, you can size/move the window,
or move other windows in front of it and move them around
you will see the update is very fast and no flickering

dedndave

as i mentioned before, there are several ways to improve this
for example, you can just create and re-draw the desired rectangle in the memory DC
the code gets a little hairy, but it can be done

another way to improve it is to use a 256-color DIB section (whenever 256 colors is enough)
rather than calling CreateCompatibleBitmap, you call CreateDIBSection

drawing into a DC with a 256-color bitmap is super fast   :biggrin:

however - that is a lot of effort
and - you will find that it works pretty well without all that extra coding

gelatine1

I am impressed by such quick replies! :) Thank you a lot!

Although I applied the changes you mentioned I am still getting some white flickering when I keep the backspace button down and after a resize I still lose the previous lines :( and I am quite sure those lines are actually written to the memoryDC.

I have my new code in an attachment in case anyone would want to take a look at it. I am going to sleep now, goodnight!


dedndave

i am looking over the code you posted
i will make some edits and get back to you

a couple things i notice right away......

first, in WM_CREATE, you call BeginPaint and EndPaint to get an hDC
if i'm not mistaken, BeginPaint/EndPaint should not be called outside WM_PAINT

many functions require a DC, sometimes even when you're not drawing anything
to get an hDC, you can use GetDC or GetWindowDC (depends on what you're doing)
when you're done using it, use ReleaseDC

http://msdn.microsoft.com/en-us/library/dd144871%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/dd144947%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/dd162920%28v=vs.85%29.aspx


another issue is font height/line height



you use GetTextMetric, so use the values on the left
to get line spacing, add tmHeight + tmExternalLeading

i guess it's better to use GetOutlineTextMetrics for true-type fonts (values on the right)
so, you'd use otmMacAscent + otmMacDescent + otmMacLineGap

http://msdn.microsoft.com/en-us/library/dd145122%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/dd144906%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/dd162755%28v=vs.85%29.aspx

i am looking into the flicker issue.....

RuiLoureiro

Dave,         
         «i am looking into the flicker issue.....»
          you may solve it cretating a memo DC ...

gelatine1

Thank you a lot Dave :)

I am getting some weird (or just unexpected) output for this GetOutlineTextMetrics.

I use this little piece of code now in the WM_CREATE message:

invoke GetDC,hWnd
mov    hdc,eax

invoke SelectObject,hdc,hFontMsg
mov hFontHdc,eax

invoke GetOutlineTextMetrics,hdc,SIZEOF OUTLINETEXTMETRIC,offset outtxt
mov eax,outtxt.otmMacAscent
add eax,outtxt.otmMacDescent
add eax,outtxt.otmMacLineGap
mov [LineHeight],eax

invoke SelectObject,hdc,hFontHdc
invoke ReleaseDC,hWnd,hdc


My text got pasted onto each other so there was something wrong.. I checked the debugger and I figured out that
outtxt.otmMacAscent = -3
outtxt.otmMacDescent = 13
outtxt.otmMacLineGap = -3

these values don't really make much sense huh do they ? The lineHeight would become 7 like this and I remember before that 16 was perfect. What is going wrong ?

Also this morning when I opened up the program I had send again it stopped giving any black background although I am still using this PatBlt function with the black brush. Last night I am pretty sure it was black but today somehow it was white. I don't really know what was going on :/ Do you get a black or white background in the files I sent ?

Oh andRuiLoureiro, that's exactly what I was using, still had the flicker though.

dedndave

not sure how you got anything from that function - lol
usually, if the structure requires the size to be set, and you don't set it, it fails

        .DATA?

otm OUTLINETEXTMETRIC <>

        .CODE

        mov     edx,offset otm
        mov     ecx,sizeof OUTLINETEXTMETRIC
        mov     [edx].OUTLINETEXTMETRIC.otmSize,ecx
        INVOKE  GetOutlineTextMetrics,hdc,ecx,edx


if the nmbers are negative, it is a little strange
make them positive and add them all up for 19
i'll see when i get that far

i noticed the white screen, as well
in your zip file, the EXE has a black background
but, when i re-assemble, it is white
no matter - i am working through WndProc, now
i'm sure i'll find it   :t