Newbie question here.
I'm in a tight loop doing some lengthy computations, and I am updating an editbox every million loops to show the progress. What is the command to refresh the editbox on the screen after I do the sendmessage wm_settext to the editbox? Likewise, I have a quit button, but while in the loop, it never gets a chance to send me a message that it was pushed. What is the normal procedure for refreshing the state of pushbuttons so I can service it in the main dialog proc?
Edited:
Ok, I found UpdateWindow for the edit box. Is this the best one to use?
UpdateWindow didn't work for the quit button however.
Call InvalidateRect before you call UpdateWindow.
invoke InvalidateRect, hWnd, NULL, TRUE
invoke UpdateWindow, hWnd
Assuming "hWnd" represents the window handle of your edit window, then the first call adds its entire visible area to Windows' internal update region. The call to UpdateWindow will then have something to do.
Thank you. It was working but only every 10th time without the invalidaterect, I will know to use it in the future. Any ideas on the quit button?
As for the button you'll have to find a way to process your application message loop. The WM_COMMAND message generated by clicking the button won't get processed if you are constantly in a processing loop rather than the main message loop.
Many apps create a separate, fast "worker thread" which does your lengthy computations while the application thread continues to respond to message events. The main thread won't respond as snappily as an app doing nothing except waiting for the user to click a button, but it will respond. This would probably also partially fix your EDIT window update issue.
Ok, Thanks again.
QuoteThe plural of "data" is "data" ---So saith the wise Alondo.
We argued for years about writing reports whether you say "The data is ..." or "The data are ..." :P
Using a separate thread for your lengthy computation is probably the better route to continue processing messages sent to your edit box. You can still post messages from your thread so that your edit box will set new text while your computation is still going on. You will still have to establish some means of communication between your thread and your main window.
If you are not yet familiar with threads, you will find an excellent detailed explanation at:
http://board.win32asmcommunity.net/viewtopic.php?t=17&highlight=thread
The other way to respond to messages sent to your main window while doing your computation without using a thread is to peek at the message loop at intervals and terminate/suspend the computation if necessary, returning control to the main window to process the message. This could be the easier way if the number of messages requiring immediate action are few.
Raymond
Thanks for the clue, Raymond. PeekMessage seems to be exactly what I was looking for!
Actually I believe data is the plural for datum. Latin, y'know! The "correct" usage for data is to treat it as plural: "the data are..."
However, I think that battle was lost long ago. Everyone treats "data" as a collective singular, like "debris": there's no such thing as one debris, yet we treat it as a singular in a sentence. At least in America we do. I'm not sure about the King's English. Here we say "the group is..." whereas in the UK, Oz, and NZ they say "the group are..."
Really rather a silly language at times. ::)
When a group acts as a group, you would say is. For instance, the group is going to the party. But, if the members of the group are acting seperately, you use are. At least in standard English. No one talks that way.
Not separately, individually. This is a good page that explains it...
http://alt-usage-english.org/groupnames.html
Anyway, way off-topic, sorry! :toothy
Ok, I've been playing with this for a couple of days. I read up on threads, and while they will probably work, they seem unnecessarily complicated. I tried peekmessage, but no luck. The problem seems to be, even though I click on the quit button while in the tight loop, a message is never sent to the button so nothing for peekmessage to find. I read up on Queued Messages in the win32.hlp file and it seems very straighfoward and simple, but it doesn't work for me. The mouse moves over the button, so I know the system is working, it just that a message to the command button is never put in the queue. I gave it plenty of time to catch up by exiting the procedure with the tight loop, but no message. I know the button works because I get a message if I click on it outside the tight loop procedure. The only thing I can think of is trying to catch the mouse messages, but before I enter that can of worms, I thought I would see if anyone else had any ideas.
Jimg
How about posting that piece of code in your tight loop where you tried to intercept the messages. We may be able to help you figure out your lack of success. PeekMessage should work when used properly.
Raymond
Ok, I'll strip out the crud and reconstruct what I tried and post it later tonight or tomorrow. Thanks.
This is embarrasing :red
I started from scratch, and peekmessage worked this time. The problem before was I was trying to do too much, testing the lpMsg return of Peekmessage to see if the button was pushed, and I never got the WM_COMMAND message with BN_CLICKED for the button.
This time I just did the peekmessage followed by a dispatchmessage and let the main loop handle it and set a flag. Seems to work great in this simple example. Here's the code in case I'm doing something really dumb you'd like to point out ;)
.486
.model flat, stdcall
option casemap :none
.nolist
include windows.inc
; -----------------------------------------------------------------------
literal MACRO quoted_text:VARARG
LOCAL local_text
.data
local_text db quoted_text,0
.code
EXITM <local_text>
ENDM
SADD MACRO quoted_text:VARARG
EXITM <ADDR literal(quoted_text)>
ENDM
sadd equ SADD
GetHandle Macro MyId:Req,MyHandle:Req
invoke GetDlgItem,hWin,MyId
mov MyHandle,eax ; save handle to control
endm
uselib MACRO libname
include libname.inc
includelib libname.lib
ENDM
; -----------------------------------------------------------------------
uselib user32
uselib kernel32
uselib shell32
.list
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data?
buf1 db 100 dup (?)
hWin dd ?
hEdt dd ?
LoopCnt dd ?
.data
QuitPushed dd 0
Started dd 0
AllDone dd 0
.code
Program:
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,101,0,ADDR DlgProc,0
invoke ExitProcess,eax
DlgProc proc uses edi esi ebx hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg == WM_COMMAND
mov eax,wParam
mov edx,wParam
shr edx,16
.if ax==1001 ; doit button
.If edx==BN_CLICKED
call Doit ; go do test
.EndIf
.elseif ax==1003 ; quit button
.If edx==BN_CLICKED
inc QuitPushed ; flag that quit was pushed
.endif
.endif
.elseif uMsg== WM_INITDIALOG
push hWnd
pop hWin
invoke GetDlgItem,hWin,1002
mov hEdt,eax ; save handle to edit box
.elseif uMsg == WM_CLOSE
invoke EndDialog,hWnd,0
.else
pass: mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp
.data?
lpMsg MSG <?>
.code
Doit:
mov LoopCnt,1
Cnt:
invoke wsprintf,addr buf1,sadd(" doing loop %li"),LoopCnt
invoke SendMessage,hEdt,WM_SETTEXT,0,addr buf1
cmp QuitPushed,0
jne Done
mov ebx,1000000000 ; this takes about a second on my machine
again:
sub ebx,1 ; this represents my long calculation loop
jnz again
inc LoopCnt
PMsgLoop:
invoke PeekMessage,addr lpMsg,hWin,0,0,PM_REMOVE ; Check for a message
or eax,eax
je Cnt ; no message available
invoke DispatchMessage,addr lpMsg
jmp PMsgLoop
Done:
invoke MessageBox,hWin,sadd("Found quit flag inside doit, quitting!"),0,0
ret
End Program
; Here's the rc
#define Doit 1001
#define CurLoop 1002
#define Quit 1003
101 DIALOGEX 6,6,77,66
CAPTION "Test"
FONT 8,"MS Sans Serif"
STYLE 0x90cf0880
EXSTYLE 0x00000000
BEGIN
CONTROL "Doit",Doit,"Button",0x54002000,8,9,61,13,0x00000000
CONTROL "",CurLoop,"Edit",0x50010000,8,26,61,13,0x00000200
CONTROL "Quit",Quit,"Button",0x50010000,8,44,61,13,0x00000000
END
I'll put this in my real program and cry and moan some more if it doesn't work there.
It will work if you only click on the "Quit" button while your loop is running.
However, the way you have it set up may have unexpected consequences if you click on the "Doit" button.
- After the DispatchMessage, your loop waits until that function has terminated and returned before performing the next instruction.
- When the message is handled by your DlgProc, guess what happens: the Doit procedure would be called and started over again until completed, and control may then be returned to the point when the loop was interrupted by the click on that "Doit" button and the computation resumed from that point!!!
As your DlgProc gets more complex, you could have other surprises also, apart from delaying your loop for a lot of messages which may be of no importance to that loop (such as processing mouse movement messages which may be required to determine which values to use to start the loop)
The proper way of using the PeekMessage is to identify only those messages which require immediate termination of the loop. If the loop could be resumed later, you then save whichever data is required for such resumption and then return control to your DlgProc.
The following would be an example for your app to process only the Quit message.
PMsgLoop:
invoke PeekMessage,addr lpMsg,hWin,WM_COMMAND,WM_COMMAND,PM_REMOVE
or eax,eax
je Cnt
mov eax,lpMsg.wParam
cmp ax,1003
jnz PMsgLoop
invoke PostMessage,lpMsg.hwnd,lpMsg.message,lpMsg.wParam,lpMsg.lParam
ret
Your 1003 case in the DlgProc should thus be modified to quit the program instead of changing a memory variable.
The above could be expanded to also look for other messages such as the WM_CLOSE.
I guess this should be enough to get you started on the right track. Many, many more variations are possible.
Raymond
Thanks Raymond.
This is similar to what I tried initially. I changed the PMsgLoop exactly as you show and ran the program. When I press Quit while the tight loop is running, nothing happens. Occasionally, I can hit the quit while the Peekmessage loop is running, and everything on the dialog disappears, only a gray dialog left. Then if I hit the X to close the program, I get a message from the system that the program is not responding. This is exactly what made me so frustrated before, nothing made sense to me.
I must not understand how PeekMessage works. From reading the description, I assumed that if I used PM_REMOVE, I had to either service this message or pass it along to the system with DispatchMessage, otherwise I might be discarding something important.
I've been following this thread for a little bit and later today I'll post an example of a simple dialog (like what you have shown recently) along with a worker thread that does some "time consuming" calculations (including using semaphores to make sure the thread has started/terminated, etc) that maybe you can use or get ideas from. Look for this in about 4-5 hours from now -- busy at the moment finishing something else.
Relvinian
QuoteFrom reading the description, I assumed that if I used PM_REMOVE, I had to either service this message or pass it along to the system with DispatchMessage, otherwise I might be discarding something important.
That is correct. That is why I said "The above could be expanded to also look for other messages such as the WM_CLOSE". In your case, all other WM_COMMAND messages could be discarded without any penalty.
In order to find out why you were getting inadequate results, I would have to look at your entire code. I have used that type of approach on numerous occasions.
However, Relvinian may provide you with more info. The use of a thread definitely would allow you to process all messages and discard only those which must not be processed further. You may want to use your current app to practice with that approach to become familiar with such type of programming.
Raymond
Ok,
I have attached a sample dialog with a worker thread that counts down and when reaching zero terminates the thread. There is no UI updating to let you know what the worker thread is doing but that can be easily acheived by creating some other control on the dialog, etc. This is rough code that I just cranked out during my lunch hour. It seems to work wells but there may be bugs.
The code is heavily commented so I hope it is easy to understand. If not, don't hesitate to ask questions.
What you will notice though is that there seems like a lot of code but I have jazzed up the dialog a little to disable the "worker button" while the thread is executing and re-enable it after it is done. The text on the button also changes during execution of the thread. The OK and Cancel buttons along with the "X" will instantly close and terminate the dialog (exe). This also aborts the thread and waits for it to close.
There is a build.bat to build the exe file. I'll let you guys play with it and if you have questions, let me know.
Relvinian
PS - I just found out that my little WM_COMMAND handler currently doesn't handle hotkeys or accelerators. Simple fix but for now, just use the buttons with mouse. ;-)
[attachment deleted by admin]
Thank you Relvinian, I'll look it over.
Raymond-
I'd still like to figure this peekmessage stuff out. I tried to take care of the issues you spoke about in the code below. When I run it, it is not recognizing the quit button. I can see the button change state, so the system is recognizing it, my test for 1003 fails in the PMsgLoop using the code you presented (unless I misunderstood or screwed something up). Ultimately, when I hit the X to close the program, it looks like it is closed, but it is still in memory and has to be removed with task manager.
.486
.model flat, stdcall
option casemap :none
.nolist
include windows.inc
; -----------------------------------------------------------------------
literal MACRO quoted_text:VARARG
LOCAL local_text
.data
local_text db quoted_text,0
.code
EXITM <local_text>
ENDM
SADD MACRO quoted_text:VARARG
EXITM <ADDR literal(quoted_text)>
ENDM
sadd equ SADD
GetHandle Macro MyId:Req,MyHandle:Req
invoke GetDlgItem,hWin,MyId
mov MyHandle,eax ; save handle to control
endm
uselib MACRO libname
include libname.inc
includelib libname.lib
ENDM
; -----------------------------------------------------------------------
uselib user32
uselib kernel32
uselib shell32
.list
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data?
buf1 db 100 dup (?)
hWin dd ?
hDoit dd ?
hEdt dd ?
hQuit dd ?
LoopCnt dd ?
.code
Program:
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,101,0,ADDR DlgProc,0
invoke ExitProcess,eax
DlgProc proc uses edi esi ebx hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg == WM_COMMAND
mov eax,wParam
mov edx,wParam
shr edx,16
.if ax==1001 ; doit button
.If edx==BN_CLICKED
call Doit ; go do test
.EndIf
.endif
.elseif uMsg== WM_INITDIALOG
push hWnd
pop hWin
GetHandle 1001,hDoit
GetHandle 1002,hEdt
GetHandle 1003,hQuit
.elseif uMsg == WM_CLOSE
invoke EndDialog,hWnd,0
.else
pass: mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp
.data?
lpMsg MSG <?>
.code
Doit:
invoke EnableWindow,hDoit,FALSE ; disable doit button while working
invoke EnableWindow,hQuit,TRUE ; enable quit button while working
mov LoopCnt,1
Cont:
invoke wsprintf,addr buf1,sadd(" doing loop %li"),LoopCnt
invoke SendMessage,hEdt,WM_SETTEXT,0,addr buf1
invoke InvalidateRect, hWin, NULL, TRUE
invoke UpdateWindow, hWin
mov ebx,1000000000 ; this takes about a second on my machine
Again:
sub ebx,1 ; this represents my long calculation loop
jnz Again
inc LoopCnt
PMsgLoop:
invoke PeekMessage,addr lpMsg,hWin,0,0,PM_REMOVE ; Check for a message
or eax,eax
je Cont ; no message available
cmp lpMsg.message,WM_CLOSE
je Done
mov eax,lpMsg.wParam
cmp ax,1003
je Done
invoke DispatchMessage,addr lpMsg ; service the message I removed
jmp PMsgLoop
Done:
invoke PostMessage,lpMsg.hwnd,lpMsg.message,lpMsg.wParam,lpMsg.lParam
invoke EnableWindow,hQuit,FALSE ; done with quit button, disable it
invoke EnableWindow,hDoit,TRUE
ret
End Program
; rc-
#define Doit 1001
#define CurLoop 1002
#define Quit 1003
101 DIALOGEX 6,6,77,62
CAPTION "Test"
FONT 8,"MS Sans Serif"
STYLE 0x90cf0880
EXSTYLE 0x00000000
BEGIN
CONTROL "Doit",Doit,"Button",0x54002000,8,9,61,13,0x00000000
CONTROL "",CurLoop,"Edit",0x50010000,8,26,61,13,0x00000200
CONTROL "Quit",Quit,"Button",0x58010000,8,44,61,13,0x00000000
END
After a little more testing, I find that the Doit procedure never gets 1003 from PeekMessage. The only message that seems to be of value is WM_LBUTTONDOWN, which would trigger the 1003 in the main message loop.
Quote from: Jimg on January 14, 2005, 02:29:07 PM
Newbie question here.
I'm in a tight loop doing some lengthy computations, and I am updating an editbox every million loops to show the progress. What is the command to refresh the editbox on the screen after I do the sendmessage wm_settext to the editbox? Likewise, I have a quit button, but while in the loop, it never gets a chance to send me a message that it was pushed. What is the normal procedure for refreshing the state of pushbuttons so I can service it in the main dialog proc?
Edited:
Ok, I found UpdateWindow for the edit box. Is this the best one to use?
UpdateWindow didn't work for the quit button however.
You need to (in VB) call DoEvents. Some where around here I have an assembly version of the VB DoEvents (which I think I got here on the MASM Forum before the hack). ::)
If you want I can look around for it and post it.
Scott
Thanks Scott. That is essentially what the Peekmessage, Dispatchmessage is doing I hope.
I spent a few hours last night with my good friend ollydbg analyzing this problem. I think that posting my findings may help and discourage others who may try something similar.
I had mentioned previously that I have often used the PeekMessage but that was with regular windows which have their own message querying code and message queue. However, a dialog box is managed by a different system which translates all messages and dispatches them immediately to the DlgProc. That DlgProc does not have to query its message queue.
Therefore, trying to peek into such Dlg messages and trying to re-dispatch them may create havoc with the entire dialog box managing system while the app is trying to perform some computation in a loop external to the DlgProc. For example, all mouse movement messages do get sent through the dialog box procedure before being handled by the default system if they are not intercepted for specific reasons.
Using thread(s) for such operations should therefore be the only safe route to use with dialog boxes (or any other type of window which does not have its own message querying code). This then allows the DlgProc to safely respond to all incoming messages while maintaining some communication with the running thread.
You will notice in the following modified code using such a route that enabling/disabling controls is not a necessity if conditions are checked before taking action. The app now works perfectly as you had intended. (You will only have to modify your resource script to enable the "Quit" button.)
Your header, macros and includes have not been reproduced in the modified code.
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
LoopProc PROTO :DWORD
.data?
buf1 db 100 dup (?)
hWin dd ?
hDoit dd ?
hEdt dd ?
hQuit dd ?
LoopCnt dd ?
computing dd ? ;flag 0=thread not active, 1=active
compstop dd ? ;0=thread must abort, 1=OK to continue
threadID dd ?
.code
Program:
invoke GetModuleHandle,NULL
push eax
invoke InitCommonControls
pop eax
invoke DialogBoxParam,eax,101,0,ADDR DlgProc,0
invoke ExitProcess,eax
DlgProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg == WM_COMMAND
mov eax,wParam
mov edx,wParam
shr edx,16
.if ax==1001 ; doit button
.If edx==BN_CLICKED
.if computing == 0
call Doit ; go do test
.endif
.EndIf
.elseif ax==1003 ; quit button
.If edx==BN_CLICKED
.if computing != 0 ;if thread active
mov compstop,0 ;advise thread to terminate
@@:
cmp computing,0
jnz @B ;wait until thread terminates
.endif
invoke EndDialog,hWnd,0
.EndIf
.endif
.elseif uMsg== WM_INITDIALOG
push hWnd
pop hWin
GetHandle 1001,hDoit
GetHandle 1002,hEdt
GetHandle 1003,hQuit
mov eax,TRUE
ret
.elseif uMsg == WM_CLOSE
.if computing != 0 ;if thread active
mov compstop,0 ;advise thread to terminate
@@:
cmp computing,0
jnz @B ;wait until thread terminates
.endif
invoke EndDialog,hWnd,0
.else
pass: mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp
.data?
lpMsg MSG <?>
.code
Doit:
mov compstop,1 ;set up communication flags
mov computing,1
invoke CreateThread,0,0,addr LoopProc,0,0,ADDR threadID
invoke CloseHandle,eax ;saving handle not usually needed
ret ;return to main app to process messages
;the computing thread has full access to global data
LoopProc proc a:DWORD
mov LoopCnt,1
Cont:
invoke wsprintf,addr buf1,sadd(" doing loop %li"),LoopCnt
invoke SendMessage,hEdt,WM_SETTEXT,0,addr buf1
invoke InvalidateRect, hWin, NULL, TRUE
invoke UpdateWindow, hWin
mov ebx,1000000000 ; this takes about a second on my machine
Again:
sub ebx,1 ; this represents my long calculation loop
jnz Again
inc LoopCnt
cmp compstop,0 ;is thread told to terminate
jnz Cont ;continue otherwise
Done:
mov computing,0 ;advises main window thread has terminated
ret ;close thread
LoopProc endp
End Program
Raymond
Thank you for your patience, Raymond. If it's gotta be threads, then I'll use threads, but I don't hafta like it :wink
I've tried many things myself and found out what you are saying is true, Doit isn't getting many of the messages using peekmessage, but the main loop is. All I really needed was some way for doit to say call me back after processing the messages just before doing a return. I played a lot with postmessage thinking it would put a message after all the other messages which would call doit back when clear, but the order of processing messages is not nearly as linear (first in, first out) as the documentation leads one to believe.
Thanks again, I'll just move on now......