Got a problem with a program that uses a child window for painting and drawing. Unfortunately, code is a little too complicated to include here. I'll try to describe it clearly.
Basically I have a main window with a child window occupying its entire client area (except for a toolbar; the child window is sized to start below the toolbar).
I created the child window after registering a class for it:
$childWinAttrs EQU WS_CHILD OR WS_CLIPSIBLINGS OR WS_VISIBLE
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, ADDR ChildClassName, NULL,
$childWinAttrs, 0, gpRect2.bottom, gpRect2.right, EDX,
MainWinHandle, NULL, hInst, NULL
MOV ChildWinHandle, EAX
After everything's set up, I do some drawing of text and simple graphics into the window. (It's a test bed for a larger project.) The problem is that nothing appears in the window until after I resize it--it's just blank. Here's the resizing code (in the parent's window procedure):
do_size:
INVOKE GetClientRect, ToolbarHandle, ADDR gpRect
MOV EAX, gpRect.bottom
MOV toolbarHeight, EAX
MOVZX EDX, WORD PTR lParam ;New window width.
; Resize toolbar:
INVOKE MoveWindow, ToolbarHandle, 0, 0, EDX, toolbarHeight, TRUE
; Resize child window:
INVOKE GetClientRect, MainWinHandle, ADDR gpRect
MOV EAX, gpRect.bottom
SUB EAX, toolbarHeight
INVOKE MoveWindow, ChildWinHandle, 0, toolbarHeight, gpRect.right, EAX, TRUE
; Bring to top of Z-order:
INVOKE SetWindowPos, ChildWinHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE OR SWP_NOSIZE \
OR SWP_SHOWWINDOW
XOR EAX, EAX
RET
Here's what I figure is happening: for some reason, until the windows are resized, the child window is behind the parent window, or otherwise rendered invisible. I tried doing this to bring the child to the top of the Z-order:
INVOKE SetWindowPos, ChildWinHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE OR SWP_NOSIZE \
OR SWP_SHOWWINDOW
but no joy. I can't figure out what's going on here. Are there some special tricks to get a child window like this to behave as expected? I also though of making the parent window transparent somehow, but I can't find anything on MSDN about that.
I hadn't planned on using a child window: originally I was just drawing into the main window's client area, which worked fine until I added a toolbar, at which point the toolbar stopped working correctly. I'm not sure, but somehow drawing into the client area DC seemed to upset the toolbar operation. The toolbar also uses tooltips, if that makes any difference. So that's why I ended up with a child window. Now if I could only get it to appear right away, I'd be happy.
Interesting discovery: the first thing I do in the program is call InitCommonControlsEx(). I tried commenting it out. The result was no display of anything, no toolbar, and nothing in the child window either.
I can't help thinking that this has something to do with the fact that I have a toolbar with tooltips.
You don't show how your code write in the window.
If you use the TextOut function,this one could only be used in a WM_PAINT message.
You don't show also the styles and extended styles of your windows.
This two things are fundamentals to know if the window work well or not.
Yes, you're right of course.
Here are the main window styles:
(class styles)
MOV wc.style, CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
$mainWinAttrs EQU WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN OR WS_VISIBLE
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, ADDR MainClassName, ADDR MainTitleText,
$mainWinAttrs, wX, wY, $mainWindowWidth, $mainWindowHeight,
NULL, NULL, hInst, NULL
Here's what I'm using to draw/paint in the child window:
INVOKE MoveToEx, hDC, currX, currY, NULL
MOV EDX, currX
ADD EDX, StaffWidth
INVOKE LineTo, hDC, EDX, currY
and for text:
; Set up font, text-align flags & background mode:
INVOKE SelectObject, hDC, [ESI + $Symbol.fHandle]
MOV oldFont, EAX
MOV EAX, [ESI + $Symbol.TAflags]
OR EAX, TA_NOUPDATECP
INVOKE SetTextAlign, hDC, EAX
MOV oldTA, EAX
INVOKE SetBkMode, hDC, TRANSPARENT
MOV oldBkMode, EAX
INVOKE TextOut, hDC, ECX, EDX, ADDR charBuffer, 1
; Reset stuff the way we found it:
INVOKE SelectObject, hDC, oldFont
INVOKE SetTextAlign, hDC, oldTA
INVOKE SetBkMode, hDC, oldBkMode
That's pretty much it.
You need a DC for painting. one way to get it is to use InvalidRect for your child window, and to wait for a WM_PAINT message.
Quote from: jj2007 on November 13, 2011, 07:26:13 AM
You need a DC for painting. one way to get it is to use InvalidRect for your child window, and to wait for a WM_PAINT message.
But I do have a DC for painting:
INVOKE GetDC, ChildWinHandle
MOV hDC, EAX
Don't I always have a DC I can use for painting? Or do I have to wait for certain conditions before starting painting?
My theory is still that I'm actually able to paint and draw, I just can't see it because the edit window is behind the parent window. Could be wrong, though.
I tried replacing GetDC() ... ReleaseDC() with BeginPaint() ... EndPaint(), but still no luck.
So let me see if I've got this straight: I can only use Begin ... EndPaint in response to a WM_PAINT message, right?
So what if I want to paint/draw into a window but not in the window proc? When/under what circumstances can I do this?
Besides the WM_PAINT issue, what's the difference between getting a DC with GetDC() and using Begin ... EndPaint()?
Quote
.data
lgfnt LOGFONT <14,0,0,0,0,0,0,0,0,0,0,0,0,"Lucida Console">
hFont dd 0
.code
.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,ADDR paintstruct
mov hdc,eax
invoke CreateFontIndirect,ADDR lgfnt
mov hFont,eax
invoke SelectObject,hdc,hFont
invoke lstrlen,ADDR chaine
invoke TextOut,hdc,20,20,ADDR chaine,eax
;invoke DeleteObject,hFont
The only difference between that example you posted and what I'm doing is that I'm preparing my fonts beforehand and storing their handles in some structures to make them accessible. Other than that, I don't see any difference. So what do you think my problem is here?
I'm almost positive that the problem is the child window being obscured by the parent. I just changed my window-creation and resizing code so the child window is smaller than the parent's client area, and added a border (WS_BORDER) so I'd be sure to see it. I can't see the child when it's created, but I can see it after resizing. ?????
I don't think this has anything to do with painting and drawing.
the parent window will not obscure the child - that is not the nature of the child/parent relationship
during WM_CREATE, a WM_SIZE message is sent to the parent window
what this means is, be prepared to handle WM_SIZE early
in the WM_SIZE handler, if you reference a handle that is stored in a global variable and has not yet been initialized, strange things may happen
this goes back to that other discussion about what things should be done in WM_CREATE
i generally try to create child windows during WM_CREATE so their handle values are initialized early
this may not be the issue with your code
without a more complete example, it is hard to say
however, you may not want to post your code at this time
that's ok - look at the many examples that create child windows and see how they differ from your code :U
Another attempt to help you without seeing your code. For me this works in a very similar case.
SWITCH uMsg
CASE WM_CREATE
....
invoke SetTimer, hWnd, 123, 500, 0
CASE WM_TIMER
invoke KillTimer, hWnd, 123
if 1 ; test 0 or 1, see if things change
invoke InvalidateRect, hChild, 0, 0
endif
Do you handle WM_ERASEBKGND message?
QuoteIf hbrBackground is NULL, the application should process the WM_ERASEBKGND message and erase the background.
But my background brush isn't NULL (I assume you're talking about what gets passed to RegisterClassEx() ). I don't think that's the issue.
Dave, what you said is worth considering. It might have everything to do with the order in which I'm doing things.
I moved all my creation stuff (toolbar window, tooltips & child window creation) to the WM_CREATE handler. Now I get nothing--no toolbar, no nothing.
So here's the deal: I'm in kind of a bind since certain things depend on certain other things. For instance, I'm sizing the child window based on the size of the parent window AND the size of the toolbar, since I want the child to fill the entire client area. So I can't resize the whole thing until everything's created.
I'm not sure that WM_CREATE is the best place to do this.
Here's what needs done:
o Create parent window
o Create toolbar (& tooltip stuff)
o Create child window
If I put things into my window procedure, I'd have
o Create parent window--> WM_CREATE
o WM_CREATE handler: create toolbar, tooltips & child window
Another way to handle this sequencing situation is just to set a "safe to paint" flag to be used by the WM_PAINT handler:
(WinMain() )
o create main window
o create toolbar & tooltips
o create child window
o load fonts, etc.
o set up other stuff needed for drawing & painting
o set "safe to paint" flag
case WM_PAINT:
if (safe to paint)
paint away ...
I had done this earlier, and it worked OK, except for the problem of this topic, the child window not appearing until resized.
One question about putting stuff in the WM_CREATE handler: if I create other stuff in that handler, what happens to the WM_CREATE messages that they generate? Don't I have a recursive situation here? At least in the case of the toolbar, which share the window procedure with the main window (the child window has its own window proc).
Regarding WM_PAINT messages: what happens if I ignore them? Do they stack up? or does the system keep sending one message over and over until it's processed? or does the default window handler take care of it?
I need a much better understanding of all this sequencing stuff than I'm able to get from either MSDN or, so far, from folks here ...
ok - i will come up with a little program
to start off, i will use Hutch's ProStart...
http://www.masm32.com/board/index.php?topic=12462.0
i think that's the latest version - it's a warmed-up version of CodeGen
a fun little program you might enjoy
i learned a lot from it when i first started writing windows programs
Quote
I need a much better understanding of all this sequencing stuff than I'm able to get from either MSDN or, so far, from folks here ...
With so little of your code visible to us there are too many possibilities as to what is wrong with it.
ok - here we go
i started with Hutch's ProStart code
i moved the toolbar include stuff into Project.asm and Project.inc
i also made a couple changes to the make batch file
then, i added the child window
all my additions are in the asm file, rather than putting some things in the inc file
i did it that way so you could find it easily
everything i added to create the child is surrounded like this
;#############################
; Added
;#############################
;stuff i added
;#############################
notice that, when i create the child window, the position and size parameters are all set to 0
we let the WM_SIZE code take care of it :U
it's also important to note that i use the main window handle from the WndProc parm, not the global value
the global handle variable has not yet been initialized when WM_CREATE is handled :P
Quote from: dedndave on November 13, 2011, 11:12:50 PM
it's also important to note that i use the main window handle from the WndProc parm, not the global value
the global handle variable has not yet been initialized when WM_CREATE is handled :P
It's probably this...
CreateWindow sends messages to wndproc before it returns with the handle.
that's correct :U
i could have done a little better job on the WM_SIZE code
wParam tells you if it is a minimized window - no need to size the child if minimized
also, lParam low/high have the client width and height
i used GetClientRect - an unnecessary step
but, you get the idea
Thanks, Dave. Haven't had a chance yet to look at what you posted but I will later.
I fixed the problem by sidestepping the whole child-window issue; I just drew and painted directly into the main window's client area, and everything works fine.
I think the original problem I had (the toolbar getting messed up, which is why I went to using a child window) was due to me using GetDC() ... ReleaseDC() in the WM_PAINT handler, instead of BeginPaint() ... EndPaint().
I would still like to know the answers to those basic questions I asked earlier, like whether one should only paint in response to a WM_PAINT message, or whether it's OK to paint at other times. And not as a matter of good style or whatever, but what is functionally the correct way to do it using WinAPI.
well - i have read a few times that you should only paint during WM_PAINT
and then, only between BeginPaint and EndPaint
not sure i understand that one :P
but, if you want to put something in the window.....
design the paint routine to handle it - perhaps some control flags to tell it something special needs to be painted
then, use InvalidateRect or InvalidateRgn to tell the OS that there is an "update region"
at that point, the OS will issue a WM_PAINT message, but at a low priority
i.e., only if no other messages are in the queue to be processed
to force an update, use UpdateWindow
when the WM_PAINT message is received, the PAINTSTRUCT has a rectangle of the area that requires updating
it also has the hDC (which is also in EAX after a BeginPaint call)
one other item in the PAINTSTRUCT is a flag to tell you if the background needs to be erased
i have done what you tried to do, before
not sure what your reason for doing it was,
but i was using the child window to eliminate flicker behind the scroll bars
in my case, the client area background color was user-selectable
if they chose some colors, the scrollbars flickered badly when they moved them or when they sized the window
by setting the client area background to COLOR_SCROLLBAR
and assigning the rest of the client area to a child window, it eliminated the flicker
i also used WS_CLIPCHILDREN - suggested by one of the forum members
i have learned to use that flag as a matter of habit for almost all windows i create :P
Any drawing that you do on the client area of the window in your WM_PAINT handler will be done each time the window is redrawn. Any drawing that you do elsewhere will not be redone each time the window is redrawn, so it will persist only until the next redraw.
;==============================================================================
include \masm32\include\masm32rt.inc
;==============================================================================
.data
hInstance dd 0
ps PAINTSTRUCT <>
.code
;==============================================================================
DlgProc proc hwndDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
SWITCH uMsg
CASE WM_CTLCOLORDLG
invoke GetStockObject, WHITE_BRUSH
ret
CASE WM_KEYDOWN
SWITCH wParam
CASE VK_D
invoke GetDC, hwndDlg
invoke TextOut, eax, 35, 20, chr$("XXX"), 3
CASE VK_R
invoke InvalidateRect, hwndDlg, NULL, TRUE
ENDSW
.IF wParam == VK_SPACE
invoke GetDC, hwndDlg
invoke TextOut, eax, 35, 20, chr$("XXX"), 3
.ENDIF
CASE WM_PAINT
invoke BeginPaint, hwndDlg, ADDR ps
invoke TextOut, eax, 35, 45, chr$("YYY"), 3
invoke EndPaint, hwndDlg, ADDR ps
CASE WM_COMMAND
SWITCH wParam
CASE IDCANCEL
invoke EndDialog, hwndDlg, 0
ENDSW
CASE WM_CLOSE
invoke EndDialog, hwndDlg, 0
ENDSW
xor eax, eax
ret
DlgProc endp
;==============================================================================
start:
;==============================================================================
invoke GetModuleHandle, NULL
mov hInstance, eax
Dialog "Test", \
"MS Sans Serif",10, \
WS_OVERLAPPED or WS_SYSMENU or DS_CENTER, \
0,0,0,50,50,1024
CallModalDialog hInstance,0,DlgProc,NULL
exit
;==============================================================================
end start
So the moral of the story is one should do all drawing within the WM_PAINT handler, unless of course you just want to draw something temporary, right?
well - temporary is a bad term - lol
you want to stay in WM_PAINT - with few exceptions that i can think of
the reason is....
if the window is momentarily obscured by another window, moved offscreen, or minimized - then restored,
the only way for the required region to be updated is via WM_PAINT
an example of where you might not do it....
you have a child window with a caption bar
you set the caption text
in cases like these, the OS will update the appearance of the caption bar if it is required
essentially, your WM_PAINT handler should be capable of updating whatever is in the client area
Quote from: dedndave on November 14, 2011, 10:40:16 PM
you have a child window with a caption bar
you set the caption text
You would use WM_NCPAINT in the child's subclass. Anyway, the PAINT handler doesn't have to do the painting: You can also just set a flag or a timer and do the painting elsewhere or later using GetDC.
Instead of just giving you little tidbits of my code, I decided to strip out all the complex stuff and just post the bare bones of the program. Here it is. It creates a main window and a child window that occupies the client area of the main window (except for the space taken up by the toolbar). It still exhibits the same problem: the contents of the child window don't appear until after it's resized.
In fact, I added some code that makes me more convinced than ever that the problem is that the main window is somehow covering the child window (or that the child window isn't being properly created or displayed until after being resized). I first write some text to the main window; if the child were being properly displayed, you wouldn't see it (since the main window is below the child in the Z-order), but you can. Only after resizing does this text disappear and the text written to the child window appears.
If someone has the patience to look at this, I'd be most grateful. It requires a .rc file (provided) which loads the bitmap for the toolbar into the app.
After read of your code:
You need to rewrite all your windows messages
Use the WM_CREATE to create the toolbar and the child windows
use the WM_PAINT message.
Put all your messages at the right place,in each window loop.
If you have no idea how to do this,use prostart (masm32),or one of the sample :
\masm32\examples\exampl01\generic\generic.asm
Your child window is not yet sized when it gets the first WM_PAINT message - because there is not yet a child win handle when the WM_SIZE message is initially being handled. Here is a demo (there are more elegant ways to achieve this, though).
ChildWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hDC:HDC, ps:PAINTSTRUCT, gpRect:RECT
MOV EAX, uMsg
CMP EAX, WM_PAINT
JE do_paint
dodefault:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
do_paint:
; Hold off painting until we're ready:
INVOKE GetClientRect, MainWinHandle, ADDR gpRect
MOV EAX, gpRect.bottom
SUB EAX, 90
sub gpRect.right, 100
INVOKE MoveWindow, ChildWinHandle, 25, 90, gpRect.right, EAX, TRUE
INVOKE BeginPaint, hWin, ADDR ps
Quote from: jj2007 on November 17, 2011, 07:30:55 AM
Your child window is not yet sized when it gets the first WM_PAINT message. Here is a demo (there are more elegant ways to achieve this, though).
I don't get it: the window's size is given when I create it:
; Get size of main & toolbar windows:
INVOKE GetClientRect, ToolbarHandle, ADDR gpRect
INVOKE GetClientRect, MainWinHandle, ADDR gpRect2
MOV EDX, gpRect2.bottom
SUB EDX, gpRect.bottom
; Use size to create child window:
INVOKE CreateWindowEx, 0, ADDR ChildClassName, NULL,
$childWinAttrs, 0, gpRect2.bottom, gpRect2.right, EDX,
MainWinHandle, NULL, hInst, NULL
MOV ChildWinHandle, EAX
Are you telling me that I can't use the window for drawing/painting until it gets
re-sized? Why is that? After all, I created it with the
WS_VISIBLE style.
Besides, you left out the part where I don't paint in the child window until well after it gets created (this is also for other reasons in my program: I have to prepare fonts and set up a lot of other stuff before painting):
do_paint:
; Hold off painting until we're ready:
CMP OK2paintFlag, TRUE
JNE dodefault
INVOKE BeginPaint, hWin, ADDR ps
...
To be more precise: It is not yet properly sized...
INVOKE CreateWindowEx, 0, ADDR ChildClassName, NULL,
$childWinAttrs, 0, gpRect2.bottom, gpRect2.right, EDX,
MainWinHandle, NULL, hInst, NULL
endif
Delete the 2
Quote
Are you telling me that I can't use the window for drawing/painting until it gets re-sized? Why is that? After all, I created it with the WS_VISIBLE style.
You can ask many questions like that.Do a correct window loop as explain in
\masm32\examples\exampl01\generic\generic.asm ;there is comment in it
You cannot write the loop messages as you want,follow the rules explains in the sample.
Sorry, Yves, you're wrong.
jj, that was the problem all along! Another stoopid mistake on my part; I was creating the child window with nonsensical x- and y-values. Your suggestion fixed it.
Yves, you don't have to do everything according to the standard canonical examples. You do have to follow certain rules about when and how to do things, though. I'm trying to learn those rules, not just copy other people's examples blindly.
There's no reason you have to create things in a WM_CREATE handler, just because everyone else does it that way.
Quote from: NoCforMe on November 17, 2011, 08:03:56 AM
I'm trying to learn those rules, not just copy other people's examples blindly.
Learning is good, not following blindly the others, too, but be ready to accept that certain standards have emerged after a lot of trial and error done by many, many very experienced coders. Transparency and maintenance, for example, are aspects that you learn to appreciate after a while...
If you want to learn, use the structure below for a simple window, and then step through it using Olly.
WndProc proc uses esi edi ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rc:RECT, xyz
SWITCH uMsg
CASE WM_DESTROY
invoke PostQuitMessage, NULL
CASE WM_CREATE
... create menus and children ...
CASE WM_COMMAND ; react here to menus and controls
movzx eax, word ptr wParam ; the Ids are in the lowword of wParam
switch eax
case IdButton1
call Button1proc
case IdEdit
call Editproc
endsw
CASE WM_SIZE
invoke GetClientRect, hWnd, addr rc
... ; resize controls
CASE WM_PAINT
call WmPaintHandlerIfAny
ENDSW
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
WndProc endp
By the way: I found the problem with your code using MasmBasic's deb macro in console mode. With the right tools, bug chasing is much easier.
SUB EDX, gpRect.bottom
; Use size to create child window:
deb 4, "Size", gpRect.bottom, gpRect2.bottom, gpRect2.right, edx ; OPT_Susy console
INVOKE CreateWindowEx, 0, ADDR ChildClassName, NULL,
$childWinAttrs, 0, gpRect2.bottom, gpRect2.right, EDX,
MainWinHandle, NULL, hInst, NULL
Output:
Size
gpRect.bottom 34 <<< the good one
gpRect2.bottom 569 <<< the bad one ;-)
gpRect2.right 588
edx 535
Quote
There's no reason you have to create things in a WM_CREATE handler, just because everyone else does it that way.
:P
Windows messages loop are the same things than the road code.
There is a stop on a road,you can ignore it,there is a crash,no surprise.
Windows give you time to perform some tasks,you can ignore them.There is a crash,no surprise.
Quote...you don't have to do everything according to the standard canonical examples. You do have to follow certain rules
about when and how to do things, though. I'm trying to learn those rules, not just copy other people's examples blindly.
that's a good thing - i think much the same way
along the way, i have learned there are reasons some things are done certain ways
there are definate advantages, sometimes - other times, they are unnecessary
an example of the unnecessary is the INVOKE WinMain construct
it is totally a carry-over from C compilers that has no real meaning in ASM-written programs
setting things up in WM_CREATE is one of the times when there are advantages
the reasons may be somewhat obscure, however, and difficult to convey :P
if you refer to the "Project.zip" file that i posted earlier in this thread....
you will see that the main window's WM_CREATE handler basically does 2 things
1) create the toolbar
2) create the child window
an advantage of doing it that way is that the WM_SIZE code takes care of sizing the child
in my example, you will see that i set the initial child window size and position values to 0 when it is created
a WM_SIZE message will be issued immediately following the completion of WM_CREATE
we eliminate the unnecessary step of calculating the size of the child in 2 different code sections
this is only one advantage of doing it this way - there are others
the hard part of trying something different is testing it under a seemingly infinite number of possible conditions
somewhere down the road, you will be chasing some obscure behaviour flaw
that is where you really learn the subtleties of writing windows programs :bg
they can't always be explained in plain-text - you just have to experience them
play with it - try it both ways and see which you like best :U
Well, thanks to all (especially jj). Everything's working nicely now; I can paint & draw in the child window, it appears instantly, resizes correctly, toolbar & tooltips work correctly, and custom cursors get selected properly.
One little tiny problem (isn't there always?). When the program first starts, I get an hourglass cursor in the child window, which goes away when I click on something or resize the window.
I checked the program's CPU usage (using Task Manager), and it's not using anything (shows as 0%). Not a huge deal, but obviously something's not right.
What are the places I should be looking for this problem? Is there a message I'm not responding to that I should? I'm handling WM_CREATE, WM_COMMAND, WM_NOTIFY and WM_SIZE in the parent, and WM_PAINT, WM_SETCURSOR, and WM_LBUTTONDOWN in the child (so far; just getting started).
the last code that you posted does not exhibit the problem
maybe you can use that fact to help troubleshoot it
the hourglass is controllable in the startup options when your process is created
i have a similar issue with a program that calls CreateProcess
you can view some related reading in the STARTUPINFO structure documentation and here...
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682015%28v=vs.85%29.aspx
of course, you do not create your own processes - it's likely to be done by windows explorer
i would suspect an issue in the main process, somewhere between CreateWindowEx and the message loop :P
or - perhaps you are not returning the correct value in EAX in one of the WndProc message handlers