Most programmers use the API MessageBox widely to communicate with the user. The user then clicks "OK" and the message box disappears. It's a shame, however, that you can't readily make timed messages boxes or other windows which disappear automatically after a short while. These would be very useful for various warning messages or copyright notices. Here is some code which in the first two example achieves this result with message boxes and in the third example by drawing a copyright notice directly onto the screen during WM_PAINT.
Another obvious way is to use the WM_TIMER message and have
everything controlled either through the window procedure or a timer procedure. This would be the preferred way if you wanted to use a modeless window to contain the temporary mesage. In theory there could be more than one such window appearing at any one time, so you would probably want to stop this happening.
The methods shown here rely on the creation of new threads to keep control of the temporary window. However, it is usually not a good idea for any thread other than the main thread to interface with the screen since this can
confuse the system and cause timing problems.
However message boxes (which are used in the first 2 examples) are modal, and modal windows can be created and destroyed using a thread other than the main thread, since there is usually no significant main thread activity while the modal window is on display. In the third example all drawing is done by the main thread, and the control thread merely changes a flag and posts a message to the main thread.
Note also that the threads which are created here to make and to control
the message boxes, simply end when they return to the system using RET.
There is no other formality required.
Temporary message box ended by EndDialog
This example and the next use a structure of 6 dwords in the data section
to hold various handles, the maximum time that the message box
should be on the screen (at +8h), the type of controls to be shown
by the message box (+10h) and the correct text to show in the message
box (at +14h). I have called this structure TEMP_MSB_PARAMS. Note that
there is only one such structure, therefore this method only works with
modal windows (or where you make sure only one such temporary window
can appear at any one time). Note also that the structure can be read
by any thread. This is because all threads created by the executables
in your program use the same memory space (but have different registers and
stack). Here is the structure as declared in the data section:-
TEMP_MSB_PARAMS DD 6 DUP 0
;+0=MSBhthread; +4=reserved; +8=time; +0Ch=hParent;
;+10h=message box type; +14h=message number
Here is the code used in the main thread. This code creates a new thread
with a starting point at code address TEMP_MSB1. ThreadId receives the
ID of the new thread, but this is not read. After the thread is created
its handle is inserted in the TEMP_MSB_PARAMS structure at offset zero.
Note that the system does not actually make the new thread (or at least
there is no action in the new thread) until this (main) thread returns to the
system. This is significant in two ways. Firstly the handle returned
by CreateThread will be received by the main thread as shown here in
this code before anything happens in the new thread. Hence it
can be relied on by the new thread as a handle. Secondly, it is not feasible simply to put the main thread to sleep for a while using the API Sleep after making
the new thread, in the hope that the message box would then appear. It
would not, but it would appear after the main thread returned to the
system (after the return of Sleep)!
Note that the EBX register survives the call to CreateThread. All
Windows APIs save and restore ESI,EDI and EBX. All your window and hook
procedures should do the same.
TEMP_MESSAGEBOX1:
MOV EBX,ADDR TEMP_MSB_PARAMS
MOV D[EBX+8h],3000D ;time=3 seconds
MOV D[EBX+10h],10020h ;message box type ("?" plus foreground)
MOV D[EBX+14h],0 ;message box message number
PUSH ADDR ThreadId,0,0,ADDR TEMP_MSB1,0,0
CALL CreateThread
MOV [EBX],EAX ;get handle to new thread
RET ;nothing happens till this thread returns
Now lets look at the coding of the new thread. Here the handle to the
foreground window just before the message box is created is found and
kept - we'll see why later. Then a second new thread is made at
the code address CONTROL_MSB1. This is used to control the time the
message box appears on the screen. Finally the message box is made containing
the controls as specified and containing the correct text from the
message tables. This is MESSAGELIST1 and MESSAGELIST2 - if you want
to look at these view them with the debugger. Now this is the clever
part. The API MessageBox usually does not return until the user clicks
"OK" or presses the "Enter" key. However, obviously it would also return
if the message box is destroyed. How do you destroy a message box? By
passing its handle to and calling EndDialog. Now look down to the
code in CONTROL_MSB1 to see how this is done.
TEMP_MSB1:
CALL GetForegroundWindow ;get current foreground window (parent)
MOV [TEMP_MSB_PARAMS+0Ch],EAX
PUSH ADDR ThreadId,0,0,ADDR CONTROL_MSB1,0,0
CALL CreateThread ;create the control thread
PUSH [TEMP_MSB_PARAMS+10h] ;load message box type
MOV EAX,[TEMP_MSB_PARAMS+14h] ;get message number
PUSH [MESSAGELIST1+EAX*4] ;get title message
PUSH [MESSAGELIST2+EAX*4] ;get main message
PUSH [TEMP_MSB_PARAMS+0Ch] ;parent
CALL MessageBoxA
RET ;finish the thread on closure
Here is the control code which is in the second new thread. The first thing
this thread does is to wait more than half a second until the message box
is made. This is continually tested for by comparing the current
foreground window with the window which was foreground when MessageBox
was called. Note that the message box was made with the flag 10000h (MB_SETFOREGROUND) to ensure that it does become the foreground window. After that the thread goes into a "wait" API WaitForSingleObject.
This API is passed the maximum time for the message box (at +8h in the
structure) which is used as a timeout. It is also passed the handle
to the first thread which was made. That handle would cease to be a
valid thread handle if the user clicks "OK". On that event WaitForSingleObject
would return. If that happens there is no need to call EndDialog and
this type of return is tested for (eax=102h if there was a timeout).
CONTROL_MSB1:
MOV ESI,50D
L3:
CALL GetForegroundWindow ;get current foreground window
CMP [TEMP_MSB_PARAMS+0Ch],EAX ;see if the same as parent
JNZ >L4 ;no so its the hWnd of the messagebox
PUSH 10D
CALL Sleep
DEC ESI
JNZ L3
RET
L4:
MOV EBX,EAX
PUSH [TEMP_MSB_PARAMS+8h],[TEMP_MSB_PARAMS] ;wait for close or timeout
CALL WaitForSingleObject
CMP EAX,102h ;see if timeout
JNZ >L5 ;no, so thread was terminated (button clicked)
PUSH 0,EBX
CALL EndDialog
L5:
RET
Temporary message box ended with simulated key
This code works in a similar way to the first, except that
instead of ending the message box using EndDialog it simply simulates
the key "Enter". It is therefore essential to ensure that the message
box has the foreground when it is made and a further check is made that
the message box is still in the foreground before "Enter" is sent. To view this in the debugger use the breakpoint TEMP_MESSAGEBOX2. Only the coding in the control thread is different:-
CONTROL_MSB2:
MOV ESI,50D
L3:
CALL GetForegroundWindow ;get current foreground window
CMP [TEMP_MSB_PARAMS+0Ch],EAX ;see if the same as parent
JNZ >L4 ;no so its the hWnd of the messagebox
PUSH 10D
CALL Sleep
DEC ESI
JNZ L3
RET
L4:
MOV EBX,EAX
PUSH [TEMP_MSB_PARAMS+8h],[TEMP_MSB_PARAMS] ;wait for close or timeout
CALL WaitForSingleObject
CMP EAX,102h ;see if timeout
JNZ >L5 ;no, so thread terminated (button clicked)
CALL GetForegroundWindow ;check the message box has the focus
CMP EAX,EBX
JNZ >L5
PUSH 0,0,0,0Dh
CALL keybd_event ;send enter key
L5:
RET
Temporary window painted directly
In this code, a flag is set to cause display of the temporary message. On a WM_PAINT message (which of course could happen at any time), if this flag is set, the message is drawn first, and the area of the message is removed from the "clipping region" to stop it being drawn over by the rest of the code which is executed on the WM_PAINT message. Another way of describing this in Window-parlance is that the area of the message is "validated". To understand this, it is necessary to understand how Windows deals with windows which overlap each other. Windows does not draw to the screen a series of windows one on top of the other so that the last one to be painted appears on top. If it did do this there would be a lot of "flashing" as windows appeared then disappeared as they were drawn over. Instead, the system keeps a record of which part of the screen needs updating (the "update region" or "invalid region") and it also knows which part of a window is visible at any one time (the "visible region"). The system only permits drawing in these two regions (together they are known as the "clipping region"). There may be various reasons for the need for updating: for example, another window may have been removed to expose a part of the window to be painted, or the user may have clicked the taskbar to show the window concerned. See also clipping control by device context and clipping control by window.
The code used to control the temporary window is very simple. First
DRAWFLAG,1h is set which will draw the window on the WM_PAINT message.
Then a thread is created (starting address DRAW_TEMP) which controls the
time the window is to remain. DRAW_TEMP is passed the value in EAX, which
in this case is set to 3 seconds. When the new thread starts this value
can be recovered from the stack at EBP+0Ch. After creating the new
thread the main thread redraws the main window by calling RedrawWindow, which
"invalidates" the whole of the main window. After the appropriate delay
the code at DRAW_TEMP returns but before doing so it sends message 404h
to the main window. You need to add in the window procedure a call to
MESSAGE_404 on receipt of this message. [Note all user-messages must have
a value of above 400h, and when sending messages between threads you would
normally use PostMessage and not SendMessage].
TEMP_PAINTEDTEXT:
OR B[DRAWFLAG],1h
MOV EAX,3000D ;3 second delay
PUSH ADDR ThreadId,0,EAX,ADDR DRAW_TEMP,0,0 ;eax=parameter to send
CALL CreateThread
MESSAGE_404:
PUSH 1,0,0,[hWnd] ;invalidate
CALL RedrawWindow
RET
;
DRAW_TEMP:
PUSH [EBP+0Ch] ;parameter sent to new thread
CALL Sleep
AND B[DRAWFLAG],0FEh
PUSH 0,0,404h,[hWnd]
CALL PostMessageA
RET
Now here is the code which draws the temporary message. This is the main
window procedure which responds to the message WM_PAINT. If DRAWFLAG,1h
is set, before going to the main paint procedures, the message is drawn
and then the area covered by message is removed from the "clipping region".
Note that you cannot use the API ValidateRect here. The reason is that
although ValidateRect will change the update or clipping region of a
window, it does not change the device context (DC). And since you have
already called BeginPaint and obtained the DC for the window, any call
to ValidateRect will have no effect. You need to use an API which alters
the DC itself. So ExcludeClipRect is used instead. Note also the code
below is simpified by using the advantage that assembler has over "C" of being
able to PUSH whenever you like. Thus, the return from SelectObject, which
is the previously selected font in the DC, is PUSHed for restoration using
SelectObject at the end of the code. Also the rectangle is pushed
for ExcludeClipRect before it is amended slightly for DrawText.
This saves having to restore it after DrawText. GET_CENTRAL_RECT is a
procedure which centralises the required size of window given by EDX and
ECX in the main window and returns the correct rectangle EBX. It is left
out of the code in order to simplify it.
MAIN_PAINT:
PUSH ADDR PAINTSTRUCT
PUSH [EBP+8]
CALL BeginPaint ;get device context in eax
MOV [hDC],EAX
TEST B[DRAWFLAG],1h ;see if should draw temporary message
JZ >L220 ;no
PUSH 12D
CALL GetStockObject ;get handle to the system's ansi font
PUSH EAX,[hDC]
CALL SelectObject
PUSH EAX ;push previous object for later
MOV EDX,100D ;get height
MOV ECX,182D ;get width
CALL GET_CENTRAL_RECT ;centralise the rectangle
PUSH 10h,4,EBX,[hDC]
CALL DrawFrameControl ;draw a button
PUSH 1,[hDC] ;transparent
CALL SetBkMode
PUSH [EBX+0Ch],[EBX+8h],[EBX+4],[EBX],[hDC] ;pushes for ExcludeClipRect
ADD D[EBX],20D ;draw a little to the right
ADD D[EBX+4],20D ;and down a little
PUSH 0,EBX,-1,ADDR FUNNY_MESS1,[hDC]
CALL DrawTextA
CALL ExcludeClipRect ;stop any write over of rect pushed earlier
PUSH [hDC] ;previous object alread pushed
CALL SelectObject
L220:
;------- normal paint code goes here ----------