Visual Projects - Easy Code power mode






Choosing the Visual executable file option automatically tells Easy Code to associate its visual library that is required for the visual functionality.

There are two visual linking modes, static or dynamic. You choose the link modes by specifying one of them in the Project Properties. The dynamic option will build your application with the required visual library needed as a separate file (ECDll32.dll for 32-bit projects, or ECDll64.dll for 64-bit projects) that must be distributed with the application. The static option (default) will build your executable with the required library (ECLib32.lib for 32-bit projects, or ECLib64.lib for 64-bit projects) internally.

The Manifest check box specifies whether the new comon controls, available in Windows XP and later systems, will be actived. For more information, please see the Including a Manifest in the project topic.


EASY CODE VISUAL RESOURCE OBJECTS

Easy Code visual mode object resources, come in two groups: Windows and Controls.

Windows

Windows are containers, that is, they may have child controls inside them. Easy Code manages three type of window objects:

Window: Common windows used in small applications as main windows. A Window object may be modal if it is not the main window or an MDI child window.

DialogBox: Mostly used as modal windows to choose/change options for the main window application which creates them. A DialogBox object may be modeless too and may be used as the main window of an application.

MDIWindow: Used as the main frame window for MDI applications. These windows may have other MDI child windows inside them. An Easy Code visual project can only have one MDIWindow object, other words one framing window.

Controls

Controls are little windows placed inside Window and DialogBox objects. Also, they can be placed inside the three type of container controls: Group, Picture and Rebar. Easy Code manages several control objects (for a list of available controls see Control objects).


REMARKS: You do not need to worry about initializating Common Controls, "InitCommonControlsEx" API function, as Easy Code initializes that internally depending on the Common controls option of the Project Properties.



OWNER WINDOW

The top window for any control object, even if it is a child of a container control (like Group, Picture or Rebar), is the Window, MDIWindow or DialogBox object which contains all of them. The main container window is called the "Owner window". When owner windows receive the WM_CREATE message, all child controls will be created and available. If that owner window is an MDIWindow type object, the MDI style messenging client is already created and, if needed, you can get its handle by calling the GetMDIClient Easy Code method (please see MDI applications).

REMARKS: At any time you can get the owner window for a control object by calling the GetOwnerWindow Easy Code method.

The Easy Code visual libraries, ECLib32.lib or ECLib64.lib for static linking, and ECDll32.dll or ECDll64.dll for dynamic linking, are built in assembly language and manage the behavior of all of the objects built, including watching for tab stops, short cuts for menus, appearance, and text and colours for objects.



WINDOW PROCEDURES

All objects in a project, windows and controls, have properties (please see Object properties) which can modify their appearance and behavior. One of the most important property is Name, as it identifies the object to its owning window routine for Window, MDIWindow and DialogBox objects. Easy Code will initiate the required skeleton code routines/procedures for the resources as they are created. Inside those procedures you may intercept messages and write any the necessary code and feature. When compiling, Easy Code checks the corresponding window procedure for each Window, MDWindow and DialogBox object in the project and if any are missing or incorrectly named, an error is fired. All of them referrances MUST exist at compile time.

How does Easy Code know the correct name for each window procedure? It uses a naming convention that takes the object name plus the word "Procedure". Procedures for Window, MDIWindow or DialogBox objects, MUST have a case sensitive name which is formed by the name of the object (specified by its Name property) plus the word "Procedure". For example, for a window object named wndMain its window procedure name MUST be:

wndMainProcedure (case sensitive)

Also, procedures for Window, MDIWindow or DialogBox objects MUST take the classic four parameters.

For 32-bit applications:

hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

For 64-bit applications:

hWnd:QWORD, uMsg:QWORD, wParam:QWORD, lParam:QWORD

So, a 32-bit window procedure for a window object named wndMain will look something like this:

wndMainProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_CREATE
;...... ;Initialization code goes here... ;······
Return FALSE
.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hWnd
.If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP

While a 64-bit window procedure for a window object named wndMain will look something like this:

wndMainProcedure Proc hWnd:QWORD, uMsg:QWORD, wParam:QWORD, lParam:QWORD
	Mov hWnd, Rcx
	Mov uMsg, Rdx
	Mov wParam, R8
	Mov lParam, R9

	.If uMsg == WM_CREATE
;...... ;Initialization code goes here... ;······
Return FALSE
.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hWnd
.If Rax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP

IMPORTANT: Please note that all lines above are written in MASM syntax and all lines below are written in MASM syntax for 32-bit applications. Please take that into account in order to make the necessary syntax conversion when programming with other assemblers or when programming 64-bit applications, where all parameters must be 8-byte values (QWORD).

The above is the minimal code required for a window object procedure, but it does nothing else than creating and destroying the window (we will analyze the WM_CLOSE message later on). Return is an Easy Code macro, (see Easy Code macros), which makes the return value quicker and clearer.

For each message you want to process, add the corresponding code. For example, to process the WM_SIZE message add the following code:

wndMainProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_CREATE
;......
;Initialization code goes here... ;······
Return FALSE
.ElseIf uMsg == WM_SIZE
;...... ;Code for WM_SIZE message goes here... ;······
.ElseIf uMsg == WM_CLOSE Invoke IsModal, hWnd
.If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP

As you can see, the window procedure always returns FALSE or TRUE in order to work properly. When returning TRUE (any other value than 0), no further message processing will be performed and a cycle is complete. When returning FALSE, it will mean that the default window procedure will be called. This is the reason why the default return value, at the end of the procedure, is FALSE. All messages you do not process, are processed by the default window procedure, which is part of the Windows API, in order to avoid undesirable results. So do not change the default return value from FALSE. Also, when you process all your messages and you do not want further processing, return TRUE, but just for the messages you have processed. This rule has the following exceptions:

The WM_NCCREATE message ignores the return value and always continues the creation of the window.

The WM_CREATE message should return other value than -1 and like mentioned, TRUE or FALSE are recommended in order to continue the creation of the window. If it returns TRUE, the focus is set to the first control being able to get it (see the TabOrder property). Most of times, this is the more usual return value. If you set the focus to any other control while processing this message (SetFocus API function), it should return FALSE so that the focus can be set to the specified control. Finally, a return value of -1 will destroy the window before being shown.

The WM_DESTROY and WM_NCDESTROY messages are sent for final clean up, but they ignore the return value and destroy the window anyway.



STARTING A NEW VISUAL PROJECT

When you start a new visual project, a window with the name you specified is added. If you do not change it, that will be the main or startup window. Any one of the window in the project may be the startup window, that is, the window that will appear first and which is the main window of the application. The App object will return the handle of the main window through one of its members. At this point, you can build three type of applications:

A basic application with a main window (a Window object). Optionally, it may have other windows (modal or modeless) like, for example, a configuration window.

An MDI application with a main MDI Window frame (an MDIWindow object) and as many MDI child windows as needed. MDI Child windows are normal windows (Window objects), but with its MDIChild property set to TRUE. It may also have other windows (modal or modeless) like, for example, a configuration window. This type of project could be an Easy Code looking application and is how the MDI example is made.

A Dialog based application with a main window (a DialogBox object). Optionally, it can have other windows (modal or modeless) like for example, a configuration window. In fact, there is practically no difference to a basic application using a Window or a DialogBox object.

If you want to build a Dialog Box based application, add a DialogBox to project (Project-->Add Dialog box) and then remove the window created by default once selected (Project-->Remove [WindowName]). Then, remember to change the startup window to the just added DialogBox window in the Project Properties.



MDI APPLICATIONS

An MDIWindow object has really two windows, the MDI Frame and the MDI Client. The entire client area of an MDI window is the MDI Client, which contains all MDI childs. As we saw before, Easy Code has a method for retrieving the MDI client handle if necessary. Anyway, all messages are received through the MDI Frame window procedure and the handle to the MDI frame is the only valid handle for sending commands and any other operation. Even when creating MDI child windows at run time, the handle passed as the parent window should be that belonging to the MDI frame window, that is, the MDIWindow object.

When you want to build an MDI application, add an MDI window to project (Project->Add MDI window). Easy Code will change the main (startup) window to the new added MDI window. Then, you can start adding or removing other windows as you need. Remember that for a Window object to be an MDI child window, its MDIChild property must be set to TRUE.

MDIWindow objects do not accept any other child controls than Picture, ToolBar, StatusBar and Rebar objects (those objects are inside the frame, not the client), so you can add a ToolBar or a StatusBar. Also, a Picture control with some other controls inside may be added, but usually, in that kind of applications, MDI child windows have all needed controls and do all work. When an MDI child window becomes active, its menu (if any) is set to the parent (the MDI frame window which is an MDIWindow object). If the active MDI child window has no menu, the MDI frame window keeps its own menu (if it has one). Although this is the default and the most appropiate behavior for MDI applications, you can override it for any MDI child window just by intercepting its WM_MDIACTIVATE message and returning TRUE. Let's supose the MDI child window is named wndMDIChild, so its procedure name MUST be wndMDIChildProcedure. Just add the following code (added code in bold font):

wndMDIChildProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_CREATE
;......
;Initialization code goes here... ;······
Return FALSE
.ElseIf uMsg == WM_MDIACTIVATE
Return TRUE

.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hWnd .If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMDIChildProcedure EndP

These two lines of code will avoid the menu default behavior for that MDI child window, but its menu (if it has one) will never appear and so, you will not be able to use it. Windows O.S. allows child windows (including MDI child ones) to have a menu, but they can never display it, that is, make it visible.

On the other hand, and in order to avoid some unexpected behavior from an MDI application, take into account the following considerations:

When an MDI frame window gets the menu of the active MDI child, the window which receives commands from that menu is the MDI frame window (MDIWindow object) not the MDI child which the menu belongs to. These commands are sent through the WM_COMMAND message.

On the WM_COMMAND message for the MDIWindow main window, always set the return default value to FALSE. You may return TRUE when, for example, you processed a menu command, but if you always return TRUE for the WM_COMMAND message, unexpected behavior of the MDI child windows may occur.

When processing the WM_SIZE message for an MDI child window, you should ALWAYS return FALSE. If you return TRUE, you will not be able to restore that MDI child window after maximizing it.

Try to remember these precautions. Sometimes, when writing code, it is easy to change the return value or forget any of the rules seen above. As a result, your MDI application may not work properly or start behaving in a strange way.



ADDING CHILD CONTROLS

Well, now you have an empty window, that can be built without errors, but does nothing. So, start designing your application by adding child controls to the window(s). To add a control, click on the corresponding button in the tool box (the cursor shape over the window object will change to a cross). Then click the mouse left button on the window object, keep the left button down while dragging over until the control object has the desired size and release the left mouse button. Remember that Group, Picture and Rebar objects are containers too, so you can drop other control objects into them.

Also, you can add a child control just by double-clicking on its corresponding button in the tool box. In that case, take into account that if the active object is a Group, a Picture or a Rebar, the added control will be a child of it. Otherwise, the control will be a child of the window object.

Easy Code also adds the corresponding window procedure for each control added to a window object (or a Group, Picture or Rebar object). In most cases, the WM_COMMAND and WM_NOTIFY messages, received by the owner window of the control, will be enough. If so, you can delete the object procedures for those controls you do not need to process (procedures for child controls do not have to exist necessarily). When Easy Code does not find the corresponding procedure name for a control object, it simply ignores it and then you do not receive messages for the referred object (except those sent to the owner window by WM_COMMAND, WM_NOTIFY and WM_DRAWITEM). On the other hand, when you do want to process messages for any control object, its procedure have to exist and its name must fit the following convention:

Procedures for control objects (any of them) MUST have a case sensitive name which is formed by the name of owner window plus its own object name.

For example, for a Static control named stcLabel which is inside a window object named wndMain, its procedure name MUST be:

wndMainstcLabel (name is case sensitive)

Besides, procedures for control objects MUST also have the classic four parameters:

hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

So, the procedure for a Static control named stcLabel which is inside a window object named wndMain will look like this:

wndMainstcLabel Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
Return FALSE
wndMainstcLabel EndP

This control procedure name convention is common to any other control object you add, and it is the same whether the control is placed directly inside the owner window or inside any other container control (Group, Picture or Rebar).

This is the minimal code needed for any control object procedure. Anyway, it does nothing, so if you are not going to process any specífic message for that control, you can delete its whole procedure.

If the name for a control object procedure does not exist or is not correct, Easy Code does not generate any error but, you WILL NOT RECEIVE other messages for that control than WM_COMMAND and WM_NOTIFY (and also WM_DRAWITEM if the control is owner draw) through its owner window. On the other hand, as said for window objects, if you process messages you should return TRUE for no further message processing, or FALSE for the default window procedure to be called (NEVER change the default return to any other value than FALSE). For any message to be processed, add the corresponding code. For example, if you want to process the WM_SETFOCUS message for a control object named stcLabel which is inside a window object named wndMain, write the following code:

wndMainstcLabel Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_SETFOCUS
;...... ;Code for WM_SETFOCUS message goes here... ;······
Return TRUE ;(or FALSE, you decide) .EndIf
Return FALSE wndMainstcLabel EndP

In this example, the return value for the WM_SETFOCUS message is TRUE. If you are going to return FALSE, just write no return sentence as the code will return FALSE (default return value) when reaching the end of the procedure:

wndMainstcLabel Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_SETFOCUS
;...... ;Code for WM_SETFOCUS message goes here... ;······
.EndIf
Return FALSE
wndMainstcLabel EndP


REMARKS: As said before, Easy Code always writes the corresponding procedure for each control added to the project. In most cases this procedure is not needed at all, as all the work can be done just by processing the WM_COMMAND and WM_NOTIFY messages sent to the owner window. So, if you do not have to process any specific message for a control object, you can delete its whole procedure (this saves some bytes in the final executable file).



CUSTOMIZING OBJECTS

When adding ToolBar, StatusBar, TabStrip, ImageList, Header or Rebar objects, you will be able to customize them by clicking on its Custom property (in the Properties window), which displays the corresponding customizing window. For more information, see the following topics:

Customizing ToolBar objects
Customizing StatusBar objects
Customizing TabStrip objects
Customizing ImageList objects
Customizing Header objects
Customizing Rebar objects



GETTING CHILD CONTROL IDENTIFIERS

At run time, all child controls you added in each window object are already created and ready to use when receiving the WM_CREATE message of the window they belong to, that is, the owner window (no children yet in the WM_NCCREATE message). Each child control has an identifier or ID, with its corresponding constant name (upper case). To refer to any control using this constant, once again, you should take into account a simple rule. The constant name is all upper case and is formed by 'IDC_' plus the name of the owner window, plus '_', plus the control object name. For example, the ID constant name for a Static control named stcLabel, belonging to a window named wndMain, will be:

IDC_WNDMAIN_STCLABEL

In most cases, the owner window and the parent of the control object are the same (i.e. when you place a control object directly on the window object), but as we saw before, there are three type of controls (Group, Picture and Rebar) which are containers and may have child controls inside them. When that is the case, it DOES NOT AFFECT the ID constant name! It continues being formed by IDC_, plus the owner window name (not its parent, if any other), plus '_', plus its own name, what makes your work easier not having to remember which is parent of which.

With these constant names, it is really easy to refer to any control along the code. This is accomplished by using the GetWindowItem Easy Code method. This method has two parameters, the handle to the owner window (or any other control object being inside) and the constant name. For example, inside a window procedure (which the referred control belongs to):

wndMainProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg ==
WM_CREATE
Invoke GetWindowItem, hWnd,
IDC_WNDMAIN_STCLABEL
; Handle to the 'stcLabel' Static control in
; the Eax/Rax register. Do something with it
Return FALSE
.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hWnd
.If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP

After the call to the GetWindowItem method, the Eax/Rax register contains the handle to the referred control. If it is a child of any container control (Group, Picture or Rebar) or several of them (i.e. a control object which is a child of a Group, which is a child of a Picture, which is a child of another Picture, etc.), the first argument for the GetWindowItem method may be directly the handle to the owner window or the handle to any other control object inside the owner window. That makes very easy to refer to any control inside the window procedure of another control (always, of course, belonging to the same owner window). For example, supose we have another child control in the same window (wndMain), which is an Edit object named edtEdit, and inside its control procedure (which correct name MUST be wndMainedtEdit) we want to do something with that Static control named stcLabel:

wndMainedtEdit Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg ==
WM_LBUTTONDOWN
Invoke GetWindowItem, hWnd,
IDC_WNDMAIN_STCLABEL
; Handle to the 'stcLabel' Static control in ; the Eax/Rax register. Do something with it Return TRUE
.EndIf
Return FALSE wndMainedtEdit EndP

As any of the child controls inside the owner window is a valid handle for the first argument of the GetWindowItem method, the edtEdit control handle will be quite right. When the referred control belongs to another window, you can use the same method in the same way. In that case, the first argument will be the handle to the other window or any of its child controls (you must know some handle). When that other window is the main window, you can easily get its handle from App.Main. For example, to get the handle to a control named stcLabel belonging to the main window from the window procedure of and Edit control named edtEdit (belonging to the main window or any other window in the project):

wndMainedtEdit Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_LBUTTONDOWN
Invoke GetWindowItem, App.Main, IDC_WNDMAIN_STCLABEL
; Handle to the 'stcLabel' Static control in
; the Eax/Rax register. Do something with it
Return TRUE
.EndIf
Return FALSE wndMainedtEdit EndP


REMARKS: You have to be careful when passing the first parameter to the GetWindowItem method. Passing a wrong handle (i.e. a handle to another window or to a child control belonging to another window) will result in a wrong returned handle (the handle to a child control from another window) or NULL.



TESTING WINDOWS

After adding some child controls and modifying their properties, you may want to test the window. If so, click the main menu choose Build->Test <WindowName> or press <Shift+F5>. The window being tested will be the window object which is active, and it will be shown modally over the Easy Code application. When testing a window no code is processed, but you can see how it will look like at run time, viewing menus and string resources, and navigating through child controls with the <Tab> key, in order to check the tab order. To close a window which is being tested, press the <Esc> key.



THE WM_COMMAND, WM_NOTIFY, WM_DRAWITEM MESSAGES

Control objects send messages to their parent. As we saw above, a control object may be a child of a window object or of another control which is a container (Group, Picture or Rebar objects), or even be nested inside several container controls. In that case, which object receives the WM_COMMAND, WM_NOTIFY and WM_DRAWITEM messages? In order to make quite easy to write code quickly (not having to remember which is parent of which) those messages are ALWAYS received by the owner window. So, your code should be placed there, in the WM_COMMAND, WM_NOTIFY or WM_DRAWITEM message of the owner window procedure which the control belongs to.



CREATING WINDOW OBJECTS AT RUN TIME

You can design several window objects in your project. When your application starts, and along all time is running, you may need to create dynamically other windows than the main window in response to user demands. All other windows which were designed at design time are inside the executable file ready to be used when needed. To create them, use the Create method. This method returns either the handle to the new created window (if modeless) or the return value (if modal) in the Eax/Rax register. It has the following syntax:

Invoke Create, lpszWindowName, hWndParentWindowHandle, lMode, lParam

The lpszWindowName parameter is a DWORD value which must be a pointer to the effective address of a null-terminated string containing the window object name (that specified by its Name property, case sensitive).

The hWndParentWindowHandle parameter is a DWORD value with the handle to the window being the parent. This value can be NULL (except for MDI child windows) if the created window is going to have no parent.

The lMode parameter is a DWORD value specifying how the window is to be shown, modal or modeless.

The lParam parameter is the DWORD value passed to a dialog box in the lParam parameter of the WM_INITDIALOG message. This value is only for DialogBox objects, modal or not, and it may be NULL if not needed.

Easy Code has two constants for the lMode parameter, ecModal and ecModeless. In fact, their values are, respectively, 1 (TRUE) and 0 (FALSE), so any other value than 0 will mean TRUE, that is, a modal window. To avoid undesired errors, always use the ecModal and ecModeless constants.

A Window (having its MDIChild property set to FALSE) and a DialogBox object may be modal or modeless, depending on the third parameter of the Create method, while a Window (having its MDIChild property set to TRUE) and an MDIWindow object can only be modeless, so the third and fourth parameters are ignored. In fact, the third parameter (lMode) is only valid for Window and DialogBox objects, while the fourth parameter (lParam) is only valid for DialogBox objects.

In MDI applications, Easy Code has the GetMDIClient method for retrieving the handle to the MDI client. Anyway, when creating MDI child windows, the handle you should pass to the Create method as the parent window (second parameter) is that belonging to the MDI frame window (the MDIWindow object), not that returned by the GetMDIClient method.

Well, now let's supose we have a project with the following objects (object names are shown in blue font):

A main Window object named wndMain and a DialogBox object named dlgOptions. Inside the main window (wndMain), we have a Button object named btnShow. We like to show the DialogBox (dlgOptions) modally when cliking the Button control (btnShow), and the main window (wndMain) to be the parent. In the DialogBox object (dlgOptions), we have two Button objects named btnOK and btnCancel, which both close the modal dialog box (btnOK closes validating and btnCancel closes cancelling).

The name for the main window procedure must be wndMainProcedure.
The name of the DialogBox object to be created is dlgOptions.
The control ID for the Button control (btnShow) must be IDC_WNDMAIN_BTNSHOW (because it belongs to wndMain object and its name is btnShow).

The name of the window procedure for the dialog box must be dlgOptionsProcedure.
The control ID for the Button control (btnOK) must be IDC_DLGOPTIONS_BTNOK (because it belongs to dlgOptions object and its name is btnOK).
The control ID for the Button control (btnCancel) must be IDC_DLGOPTIONS_BTNCANCEL (because it belongs to dlgOptions object and its name is btnCancel).

The owner window is the object which receives the WM_COMMAND messages sent by any of its child controls (in this example the btnShow button click messages). So, inside the WM_COMMAND message of the main window procedure (wndMainProcedure), we'll write the following code:

wndMainProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_CREATE
Invoke GetWindowItem, hWnd, IDC_WNDMAIN_STCLABEL
; ....
; Initaialization code goes here
; ....
Return FALSE
.ElseIf uMsg == WM_COMMAND
LoWord wParam .If Ax == IDC_WNDMAIN_BTNSHOW
HiWord wParam
.If Ax == BN_CLICKED
Invoke Create, TextAddr("dlgOptions"), hWnd, ecModal, NULL
Return TRUE
.EndIf
.EndIf
.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hWnd .If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP


while inside the WM_COMMAND message of the dialog box (dlgOptionsProcedure) we'll write the following code:

dlgOptionsProcedure Proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_INITIDIALOG
Invoke GetWindowItem, hWnd, IDC_WNDMAIN_STCLABEL
; Here you have lParam set to the value passed
; to the Create method as the fourth parameter
;...... ; Initialization code goes here... ;······ Return FALSE
.ElseIf uMsg == WM_COMMAND
LoWord wParam
.If Ax == IDC_DLGOPTIONS_BTNOK
HiWord wParam .If Ax == BN_CLICKED
Invoke EndModal, hDlg, IDOK
Return
TRUE .EndIf
.ElseIf Ax == IDC_DLGOPTIONS_BTNCANCEL
HiWord wParam .If Ax == BN_CLICKED
Invoke EndModal, hDlg, IDCANCEL
Return
TRUE
.EndIf
.EndIf
.ElseIf uMsg == WM_CLOSE
Invoke IsModal, hDlg .If Eax
Invoke EndModal, hDlg, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
dlgOptionsProcedure Endp

LoWord and HiWord are Easy Code macros which return, respectively, the low word and high word (16-bit values or Word) of a 32-bit value (DWord) which is passed as an argument (in this example wParam). The 16-bit returned value is in the Eax/Rax register (in fact, the value is the Ax register). TextAddr is another Easy Code macro which creates a local and temporary text string (in this example containing the text "dlgOptions", which is the name of the DialogBox object). No final NULL is needed for the string, as it is added internally by TextAddr macro. Finally, in the WM_COMMAND message of the owner window which receives the button clicks from btnOK and btnCancel (the DialogBox object), we call another Easy Code method, EndModal, which closes (destroys) a modal window and returns the specified value in the Eax/Rax register (in the example above IDOK or IDCANCEL). The EndModal method has two parameters, the handle to the window to be destroyed and the return value. This method returns TRUE or FALSE, in the Eax/Rax register, depending on whether the window was destroyed or not (i.e. if you call the method for a window object which is not modal, it will do nothing and will return FALSE), and may be called from any part of the code. Once is called, the modal window will be definitively destroyed. That is why we should return TRUE after calling the EndModal method, so that there is no further processing for the WM_CLOSE message.

Another way of closing a modal window with IDCANCEL as the return value, is sending a WM_CLOSE message. In the example above, the code for the cancel button (btnCancel) inside WM_COMMAND message in dialog box (dlgOptions) procedure could be changed to the following code (changed text in bold font):

	.ElseIf uMsg == WM_COMMAND
LoWord wParam
.If Ax == IDC_DLGOPTIONS_BTNOK
HiWord wParam .If Ax == BN_CLICKED
Invoke EndModal, hDlg, IDOK
Return
TRUE .EndIf
.ElseIf Ax == IDC_DLGOPTIONS_BTNCANCEL
HiWord wParam .If Ax == BN_CLICKED
Invoke SendMessage, hDlg, WM_CLOSE, 0, 0 Return TRUE .EndIf
.EndIf .ElseIf uMsg == WM_CLOSE
Invoke IsModal, hDlg
.If Eax
Invoke EndModal, hDlg, IDCANCEL
Return TRUE
.EndIf
.EndIf

Note that after sending the WM_CLOSE message, you must return TRUE for no further processing of the WM_COMMAND message, as the window has already been destroyed (it does not exist) and the application could crash if the message continues being processed. Also, when processing the WM_CLOSE message for a modal window, and only if the window is modal, you can remove some code:

	.ElseIf uMsg == WM_CLOSE
Invoke EndModal, hDlg, IDCANCEL
Return TRUE
.EndIf
Return FALSE

And that is all you need to create a window oject at run time which was designed at design time. Quite easy, isn't it?

IMPORTANT: The EndModal method only works for modal windows. If you invoke it with a handle belonging to a non-modal window, the method will return FALSE and the window will not be destroyed. On the other hand, after calling the EndModal method with a valid handle (a modal window handle), the window is already destroyed, so you have to return TRUE for no further processing as the object does not exist. Otherwise, errors might occur.



The WM_CREATE message

All child controls you added to a window at design time are already created and available when it receives the WM_CREATE message (use their constant ID for any operation). As said before, this message must return other value than -1 in order to continue the creation of the window (a return value FALSE or TRUE are quite right). If it returns TRUE, the focus is set to the first control being able to get it (see the TabOrder property). Most of times, this is the more usual return value. On the other hand, if you set the focus to any other control while processing this message, (SetFocus API function), it should return FALSE so that the focus can be set to the specified control. Finally, a return value of -1 will destroy the window before being shown.

REMARKS: Dialog boxes do not receive the WM_CREATE message. Instead, they receive the WM_INITDIALOG message with the lParam parameter set to the value which was passed to the Create method as the fourth parameter. This parameter may be NULL if not needed.



The ECM_AFTERCREATE message

Just after creating a window, and before being shown, Easy Code sends the ECM_AFTERCREATE message to the window procedure. This message can be useful to make some kind of initialization or change that could not be performed during the WM_CREATE message. At this time, the window and all of its children are completely created and ready to be shown. The ECM_AFTERCREATE message is Easy Code exclusive, its value is WM_USER + 1049 and its sintax is automatically corrected to upper case by the IDE.

REMARKS: The return value for this message specifies whether the window has to be destroyed or not. If this value is -1, the window will be destoyed before being shown, while any other value will make the process to be continued.



The WM_CLOSE message

A Window (non MDI child) and a DialogBox object may be modal or modeless. When creating a modal window, its parent (if any) is disabled and has no control until the modal window is closed. At this point, we expect a return value to tell us whether the user validated or cancelled whatever inside the modal window. This value (usually IDOK or IDCANCEL) is returned in the Eax/Rax register when the Create method returns, and it is the value passed to the EndModal method when the modal window was destroyed.

On the other hand, when creating modeless (non modal) windows, the Create method returns immediately with the handle to the new created window in the Eax/Rax register. If the window could not be created for some reason, i.e. the name does not exist (remember that object names are case sensitive), then the returned value will be NULL.

In both cases, you can avoid the window to be destroyed by returning TRUE in the WM_CLOSE message (i.e. if you ask for confirmation), while returning FALSE will close (destroy) the window normally. In modal windows, any confirmation must be asked before calling the EndModal method, as after calling it the window is already destroyed. So, all code for avoiding to close and destroy a window object should be placed in the WM_CLOSE message. For example, if you asked for confirmation and the user answered no, just return TRUE. For modal windows, also return TRUE but without calling the EndModal method.

When adding Window and DialogBox objects to a project, as they both may be modal windows or not, Easy Code adds the necessary code in the WM_CLOSE message for that window to close properly whether it is modal or not. This code (in bold font) is the following:

wndMainProcedure Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_CREATE
;...... ;Initialization code goes here... ;······
Return FALSE
	.ElseIf uMsg == WM_CLOSE
		Invoke IsModal, hWnd
.If Eax
Invoke EndModal, hWnd, IDCANCEL
Return TRUE
.EndIf
.EndIf
Return FALSE
wndMainProcedure EndP

When closing the window, another Easy Code method is called, IsModal, which returns TRUE or FALSE in the Eax/Rax register, depending on whether the window object is a modal window or not. When it is modal, the EndModal method must be called in order to close properly, while when it is modeless, the window closes normally by returning FALSE.

REMARKS: The WM_CLOSE message is the last chance to avoid the window to be destroyed. After that, you will receive the WM_DESTROY and WM_NCDESTROY messages, but just for cleaning up. At the time of these last two messages, there is nothing to do. The window is being destroyed!

IMPORTANT: Be careful! Returning always TRUE for a WM_CLOSE message, will mean that the window will never be destroyed. If it is a modal window, and you do not call the EndModal method, your application will never be able to exit in the right way.



DIALOG BOXES

Dialog boxes are frequently used in Windows applications to display or require information to or from the user. They can be modal or modeless and are created from a template usually being in the program's resource script file. To calculate the dimensions for a dialog box, Windows uses the average height and width of the dialog's font. All child controls being inside a dialog box use the same font than their parent (the dialog box), so they cannot have its own font as in any other window. Using just a font for all objects, allows the dialog box and all of its children to be always proportionally shown in different screen resolutions and font sizes. When changing the Font property for a dialog box at design time, it and all its child controls are resized according to the new font size, while changing its font at run time just changes the font, but no resizing is performed.

Visual projects have their own dialog box, the DialogBox object, which offers some more features than the dialog box in classic projects. All DialogBox objects needed by an application, being designed in the visual environment (design time), can be created at run time by calling the Create method. Once created (both modal or modeless), a DialogBox object does not receive a WM_CREATE message, instead it receives a WM_INITDIALOG message, previous to show the window, where all necessary code can be written. When receiving this message, the lParam parameter is that passed as the fourth argument to the Create method. This parameter can be any value you need to pass to the DialogBox, i.e. the address of a data structure, or it can be NULL if not needed.

The return value for a WM_INITDIALOG message has a special meaning (also applied in the WM_CREATE message for Window objects). It is the following:

If it returns TRUE, the focus is set to the first control being able to get it (see the TabOrder property). Most of times, this is the usual return value.

If you set the focus to any other control during this message, by calling the SetFocus API function, you should return FALSE so that the focus can be set to the specified control.

When destroying a modal window (being a DialogBox or a Window object), you should call the Easy Code EndModal method. This is the right way to destroy a modal window. NEVER call the DestroyWindow API function if the window to be destroyed is a modal window.



PROCESSING IDLE TIME

When the application has no messages to process, it is said to be idle and does nothing else than waiting for a new message. You can take advantage of this idle time in order to perform some background processing. To do so, just write the following procedure:

OnIdle Proc lCount:DWORD
Return FALSE
OnIdle EndP

The name for this procedure MUST be OnIdle (case sensitive), it has to be in the main window and it will be called when there are no messages to process. The lCount parameter is incremented each time OnIdle is called and is reset to 0 each time a new message is processed, so you can know when a new idle cycle has started as lCount will be set to 0. You can call different idle routines based on this count:

OnIdle Proc lCount:DWORD
.If lCount == 0
; Do some task
.ElseIf lCount == 1
; Do some other task
.ElseIf lCount >= 50
Return FALSE
.EndIf Return TRUE
OnIdle EndP

This procedure should return TRUE in order to receive more idle processing time. If it returns FALSE, it will not be called during the current cycle, that is, OnIdle will not be called until the next idle cycle.

It is very important to take into account the behavior of the OnIdle procedure in order to avoid overloadng the processor. If this procedure always returns TRUE, the processor will be permanently overloaded, so it is a good practice to perform different small tasks until lCount reaches a certain value (50 in the example above).


REMARKS: The OnIdle procedure is used to perform simple tasks in the background. Lengthy tasks should be splitted into many small routines which would be sequentally called by using the lCount parameter.



ACCESSING THE WINDOWS WinMain FUNCTION (WHEN THE APPLICATION STARTS AND ENDS)

You can also have control when the application starts and/or when the application ends (from the beginning and the end of the WinMain function), just by adding, respectively, the MainStart and/or the MainEnd procedures (case sensitive) in the .Code section of the Main window (startup window) of the application:

MainStart Proc hInstance:DWORD, hPrevInst:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
; Write your code here
Ret
MainStart EndP
MainEnd Proc hInstance:DWORD, hPrevInst:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
; Write your code here
Ret
MainEnd EndP

When the WinMain function begins, it calls the MainStart procedure (if existing) and when the WinMain function ends, it calls the MainEnd procedure (if existing). You should be very careful with the code you write in those two procedures (specially the first one) since you could make the application to crash. For both procedures, the return value is irrelevant.



CONTROLLING THE APPLICATION MESSAGE LOOP

In most cases it is quite enough to process messages in object procedures. Anyway, if you need more control and want to check messages before being processed, add the following code in the .Code section of the main window of the application.

ProcessMessages Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
Return FALSE
ProcessMessages EndP

The name for this procedure MUST be ProcessMessages (case sensitive) and it is directly called from the application message loop before than any other API function. If ProcessMessages exists, that is, if you write its code, it MUST have the specified name and be in the main window, so that Easy Code can call it at each message.

ProcessMessages allows you to check the destination object (hWnd), the message (uMsg) and the two message parameters (wParam and lParam). If you return TRUE for a message, it will never be processed. As you can see, this procedure gives you a full control over the application. Supose you do not want the WM_KEYDOWN message to be processed for a Static object named stcLabel, belonging to the main window of the application (named wndMain). The ID constant name for the stcLabel object will be IDC_WNDMAIN_STCLABEL (because it's inside wndMain and its name is stcLabel).

Just write the following code:

ProcessMessages Proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.If uMsg == WM_KEYDOWN
Invoke GetWIndowItem, App.Main, IDC_WNDMAIN_STCLABEL
; Handle to 'stcLabel' in the Eax/Rax register .If Eax == hWnd

Return TRUE
.EndIf
.EndIf
Return FALSE
ProcessMessages EndP

In the example above, we can easily retrieve the handle to stcLabel because it is inside the main window (App.Main always stores the handle to the main window). Otherwise, you should know the owner window for the control to be checked.

In most applications this procedure is not needed at all. As you decide which is the main window (where this procedure has to be in) and whether you are going to use ProcessMessages or not, Easy Code never adds it by default.

IMPORTANT: Be very careful with this procedure and never change the default return value FALSE. As returning TRUE means that the message is not processed (ignored), if the procedure always returns TRUE (that is, any other value than FALSE), it will mean that no message will be processed and your application will crash.



PRESERVING REGISTERS IN VISUAL PROJECTS

When working with visual projects, you can freely use Ebx, Ecx, Edx, Edi, and Esi (or Rbx, R10, R11, Rdi, and Rsi for 64-bit projects) registers along all procedures in the project without preserving them (except for your own convenience when you need to preserve any), as Easy Code takes care of them. On the other hand, when calling any Easy Code method, you can rely on none of those registers will be changed.



OBJECTS - PROPERTIES AND METHODS

Each object, window or control, has properties which modify its appearance and behavior. Modifiying those properties and moving and sizing controls, you can easily design an application in the Easy Code development environment. Along all the code, inside the procedures of any object, you can modify properties and call methods. What you write there will be executed at run time (when the application is running). To do so, Easy Code has several methods to get/set useful information from/to objects and perform several operations. You have to get familiar with all of those properties and methods, and programming a big 32-bit Windows application in assembler will be easy, quick and possible.

Object properties
Getting and setting properties
Easy Code methods

To illustrate everything said here, have a good look to the examples which come with the Easy Code application (located in the Examples subfolder of the Easy Code directory). Almost all of the example applications are visual projects, are entirely programmed with Easy Code and use all programming style we saw in this chapter.

REMARKS: In order to get into the 64-bit complex world, have a look at the diferences between the same example projects for 32-bit and 64-bit.