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
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
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)
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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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
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:
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
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.
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.
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 (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 (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
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
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!
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/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/dd144947%28v=vs.85%29.aspx)
http://msdn.microsoft.com/en-us/library/dd162920%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
(http://i.msdn.microsoft.com/dynimg/IC444269.png)
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/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/dd144906%28v=vs.85%29.aspx)
http://msdn.microsoft.com/en-us/library/dd162755%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.....
Dave,
«i am looking into the flicker issue.....»
you may solve it cretating a memo DC ...
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.
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
in your WM_PAINT code, you use EBX and EDI without preserving them :P
something screwy with the text loop
to test it, i jumped around it - and the screen is black again :P
jmp around
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
around:
I changed the GetOutlineTextMetrics function to this
mov outtxt.otmSize,SIZEOF OUTLINETEXTMETRIC
invoke GetOutlineTextMetrics,hdc,SIZEOF OUTLINETEXTMETRIC,offset outtxt
nothing changed
I preserved ebx and edi like this:
cmp eax,WM_PAINT
jnz __cont0
push ebx
push edi
...
pop edi
pop ebx
Nothing changed :( I'm kinda lost in all this
And okay :t I'll find what's going wrong in the text loop
This ETO_OPAQUE flag in the ExtTextOut function messed everything up.
I have a black background again :biggrin: But... this happens:
(http://s26.postimg.org/v63341xv9/blackwhite.jpg?noCache=1404141625)
i set the text to red so it doesn't get hidden in the backgrounds :p
What is this ? another flag that should be altered ? or um oh right I need to use SetBkgndColor to black I guess ?
EDIT: SetBkColor did the thing :) although there is still something wrong with the LineHeight now. Can't seem to get it right
even though you filled in the bitmap with all black, you still have to set the background color for ExtTextOut
invoke SetBkColor,memdc,0
http://msdn.microsoft.com/en-us/library/dd162713%28v=vs.85%29.aspx (http://msdn.microsoft.com/en-us/library/dd162713%28v=vs.85%29.aspx)
QuoteThe ExtTextOut function draws text using the currently selected font, background color,
and text color. You can optionally provide dimensions to be used for clipping, opaquing, or both.
the line height thing is probably a clipping issue
to test that theory, try it without clipping
but, i think your clipping rectangle isn't moving with your text
May I ask , what is clipping? Do you know any article about it ?
Thanks in advance, I hope I'm not bothering you too much
well - clipping is like "not drawing outside this rectangle"
for example - your text is wider than the area you want to display it - you clip it
in this case, you are not using clipping - you don't have that flag set in ExtTextOut
so - you don't really need a rectangle pointer, either (also used for opaquing)
but - that's not where the problem lies
the problem lies here, with the calculation of the top of the rect for InvalidateRect :biggrin:
mov ecx,[CurrentLine]
shl ecx,7
mov edx,[pmem]
add edx,ecx
mov ecx,[CurrentLine]
mov ebx,[BytesPerLine]
mov ebx,[ebx+4*ecx]
mov [edx+ebx],eax
inc ebx
mov edx,[BytesPerLine]
mov [edx+4*ecx],ebx
mov rect.left,0
shl ecx,4
mov rect.top,ecx
mov rect.right,4FFh
add ecx,16
mov rect.bottom,ecx
invoke InvalidateRect,hWnd,offset rect,0
you can test it very easily....
type 4 lines of text - notice that each line is shorter :P
now, minimize the window - then restore it
this causes the OS to send a WM_PAINT message with the entire client area as the update region
you have to use the LineHeight variable in that calculation :t
try this one.....
Thanks a lot once again dave. You have been really helpful so thank you for all of that :) I finally got everything working well again.
A few suggestions..
mov dword ptr [esi+4],CS_HREDRAW or CS_VREDRAW
This can actually be zeroed -- these flags cause the WHOLE window to be redrawn every time the size changes, which will result in flicker.
Using the BLACK_BRUSH for the window background is perfectly fine, flicker is caused by the background being cleared unnecessarily. Specifically, when you call InvalidateRect, the last parameter is true/false to say whether you want the background cleared in the invalidated rectangle; setting it to false means it won't be cleared, which is fine as long as you're drawing entirely over the area so no previous pixels remain - which you are, with BitBlt.
invoke GlobalAlloc,GHND,65535
mov hmem,eax
invoke GlobalLock,hmem
mov pmem,eax
can simply be:
invoke GlobalAlloc,GPTR,65535
mov pmem,eax
and to free:
invoke GlobalFree,pmem
Equally for BytesPerLine.
There's no need to lock/unlock, it's a left-over from windows 3.