"Use of Win32" demonstrations
Simulating a dialog

   Using new message loop
   Using EnableWindow

Using new message loop

This coding shows one way to create a window acting just like a dialog, making use of common controls (eg. buttons) and the usual Windows keyboard interface. Why would you want to do this? Well, it gives you the opportunity to make some exotic types of windows, yet still using the dialog functionality provided by the system. This reduces the overall amount of code required. You will see in the code below that once the main window is made the code enters its own message loop. It doesn't have to have its own message loop - it could use the main message loop instead. But this code is useful if there is no main message loop (eg. because there is no main window) or you prefer not to mix your code. The new message loop includes the API IsDialogMessage which ensures that the dialog keyboard interface is used by the similated dialog.
If you want to draw your own window, it is important to follow the rules. If your window has a "frame" (a border or a title bar) only draw this during the message WM_NCPAINT. The client body of the window can be drawn during the message WM_PAINT or WM_ERASEBKGND message.
Although the example here is for a modeless window, with its own taskbar entry, you can make a modal window too. To do that, use the style WS_EX_TOPMOST when making the window, and disable all other windows which might otherwise accept mouse or keyboard input. Don't forget to enable them again when the simulated dialog window is destroyed!
See also simulated dialog using EnableWindow.
The relevant breakpoint is SIMULATE_DLG1.
Here is the code:-

DATA SECTION
;
hSimDlg DD 0
BUTTON_CLASSNAME DB 'BUTTON',0
SIMDLG_CLASSNAME1 DB 'SD1',0
;
CODE SECTION
;
SIMULATE_DLG1:
CALL INITIALISE_WNDCLASS    ;get WNDCLASS structure in ebx & set values
MOV D[EBX+4],ADDR SimDlgProc1
MOV D[EBX+24h],ADDR SIMDLG_CLASSNAME
PUSH EBX
CALL RegisterClassA     ;register SimDlgProc
PUSH 0,[hInst],0,0      ;no parent therefore appears on taskbar
PUSH 160,299            ;set height, width
PUSH 60,60              ;y-pos then x-pos
PUSH 90000000h+2000000h ;WS_POPUP+VISIBLE+CLIPCHILDREN
PUSH 'Dialog window'    ;title (for the taskbar)
PUSH ADDR SIMDLG_CLASSNAME     ;registered class name
PUSH 0
CALL CreateWindowExA    ;make main window, returning handle in EAX
MOV [hSimDlg],EAX       ;keep handle
L1:
PUSH 0,0,0
PUSH ADDR MSG
CALL GetMessageA
OR EAX,EAX              ;see if WM_QUIT
JZ >L2
PUSH ADDR MSG,[hSimDlg]
CALL IsDialogMessage
OR EAX,EAX              ;see if the message has been processed
JNZ L1                  ;key was dealt with by system
PUSH ADDR MSG
CALL TranslateMessage
PUSH ADDR MSG
CALL DispatchMessageA
JMP L1
L2:
PUSH hInst,ADDR SIMDLG_CLASSNAME1
CALL UnregisterClassA   ;ensure class is removed
PUSH [hWnd]
CALL SetFocus           ;restore focus to main window
RET
The WNDCLASS structure is filled with zeroes by INITIALISE_WNDCLASS. In particular, no background colour is given to the structure at +1Ch. This stops the system trying to paint the background of the window, and allows you do do so on the message WM_ERASEBKGND. Note that the style WS_CLIPCHILDREN is used. This ensures that the device context sent with the WM_ERASEBKGND message will not allow you to draw over any controls. Once the window is made the thread enters the message loop. Note how IsDialogMessage is used in the message loop. This API can be used by ordinary windows as well as dialogs. Calling this API ensures that the keys used in the usual dialog keyboard interface are processed properly by the system. This permits, for example, navigation around the controls using TAB, arrows, and a simulated mouse click on ENTER. All this is done by the system and you don't have to write any code.
Here is the code for the window procedure:-
SimDlgProc1:
;
PUSH EBP
MOV EBP,ESP
PUSH EBX,EDI,ESI        ;save registers as required by Windows
;now [EBP+8]=hWnd, [EBP+0Ch]=uMsg, [EBP+10h]=wParam, [EBP+14h]=lParam,
MOV EAX,[EBP+0Ch]       ;uMsg
CMP EAX,1h              ;see if WM_CREATE
JNZ >L316               ;no
CALL CREATE_DLGWINDOW
JMP >L400
L316:
CMP EAX,2               ;see if WM_DESTROY
JNZ >L318               ;no
PUSH 0
CALL PostQuitMessage    ;Quit via message loop
JMP >L360
L318:
CMP EAX,111h            ;see if WM_COMMAND
JNZ >L322               ;no
CALL PROCESS_SIMDLGCOMMAND
JMP >L360
L322:
CMP EAX,14h             ;see if WM_ERASEBKGND
JNZ >L328               ;no
MOV ESI,ADDR SIMDLG_MESS1
CALL PAINT_DLGWINDOW
MOV EAX,1               ;ensure that it returns "TRUE"
JMP >L400
L328:
PUSH [EBP+14h],[EBP+10h],[EBP+0Ch],[EBP+8h]
CALL DefWindowProcA
JMP >L400
L360:
XOR EAX,EAX
L400:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET 10h
Note here that you send the message WM_QUIT on receiving the message WM_DESTROY. WM_QUIT causes the thread to exit the message loop, because GetMessage returns EAX=0 on this message.
Here is the code called on the message WM_CREATE to create the controls:-
CREATE_DLGWINDOW:
PUSH 12D
CALL GetStockObject     ;get ANSI_VAR_FONT
MOV EDI,EAX             ;keep font in edi
;******************* make 1st button
PUSH 0,[hInst],20h,[EBP+8h]   ;button id, parent
PUSH 28,70,112,54       ;ht, width, y-pos, x-pos
PUSH 50000000h+10000h   ;style CHILD+VIS+TABSTOP
PUSH 'Do nothing'
PUSH ADDR BUTTON_CLASSNAME
PUSH 0
CALL CreateWindowExA    ;make button, returning handle in EAX
PUSH EAX                ;push now for setfocus
PUSH 0,EDI,30h,EAX      ;no redraw, font, WM_SETFONT, handle
CALL SendMessageA       ;use it to write the text
CALL SetFocus
;******************* make 2nd button
PUSH 0,[hInst],22h,[EBP+8h]   ;button id, parent
PUSH 28,70,112,177      ;ht, width, y-pos, x-pos
PUSH 50000000h+10000h   ;style CHILD+VIS+TABSTOP
PUSH 'Finish'
PUSH ADDR BUTTON_CLASSNAME
PUSH 0
CALL CreateWindowExA    ;make button, returning handle in EAX
PUSH 0,EDI,30h,EAX      ;no redraw, font, WM_SETFONT, handle
CALL SendMessageA       ;use it to write the text
;****************************************
XOR EAX,EAX             ;return EAX=0 to make window
RET
Each button is a child window with the dialog window as its parent. Here you are using the BUTTON class, which means that the window procedure for these windows is internal to the system. This gives you button functionality for very little code. The styles TABSTOP (and if you needed it GROUP) normally reserved for dialogs, are used here to ensure proper navigation around the two buttons. All sizes are normal window sizes, not dialog units, which can change with the size of the user's chosen font size.
Here is the code called on the WM_ERASEBKGND message:-
PAINT_DLGWINDOW:
MOV EBX,ADDR CLIENT_RECT
PUSH EBX,[EBP+8h]
CALL GetClientRect
MOV EDI,[EBP+10h]       ;get the device context sent in wParam
PUSH 10h,4,EBX,EDI
CALL DrawFrameControl   ;draw a large button
PUSH 1,EDI              ;transparent
CALL SetBkMode
PUSH 12D
CALL GetStockObject     ;get ANSI_VAR_FONT
PUSH EAX,EDI
CALL SelectObject
ADD D[EBX],34D          ;
ADD D[EBX+4],34D        ;restrict text
SUB D[EBX+8],46D        ;to smaller rectangle
SUB D[EBX+0Ch],46D      ;
PUSH 10h,EBX,-1,ESI,EDI ;10h=multiline
CALL DrawTextA
RET
WM_ERASEBKGND is sent when the system processes the WM_PAINT message within its own DefWindowProc. Your window procedure ensured that WM_PAINT was sent to DefWindowProc (in fact that all unprocessed messages were sent to that API). In the same way as you have seen before when processing WM_PAINT DefWindowProc calls the API BeginPaint. Before that API returns it sends WM_ERASEBKGND to the window, sending also the device context. You could paint the client contents of the window during the WM_PAINT message itself but this code saves calls to BeginPaint and EndPaint.
In the paint procedure, the API DrawFrameControl is used to simulate a dialog simply by drawing a very large button, then DrawText is used to write the text in the window, with the background set to transparent. The child buttons are not written over because the system organises the painting of those and does not permit them to be obscured (you must use the style WS_CLIPCHILDREN when making the main simulated dialog window to ensure this).
Here is the code used on the WM_COMMAND message:-
PROCESS_SIMDLGCOMMAND:
MOV EAX,[EBP+10h]       ;get wParam
MOV EDX,EAX
SHR EDX,16D             ;get high order word
JNZ >L28                ;z=BN_CLICKED
CMP AX,22h              ;see if "finish" was clicked
JNZ >L28                ;no
PUSH [EBP+8h]
CALL DestroyWindow
L28:
XOR EAX,EAX
RET
The notification message BN_CLICKED is sent also if "Enter" is pressed when the focus is on one of the buttons. DestroyWindow sends the message WM_DESTROY to the window procedure.

Using Enable Window

Here is another way to make a window acting just like a dialog, this time using EnableWindow to disable the main window while the simulated dialog remains on the screen. Unlike in simulated dialog using new message loop there is no new message loop. Instead, the main message loop is used to send all messages intended for the simulated dialog. For this reason if a dialog's normal keyboard interface (TAB, ARROWS, ENTER etc.) is required, IsDialogMessage should be called in the main message loop giving the handle to the simulated dialog window.

The relevant breakpoint is SIMULATE_DLG2.
Here is the code:-

DATA SECTION
;
hSimDlg DD 0
BUTTON_CLASSNAME DB 'BUTTON',0
SIMDLG_CLASSNAME2 DB 'SD2',0
;
CODE SECTION
;
SIMULATE_DLG2:
CALL INITIALISE_WNDCLASS    ;get WNDCLASS structure in ebx & set values
MOV D[EBX+4],ADDR SimDlgProc2
MOV D[EBX+24h],ADDR SIMDLG_CLASSNAME2
PUSH EBX
CALL RegisterClassA     ;register SimDlgProc
PUSH 0,[hInst],0,0      ;no parent therefore appears on taskbar
PUSH 160,299            ;set height, width
PUSH 60,60              ;y-pos then x-pos
PUSH 90000000h+2000000h ;WS_POPUP+VISIBLE+CLIPCHILDREN
PUSH 'Dialog window'    ;title (for the taskbar)
PUSH ADDR SIMDLG_CLASSNAME2     ;registered class name
PUSH 0
CALL CreateWindowExA    ;make main window, returning handle in EAX
RET
Here is the code for the window procedure:-
SimDlgProc2:
;
PUSH EBP
MOV EBP,ESP
PUSH EBX,EDI,ESI        ;save registers as required by Windows
;now [EBP+8]=hWnd, [EBP+0Ch]=uMsg, [EBP+10h]=wParam, [EBP+14h]=lParam,
MOV EAX,[EBP+0Ch]       ;uMsg
CMP EAX,1h              ;see if WM_CREATE
JNZ >L316               ;no
PUSH 0,[hWnd]
CALL EnableWindow       ;disable main window
CALL CREATE_DLGWINDOW
JMP >L400
L316:
CMP EAX,2               ;see if WM_DESTROY
JNZ >L318               ;no
PUSH 1,[hWnd]
CALL EnableWindow       ;enable main window
PUSH [hWnd]
CALL SetActiveWindow
PUSH 0,[EBP+8],499h,[hWnd]    ;message 0/499
CALL PostMessageA       ;cause class to be removed upon return
JMP >L360
L318:
CMP EAX,111h            ;see if WM_COMMAND
JNZ >L322               ;no
CALL PROCESS_SIMDLGCOMMAND
JMP >L360
L322:
CMP EAX,14h             ;see if WM_ERASEBKGND
JNZ >L328               ;no
MOV ESI,ADDR SIMDLG_MESS2
CALL PAINT_DLGWINDOW
MOV EAX,1               ;ensure that it returns "TRUE"
JMP >L400
L328:
PUSH [EBP+14h],[EBP+10h],[EBP+0Ch],[EBP+8h]
CALL DefWindowProcA
JMP >L400
L360:
XOR EAX,EAX
L400:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET 10h
Note here an "own-user" message is sent to the main window procedure to close the SimDlgProc2 class (code not shown). See simulated dialog using new message loop for a full description of the code used here.