This tutorial will give a simple introduction to the usage of the Eiffel WEL library. We will build a simple calculator in 3 easy steps. The method employed here will make exclusive use of pure WEL Eiffel code only i.e no external dependency on third-party programs such as resource editors. The calculator interface will consist of a main window, a menu, an about dialog box and a dialog box for choosing an operator for the calculation of the entered operands. A simple multi-line text field will allow the user to edit comments and save them to a text file. Optionally, the multi-line text field can also be filled with the contents of a text file. The main window will appear as follows:
In each step, I'll first provide the complete source code for the class in question and then discuss line by line the logic of what is going on. The ace file for the calculator application is here.
Note: The ace file makes use of the multi-threaded precompiled WEL classes.
In this step, we will build the main window of the calculator.
Every program in Eiffel as you know has a main root class. This class is responsible for initializing the whole system. Similarly, every windows application build using WEL has a root class. However, the restriction is that this root class MUST descend from the deferred class WEL_APPLICATION. We will call our root class WEL_CALCULATOR. The source for WEL_CALCULATOR is as follows:
classWEL_CALCULATOR
inherit
WEL_APPLICATION
creation
make
feature
main_window: WEL_FRAME_WINDOW is
-- Create the application's main window.
once
!! Result.make_top ("WEL Calculator")
end
end -- class WEL_CALCULATOR
The creation feature 'make' for our root class is simply inherited from class WEL_APPLICATION. The feature 'main_window' above is declared as a deferred feature of class WEL_APPLICATION and is of class type WEL_COMPOSITE_WINDOW . This feature contains a reference to the application's main window and so MUST be made effective in all descendent classes of class WEL_APPLICATION. Class WEL_FRAME_WINDOW, a descendent of deferred class WEL_COMPOSITE_WINDOW encapsulates a generic window with a system menu (the one you get when you click the upper left hand corner icon of a window), a close, maximize/minimize and dock buttons (all on the upper right hand corner of a window). Class WEL_FRAME_WINDOW also contains a creation feature called 'make_top'. We make the deferred feature 'main_window' effective above simply by creating an instance of WEL_FRAME_WINDOW by calling the creation feature 'make_top'. The parameter passed is the title of the window (in this case 'WEL Calculator'). Note that the effective 'main_window' feature of class WEL_CALCULATOR above changes its class type from WEL_COMPOSITE_WINDOW as declared in ancestor class WEL_APPLICATION to class type WEL_FRAME_WINDOW. This is valid as per Eiffel's type conformance rules. Also note that feature 'main_window' is a once feature. Obviously, the main window should only be created once upon startup. The above listing produces a generic window like this:
This generic window is not awfully useful. To create a customized main window, what we have to do is descend from the generic class WEL_FRAME_WINDOW and override it's default behaviour. We will create a descendant of WEL_FRAME_WINDOW called CALCULATOR_MAIN_WINDOW. The features we will override are the ones responsible for the dimensions of the window i.e the width and the height of the window and the window background color. The resultant code for class CALCULATOR_MAIN_WINDOW is:
classCALCULATOR_MAIN_WINDOW
inherit
WEL_FRAME_WINDOW
redefine
class_background,
default_width,
default_height
end
creation
make
feature -- Initialization
make is
-- Make the main window
do
make_top ("WEL Calculator")
end
feature {NONE} -- Implementation
class_background: WEL_LIGHT_GRAY_BRUSH is
-- Light gray background
once
!! Result.make
end
default_width: INTEGER is
do
Result := 400
end
default_height: INTEGER is
do
Result := 210
end
end -- class CALCULATOR_MAIN_WINDOW
This class simply redefines the features 'default_width', 'default_height' and 'class_background' from the ancestor class WEL_FRAME_WINDOW. The 'default_width' and 'default_height' features are pretty much self-explanatory. Feature 'class_background' is declared as class type WEL_BRUSH in ancestor class WEL_FRAME_WINDOW. Because class WEL_LIGHT_GRAY_BRUSH is a descendent of WEL_BRUSH, our redefinition of feature 'class_background' as class type WEL_LIGHT_GRAY_BRUSH is type conformant to the original declaration. Feature 'class_background' simply returns an instance of class WEL_LIGHT_GRAY_BRUSH which encapsulates a gray shade brush. Creation feature 'make' above simply calls creation feature 'make_top' of ancestor class WEL_FRAME_WINDOW to initialize the main window with the title 'WEL Calculator'. Because we redefined class WEL_FRAME_WINDOW and created a custom class CALCULATOR_MAIN_WINDOW, our root class WEL_CALCULATOR redefines feature 'main_window' of class type WEL_COMPOSITE_WINDOW as declared in ancestor class WEL_APPLICATION to class type CALCULATOR_MAIN_WINDOW. The redefinition of feature 'main_window' from class type WEL_COMPOSITE_WINDOW to class type CALCULATOR_MAIN_WINDOW is valid as per Eiffel type conformance rules. The resultant root class is:
classWEL_CALCULATOR
inherit
WEL_APPLICATION
creation
make
feature
main_window: CALCULATOR_MAIN_WINDOW is
-- Create the application's main window.
once
!! Result.make
end
end -- class APPLICATION
Note the redefinition of 'main_window' and how the implementation of this feature differs from the previous listing of this root class. The above code changes results in a window that looks like this:
Whew, we are almost done with creating the main window :-) What is left now is to add two single line edit controls for the user to be able to enter the two operands, another single line edit control to display the resultant value of the calculation, a multi-line edit control to enter comments, and a button to compute the result. This part is really much simpler than it sounds. The modified class CALCULATOR_MAIN_WINDOW is:
classCALCULATOR_MAIN_WINDOW
inherit
WEL_FRAME_WINDOW
redefine
class_background,
default_width,
default_height,
on_control_id_command
end
creation
make
feature -- Initialization
make is
-- Make the main window
do
make_top ("WEL Calculator")
!!edit_cntrl.make(Current,"",10, 5, 150, 25, -1)
!!edit_cntrl2.make(Current,"",10, 35, 150, 25, -1)
!!compute_btn.make(Current,"Compute -->", 10, 65, 150, 40, Id_compute_btn)
!!result_cntrl.make(Current, "",10,110,150,25,-1)
!!comment_edit.make(Current,"", 175, 5, 200, 145, -1)
end
feature {NONE} -- Implementation
Id_compute_btn: INTEGER is unique
edit_cntrl, edit_cntrl2, result_cntrl: WEL_SINGLE_LINE_EDIT
comment_edit: WEL_MULTIPLE_LINE_EDIT
compute_btn: WEL_PUSH_BUTTON
class_background: WEL_LIGHT_GRAY_BRUSH is
-- Gray background
once
!! Result.make
end
default_width: INTEGER is
do
Result := 400
end
default_height: INTEGER is
do
Result := 210
end
on_control_id_command(control_id: INTEGER) is
do
end
end -- class CALCULATOR_MAIN_WINDOW
Note: A control in the following discussion is a synonym for a window (albeit a small one). Examples include buttons, edit boxes, list boxes etc.
In this modified class, we introduce features 'edit_cntrl', 'edit_entrl2', and 'result_cntrl' of class type WEL_SINGLE_LINE_EDIT, feature 'comment_edit' of class type WEL_MULTI_LINE_EDIT and feature 'compute_btn' of class type WEL_PUSH_BUTTON and a constant 'Id_compute_btn'. A little exposition on the new class types: class WEL_SINGLE_LINE_EDIT encapsulates a standard single line edit control; class WEL_MULTI_LINE_EDIT encapsulates a standard multi-line edit control; class WEL_PUSH_BUTTON as you might have guessed, abstracts a standard push button. The modified creation feature 'make' creates the three single line edit controls, one multi-line edit control and a button after the main window is created. The reason will be clear shortly. If you notice, each of the controls has a similar creation style. The 'make' feature on all these controls has exactly seven parameters. We'll pick the following arbitrary line to explain the parameters:
!!compute_btn.make(Current,"Compute -->", 22, 75, 150, 40, Id_compute_btn)
The first parameter passed is Current. The Current object in this case is of class type CALCULATOR_MAIN_WINDOW. The reason for passing this is based on the Win95/NT windowing model (virtually all windowing systems also adopt a similar approach). Every window you see on your desktop (yes, even buttons, edit boxes, combo boxes, list boxes and radio buttons are considered to be small windows) has a parent-child relationship to some other window. For example, the OK button you see in a dialog box has the dialog box as its parent window. Another example: Every application's main window has the typical Explorer desktop as its parent window because the Explorer desktop window is the first and top-most window you see upon startup. One reason among others for maintaining this hierarchy is that when an event is generated in a window, say........ a click of a button in a dialog box, then the dialog box (i.e the parent window of the button) should be notified that the button was pressed. Why should the dialog box be notified ??? Well, one possible reason could be to close the dialog box in response to an OK button click. Another reason for maintaining this hierarchy is that when a parent window is destroyed, then all child windows are also automatically destroyed. Because of this parent-child dependency, you can now understand why the controls are created after the main window (parent window) is initialized in feature 'make' as mentioned before.
The second parameter is the title of the window. For a push button, the title is simply the caption of the button. For an edit control, the title is the simply the text that is displayed in the edit control. If you notice, the title for the three edit controls in the above code listing simply passes an empy string " ". As you might have guessed, we want an empty edit control. The third and fourth parameters denote the control's top left (x,y) coordinates relative to the parent window. The fifth and sixth parameters are the width and height of the control. The last parameter is the control's integer id. What purpose does this id serve ??? Remember the parent-child hierarchy example mentioned above. Suppose a dialog box has many buttons; say an OK and a CANCEL button. When a click occurs in one of these buttons, the dialog box as we discussed before is notified of this event. But how does the dialog box know which button generated the event i.e. which button was clicked? The id of the button is how the dialog box distinguishes one button from the other. Similarly, whenever any event occurs in any child window, the parent window is notified of what event occured and which child window generated the event; via the child window's id. Notice that for all the three edit controls, we simply pass an id of -1. This contradicts what I mentioned of having each child control a unique id. The answer is quite simple. The reason for giving a unique id to each child control is to be able to distinguish which child control generated an event We choose an arbitrary non unique number (-1) because we are NOT interested in processing any events that these controls generate. We could have passed any other number; it was only a matter of personal choice choosing -1. Nothing prevents us from giving the controls different ids, but why bother ?? We are not going to be needing them anyway! Because we are only interested in the button control click event, we therefore give it a unique id.
One last addition to this class is the redefinition of feature 'on_control_id_command'. Remember the parent-child hierarchy ? When a notification message arrives in a parent window, the event intercepting feature in the ancestor class WEL_COMPOSITE_WINDOW (an ancestor of WEL_FRAME_WINDOW) calls an empty (non-implemented) feature 'on_control_id_command' passing the id of the child window (control) that generated the event as a parameter. We simply redefine the empty feature 'on_control_id_command' to process the events. Right now, we don't process any events, so clicking the button now doesn't do anything as yet ! Step 3 will implement the feature 'on_control_id_command'.
You might ask whether there exists any other method of catching control events. The control ids mentioned above is actually not necessary. You can get rid of those unique ids all together (just pass a -1 or any other number you like) and still be able to process events for the controls. We defer this discussion of catching events without relying on unique ids using feature 'on_control_command' in Step 2
Our customized main window now looks like this:
This brings us to the end of step 1. As you have seen, most of what we have done so far is simply extend and specialize (by redefinition) the WEL_FRAME_WINDOW class to suite our calculator application.
In this step we will add a menu and an about dialog box.
Our main menu has two pop-up menus: Options and Help.
Options has five menu-items with the following captions:
'Load Comment...'
'Save Comment...'
'Change operator...'
A seperator menu-item.
'Exit...'.
Help has one menu-item with caption 'About...'.
The two pop-up menus look like this:
Now we incorporate this menu into our application. Our modified class CALCULATOR_MAIN_WINDOW is:
class CALCULATOR_MAIN_WINDOW inherit WEL_FRAME_WINDOW redefine class_background, default_width, default_height, on_control_id_command, on_menu_command end creation make feature -- Initialization make is -- Make the main window do make_top ("WEL Calculator") set_menu(main_menu) !!edit_cntrl.make(Current,"",10, 5, 150, 25, -1) !!edit_cntrl2.make(Current,"",10, 35, 150, 25, -1) !!compute_btn.make(Current,"Compute -->", 10, 65, 150, 40, Id_compute_btn) !!result_cntrl.make(Current, "",10,110,150,25,-1) !!comment_edit.make(Current,"", 175, 5, 200, 145, -1) end feature {NONE} -- Implementation Id_compute_btn: INTEGER is unique Cmd_about: INTEGER is 40001 Cmd_change_operator: INTEGER is 40002 Cmd_save_comment: INTEGER is 40003 Cmd_load_comment: INTEGER is 40004 Cmd_exit: INTEGER is 40005 edit_cntrl, edit_cntrl2, result_cntrl: WEL_SINGLE_LINE_EDIT comment_edit: WEL_MULTIPLE_LINE_EDIT compute_btn: WEL_PUSH_BUTTON class_background: WEL_LIGHT_GRAY_BRUSH is -- Gray background once !! Result.make end default_width: INTEGER is do Result := 400 end default_height: INTEGER is do Result := 210 end main_menu: WEL_MENU is local options, help: WEL_MENU once !!options.make options.append_string("&Load Comment...", Cmd_load_comment ) options.append_string("&Save Comment...", Cmd_save_comment) options.append_string("&Change Operator...",Cmd_change_operator) options.append_separator options.append_string("E&xit", Cmd_exit) !!help.make help.append_string("&About...", Cmd_about) !!Result.make Result.append_popup(options, "&Options") Result.append_popup(help, "&Help") end on_control_id_command(control_id: INTEGER) is do end on_menu_command(menu_id: INTEGER) is do inspect menu_id when Cmd_about then when Cmd_change_operator then when Cmd_load_comment then when Cmd_save_comment then when Cmd_exit then destroy end end end -- class CALCULATOR_MAIN_WINDOW
We add a once feature called 'main_menu' of class type WEL_MENU. Class WEL_MENU as its name suggests encapsulates a menu. Feature 'main_menu' declares two local references 'options' and 'help' of class type WEL_MENU. These local references encapsulsate the two pop-up menus mentioned above. You might be confused by the pop-up menu and the main menu both being class type WEL_MENU. Actually, each pop-up menu is a sub-menu. In other words, an application's main menu consists of potentially many sub-menus (popup-menus) which are similar to the main menu in functionality, hence the same class type. This nesting of menus can be extended to any arbitrary depth. i.e a menu can have a sub-menu which in turn can have a sub-menu and so on. To create the 'options' popup-menu, we call feature 'append-string' of class WEL_MENU. Feature 'append_string' creates a menu-item and has two input parameters. The first one is simply the string caption of the menu item. Don't be alarmed by the ampersand '&' in the caption string. No, this is NOT the address operator you're accustomed to seeing in C :-). All the ampersand does is instruct feature 'append_string' which character of the menu-item string caption to underline. This enables the user to simply press the underlined character key of the menu-item string caption to select that menu-item when the containing pop-up menu is highlighted. The second parameter is the menu-item's unique integer id. The reason for passing this id will be clear shortly. Pop-up menu 'help' is created in a similar fashion.
Note: Our choice of the integer constants for the menu-item ids was arbitrary. You may choose any integer constant you like.
After the two pop-up menus are created, they are merged together in the application's main menu, which is referenced by feature 'main_menu'. This merging is done by feature 'append_popup'. The feature's first parameter is the pop-up menu object reference. The second parameter is the string caption of the pop-up menu. The only addition to creation feature 'make' of our root class CALCULATOR_MAIN_WINDOW is to display the main menu. This is done by a simple call to feature 'set_menu' inherited from class WEL_FRAME_WINDOW, passing it a reference to an object of class type WEL_MENU, which in our case is simply feature 'main_menu'. Simple...huhh :-).
One more change to our root class is the redefinition of feature 'on_menu_command'. When the user clicks on a menu-item, a notification is sent to the menu containing parent window (Remember the parent-child hierarchy for the push buttons and other controls? Think of a menu as a child window like the controls discussed previously). When that notification message arrives, the event intercepting feature in the ancestor class WEL_COMPOSITE_WINDOW (an ancestor of WEL_FRAME_WINDOW) calls an empty (non-implemented) feature 'on_menu_command' passing the id of the menu-item that generated the event as a parameter. We simply redefine the empty feature 'on_menu_command' to process the events for the menu-items. Just to brush up your memory, this is very much analogous to feature 'on_control_id_command' from step 1. The implementation of feature 'on_menu_command' inspects the menu_id parameter to check which menu-item generated the event by using an inspect clause and then taking the appropriate action. Right now, only the event for the Exit menu-item is processed. When the Exit menu-item is clicked, the application is closed by calling the feature 'destroy' inherited from ancestor class WEL_COMPOSITE_WINDOW (an ancestor of WIN_FRAME_WINDOW).
We now add an about dialog box to spice up our calculator. The class corresponding to the about dialog box is:
class ABOUT_DIALOG inherit WEL_FRAME_WINDOW redefine on_control_command, default_style end;
creation make
feature -- Initialization
make(dlg_parent: WEL_COMPOSITE_WINDOW) is do make_child(dlg_parent,"About") move_and_resize(0, 0, 243, 112, true) !! ok_btn.make(Current,"OK",150,25,74,24, -1) !! static_text.make(Current,"WEL Calculator",10,20,100,18,-1) !! static_text2.make(Current,"By Dr. Ostroff",10,40,140,18, -1) end
feature activate is do show end
on_control_command(control: WEL_CONTROL) is do if control = ok_btn then hide end end
default_style: INTEGER is do Result := Ws_caption + Ws_ex_dlgmodalframe end
feature {NONE}
ok_btn: WEL_PUSH_BUTTON static_text: WEL_STATIC static_text2: WEL_STATIC
end
Because a dialog box is similar to other windows (like our main window), class ABOUT_DIALOG as you might have guessed descends from WEL_FRAME_WINDOW. In fact, this is the class to descend from to create any dialog boxes. To be really honest, I lied when I mentioned any. Actually, one dialog box I know of doesn't descend from WEL_FRAME_WINDOW, however, that is not much of a concern to us right now :-) A little digression here: If you were persistent enough to have read the previous version of this tutorial that relied on resource editors, you might have noticed that class WEL_MODAL_DIALOG was used as an ancestor class there instead of WEL_FRAME_WINDOW. As a matter of fact, class WEL_MODAL_DIALOG also descends from WIN_FRAME_WINDOW , however, it's intended to be used only with dialog boxes already contained in an application's resource i.e. Any dialog box in an application's resource can be encapsulated by class WEL_MODAL_DIALOG (at-least as I understand it). What we are now doing is creating the dialog box dynamically using pure WEL Eiffel code withoug relying on resources, so using WEL_MODAL_DIALOG is out of the question.
Notice above that the ABOUT_DIALOG class creation feature 'make' DOES NOT call inherited creation feature 'make_top' from class WEL_FRAME_WINDOW, as was the case for the main window; instead we call inherited creation feature 'make_child'. Feature 'make_child' as its name suggests, creates a child window. The about dialog box is a child window of the applications's main window. The first parameter in feature 'make_child' is the parent window object reference. Remember, this is important to maintain the parent-child hierarchy as discussed previously. The second parameter is simply the title of the about box. Feature 'move_and_resize' does exactly what it says. It specifies the top-left coordinate along with the width and height of the about box. The true parameter at the end simply instructs feature 'move_and_resize' to repaint the about box after the move and resize operation. Could we have overriden features 'default_width' and 'default_height' as we did for the main window to achieve the same end?? Yes, we could have, but why bother with overriding two features when the same thing can be achieved with a simple feature call !!!
The dialog box has a push button captioned 'OK' of class type WEL_PUSH_BUTTON (already discussed before). In addition, we introduce a new WEL library class here namely WEL_STATIC. A static control is simply a label or a text displaying control that can't be edited, hence the name STATIC. The two static text controls are captioned "WEL Calculator" and "By Dr. Ostroff". The controls are instantiated in the about box's 'make' feature. The seven parameters passed to each controls 'make' feature is exactly as mentioned in step 1. Note how we maintain the parent-child hierarchy between the about box and the three child controls by passing 'Current' in the three controls 'make' feature. An important subtlety to remember here is that after the about box's 'make' feature is called, the dialog box exists but is NOT yet displayed. To display the dialog box, feature 'show' inherited from WEL_FRAME_WINDOW needs to be called. This is exactly what feature 'activate' does. Feature 'default_style' is overriden to give the window a dialog box outlook. Don't worry too much about the Ws_..... constants for now.
You might be wondering why we DON'T pass a unique id for any of the three controls (we simply pass a -1 id) in the about box's creation feature 'make'. Based on previous discussion, this means we are not interested in processing events for these controls, right ???................... WRONG !!! Remember, I mentioned in step 1 that there was a way of catching control events without relying on ids. That's exactly what we'll be doing here. I'm not mentioning this method here to cram your head with further redundancy. I only intend to show you of the viable options and flexibility the WEL class library offers. Feature 'on_control_command' enables us to process control events without using ids. If you glance at this feature's signature on_control_command(control: WEL_CONTROL), it's very much similar to feature 'on_control_id_command' with signature on_control_id_command(control_id: INTEGER). The latter is used to trap events using a control's id as mentioned in step 1. How is the former one different ?? Well, the only difference is that rather than relying on a control's unique id, the control's object reference is used instead. For example, the OK button is encapsulated by the object reference 'ok_btn' declared above of class type WEL_PUSH_BUTTON. When the user clicks the OK button, feature 'on_control_command' is called with the parameter 'control' passed being the object reference of the OK button (similar to ok_btn). It's only a matter of checking the parameter 'control' of feature 'on_control_command' to check which control generated an event. In our case, we simply perform a reference equivalence check to see if the OK button generated an event and if so, simply hide the about box by calling inherited feature 'hide'.
The about dialog box looks like this:
We woud like to display this dialog box in response to the 'About...' menu-item click by the user. Our modified class CALCULATOR_MAIN_WINDOW is:
class CALCULATOR_MAIN_WINDOW
inherit WEL_FRAME_WINDOW redefine class_background, default_width, default_height, on_control_id_command, on_menu_command end
creation make
feature -- Initialization
make is -- Make the main window
do make_top ("WEL Calculator") set_menu(main_menu) !!edit_cntrl.make(Current,"",10, 5, 150, 25, -1) !!edit_cntrl2.make(Current,"",10, 35, 150, 25, -1) !!compute_btn.make(Current,"Compute -->", 10, 65, 150, 40, Id_compute_btn) !!result_cntrl.make(Current, "",10,110,150,25,-1) !!comment_edit.make(Current,"", 175, 5, 200, 145, -1) end
feature {NONE} -- Implementation
Id_compute_btn: INTEGER is unique
Cmd_about: INTEGER is 40001 Cmd_change_operator: INTEGER is 40002 Cmd_save_comment: INTEGER is 40003 Cmd_load_comment: INTEGER is 40004 Cmd_exit: INTEGER is 40005
edit_cntrl, edit_cntrl2, result_cntrl: WEL_SINGLE_LINE_EDIT comment_edit: WEL_MULTIPLE_LINE_EDIT
compute_btn: WEL_PUSH_BUTTON
class_background: WEL_LIGHT_GRAY_BRUSH is -- Gray background once !! Result.make end
default_width: INTEGER is do Result := 400 end
default_height: INTEGER is do Result := 210 end
main_menu: WEL_MENU is local options, help: WEL_MENU once !!options.make options.append_string("&Load Comment...", Cmd_load_comment ) options.append_string("&Save Comment...", Cmd_save_comment) options.append_string("&Change Operator...",Cmd_change_operator) options.append_separator options.append_string("E&xit", Cmd_exit)
!!help.make help.append_string("&About...", Cmd_about)
!!Result.make Result.append_popup(options, "&Options") Result.append_popup(help, "&Help") end
on_control_id_command(control_id: INTEGER) is do end
on_menu_command(menu_id: INTEGER) is do inspect menu_id when Cmd_about then about_dialog.activate when Cmd_change_operator then when Cmd_load_comment then when Cmd_save_comment then when Cmd_exit then destroy end end
about_dialog: ABOUT_DIALOG is once !!Result.make(Current) end
end -- class CALCULATOR_MAIN_WINDOW
Processing the menu-item event is darn simple. The only feature we add is 'about_dialog' of custom class type ABOUT_DIALOG. This class as discussed above encapsulates our about dialog box and is analogous to WIN_FRAME_WINDOW, which abstracts the main window. Feature 'about_dialog' simply calls creation feature 'make' and passes 'Current' as the first parameter to maintain the parent-child hierarchy between the main window and the about box. When the user clicks on the 'About...' menu-item, feature 'on_menu_command' as discussed before is called with the id corresponding to the 'About...' menu-item (Cmd_about). When that happens, we simply call feature 'activate' of class ABOUT_DIALOG to display the dialog box. Pretty simple, isn't it ??
This brings us to the end of step 2. One more step to go!
So far we really have only been working on the user-interface of the calculator. Now, we'll begin work on the calculator's actual functionality. In this step, we'll allow the user to choose an operator (you guessed it, via a dialog box ! ), enter two operands (real numbers) and let the calculator compute the result. We also enable the user to enter and edit comments and optionally save them in a text file. In addition, we also allow the user to load the contents of the comment edit control from a text file. Before we code the functionality, we have to take care of the operator dialog box. It's very much similar to the about box from step 2.
Let's start off with the class corresponding to the operator dialog box:
class CHANGE_OPERATOR_DIALOG inherit WEL_FRAME_WINDOW redefine on_control_command, default_style end;
creation make
feature -- Initialization
make(dlg_parent: WEL_COMPOSITE_WINDOW) is do make_child(dlg_parent, "Choose Operator") move_and_resize(0, 0, 285, 140, true) !!grp_box.make(Current,"Operator", 12, 7, 140, 98, -1) !!ok_btn.make(Current,"OK",190,15,80,24, -1) !!cancel_btn.make(Current,"Cancel",190,45,80,24, -1) !!plus_radio.make(Current,"Plus",20,28,80,15,-1) !!minus_radio.make(Current,"Minus",20,46,80,15,-1) !!multiply_radio.make(Current,"Multiply",20,64,80,15, -1) !!divide_radio.make(Current,"Divide",20,82,80,15,-1) end
feature {NONE}
ok_btn: WEL_PUSH_BUTTON cancel_btn: WEL_PUSH_BUTTON
plus_radio: WEL_RADIO_BUTTON minus_radio: WEL_RADIO_BUTTON multiply_radio: WEL_RADIO_BUTTON divide_radio: WEL_RADIO_BUTTON
grp_box: WEL_GROUP_BOX
feature {CALCULATOR_MAIN_WINDOW}
activate is local p: CALCULATOR_MAIN_WINDOW do p ?= parent inspect p.operator when '+' then plus_radio.set_checked when '-' then minus_radio.set_checked when '*' then multiply_radio.set_checked when '/' then divide_radio.set_checked end show end
feature {NONE}
on_control_command(control: WEL_CONTROL) is local p: CALCULATOR_MAIN_WINDOW do if control = ok_btn then p ?= parent if plus_radio.checked then p.set_operator('+') elseif minus_radio.checked then p.set_operator('-') elseif multiply_radio.checked then p.set_operator('*') else p.set_operator('/') end hide elseif control = cancel_btn then plus_radio.set_unchecked minus_radio.set_unchecked multiply_radio.set_unchecked divide_radio.set_unchecked hide end end
default_style: INTEGER is do Result := Ws_caption + Ws_ex_dlgmodalframe end
end -- CHANGE_OPERATOR_DIALOG
This class is very much similar to the ABOUT_DIALOG class. All the features in this class have already been discussed previously. The only addition is the introduction of a new WEL library class WEL_RADIO_BUTTON. Notice how we make use of the parent-child hierarchy in feature 'activate'. Before displaying the dialog box (i.e. calling feature 'show'), we query feature 'operator' contained in the parent window object (i.e. calculator's main window having class type CALCULATOR_MAIN_WINDOW) and correspondingly set the correct radio control in the check state. Just so you are not confused, feature 'operator' is only introduced in the updated main window class later below. Check the updated main window class (CALCULATOR_MAIN_WINDOW) further below for the changes.
Whent the user clicks either the OK or Cancel button, feature 'on_control_command' as discussed before is called. If the OK button is clicked, we update feature 'operator' contained in the parent window object with the operator the user checked. If the Cancel button was clicked, we simply uncheck all the radio controls so that when the dialog box is invoked at a later time, the appropriate radio control will be checked by feature 'activate' as explained before. In both cases, we hide the dialog box at the end by calling feature 'hide'.
The operator dialog box looks like this:
We woud like to display this dialog box in response to the 'Change Operator...' menu-item click by the user. Our modified CALCULATOR_MAIN_WINDOW class is:
class CALCULATOR_MAIN_WINDOW
inherit WEL_FRAME_WINDOW redefine class_background, default_width, default_height, on_control_id_command, on_menu_command end
creation make
feature -- Initialization
make is -- Make the main window
do make_top ("WEL Calculator") set_menu(main_menu) !!edit_cntrl.make(Current,"",10, 5, 150, 25, -1) !!edit_cntrl2.make(Current,"",10, 35, 150, 25, -1) !!compute_btn.make(Current,"Compute -->", 10, 65, 150, 40, Id_compute_btn) !!result_cntrl.make(Current, "",10,110,150,25,-1) !!comment_edit.make(Current,"", 175, 5, 200, 145, -1) set_operator('+') end
feature {CHANGE_OPERATOR_DIALOG}
operator: CHARACTER
set_operator(new_op: CHARACTER) is do operator := new_op end
feature {NONE} -- Implementation
Id_compute_btn: INTEGER is unique
Cmd_about: INTEGER is 40001 Cmd_change_operator: INTEGER is 40002 Cmd_save_comment: INTEGER is 40003 Cmd_load_comment: INTEGER is 40004 Cmd_exit: INTEGER is 40005
edit_cntrl, edit_cntrl2, result_cntrl: WEL_SINGLE_LINE_EDIT comment_edit: WEL_MULTIPLE_LINE_EDIT
compute_btn: WEL_PUSH_BUTTON
class_background: WEL_LIGHT_GRAY_BRUSH is -- Gray background once !! Result.make end
default_width: INTEGER is do Result := 400 end
default_height: INTEGER is do Result := 210 end
main_menu: WEL_MENU is local options, help: WEL_MENU once !!options.make options.append_string("&Load Comment...", Cmd_load_comment ) options.append_string("&Save Comment...", Cmd_save_comment) options.append_string("&Change Operator...",Cmd_change_operator) options.append_separator options.append_string("E&xit", Cmd_exit)
!!help.make help.append_string("&About...", Cmd_about)
!!Result.make Result.append_popup(options, "&Options") Result.append_popup(help, "&Help") end
on_control_id_command(control_id: INTEGER) is local value: REAL do if control_id = Id_compute_btn then if (edit_cntrl.text.is_real) and (edit_cntrl2.text.is_real) then result_cntrl.clear inspect operator when '+' then value := edit_cntrl.text.to_real + edit_cntrl2.text.to_real when '-' then value := edit_cntrl.text.to_real - edit_cntrl2.text.to_real when '*' then value := edit_cntrl.text.to_real * edit_cntrl2.text.to_real when '/' then value := edit_cntrl.text.to_real / edit_cntrl2.text.to_real end result_cntrl.set_text(value.out) end end end
on_menu_command(menu_id: INTEGER) is do inspect menu_id when Cmd_about then about_dialog.activate when Cmd_change_operator then change_operator_dialog.activate when Cmd_load_comment then when Cmd_save_comment then when Cmd_exit then destroy end end
about_dialog: ABOUT_DIALOG is once !!Result.make(Current) end
change_operator_dialog: CHANGE_OPERATOR_DIALOG is once !!Result.make(Current) end
end -- class CALCULATOR_MAIN_WINDOW
The changes made to the main window class (CALCULATOR_MAIN_WINDOW) are as follows:
Congratulations, we have a simple working calculator! Believe it or not, we are almost done with the tutorial. I can already sense you relentlessly grinding your teeth and begging when will this finally be over :-( Calm down.......... just a little bit of patience and we'll have a full-fledged (at-least for this tutorial) calculator application working :-)
Lastly, we add the abiltiy for the user to load and save comments to a text file. As of now, you can edit the text in the comment edit control, but once the application quits, that text is lost. Our updated (I promise the last one!) main window class CALCULATOR_MAIN_WINDOW is:
class CALCULATOR_MAIN_WINDOW
inherit WEL_FRAME_WINDOW redefine class_background, default_width, default_height, on_control_id_command, on_menu_command end
creation make
feature -- Initialization
make is -- Make the main window
do make_top ("WEL Calculator") set_menu(main_menu) !!edit_cntrl.make(Current,"",10, 5, 150, 25, -1) !!edit_cntrl2.make(Current,"",10, 35, 150, 25, -1) !!compute_btn.make(Current,"Compute -->", 10, 65, 150, 40, Id_compute_btn) !!result_cntrl.make(Current, "",10,110,150,25,-1) !!comment_edit.make(Current,"", 175, 5, 200, 145, -1) set_operator('+') set_window_title(Void) end
feature {CHANGE_OPERATOR_DIALOG}
operator: CHARACTER
set_operator(new_op: CHARACTER) is do operator := new_op end
feature {NONE} -- Implementation
Id_compute_btn: INTEGER is unique
Cmd_about: INTEGER is 40001 Cmd_change_operator: INTEGER is 40002 Cmd_save_comment: INTEGER is 40003 Cmd_load_comment: INTEGER is 40004 Cmd_exit: INTEGER is 40005
edit_cntrl, edit_cntrl2, result_cntrl: WEL_SINGLE_LINE_EDIT comment_edit: WEL_MULTIPLE_LINE_EDIT
compute_btn: WEL_PUSH_BUTTON
class_background: WEL_LIGHT_GRAY_BRUSH is -- Gray background once !! Result.make end
default_width: INTEGER is do Result := 400 end
default_height: INTEGER is do Result := 210 end
main_menu: WEL_MENU is local options, help: WEL_MENU once !!options.make options.append_string("&Load Comment...", Cmd_load_comment ) options.append_string("&Save Comment...", Cmd_save_comment) options.append_string("&Change Operator...",Cmd_change_operator) options.append_separator options.append_string("E&xit", Cmd_exit)
!!help.make help.append_string("&About...", Cmd_about)
!!Result.make Result.append_popup(options, "&Options") Result.append_popup(help, "&Help") end
on_control_id_command(control_id: INTEGER) is local value: REAL do if control_id = Id_compute_btn then if (edit_cntrl.text.is_real) and (edit_cntrl2.text.is_real) then result_cntrl.clear inspect operator when '+' then value := edit_cntrl.text.to_real + edit_cntrl2.text.to_real when '-' then value := edit_cntrl.text.to_real - edit_cntrl2.text.to_real when '*' then value := edit_cntrl.text.to_real * edit_cntrl2.text.to_real when '/' then value := edit_cntrl.text.to_real / edit_cntrl2.text.to_real end result_cntrl.set_text(value.out) end end end
on_menu_command(menu_id: INTEGER) is do inspect menu_id when Cmd_about then about_dialog.activate when Cmd_change_operator then change_operator_dialog.activate when Cmd_load_comment then open_dialog.activate(Current) if open_dialog.selected then load_from_text_file(open_dialog.file_name) end when Cmd_save_comment then save_dialog.activate(Current) if save_dialog.selected then save_to_text_file(save_dialog.file_name) load_from_text_file(save_dialog.file_name) end when Cmd_exit then destroy end end
about_dialog: ABOUT_DIALOG is once !!Result.make(Current) end
change_operator_dialog: CHANGE_OPERATOR_DIALOG is once !!Result.make(Current) end
open_dialog: WEL_OPEN_FILE_DIALOG is once !!Result.make Result.set_filter(<<"Comment file">>, <<"*.cmt">>) end
save_dialog: WEL_SAVE_FILE_DIALOG is once !!Result.make Result.set_filter(<<"Comment file">>, <<"*.cmt">>) end
load_from_text_file(file_name: STRING) is local text_file: PLAIN_TEXT_FILE buffer: STRING do !!text_file.make_open_read(file_name)
from !!buffer.make(text_file.count) until text_file.end_of_file loop text_file.read_line buffer.append(text_file.last_string) buffer.append("%R%N") end
comment_edit.set_text(buffer) text_file.close set_window_title(file_name) end
save_to_text_file(file_name: STRING) is local text_file: PLAIN_TEXT_FILE buffer: STRING line: INTEGER do !!text_file.make_open_write(file_name)
from line := 0 until line = comment_edit.line_count loop !!buffer.make(comment_edit.line_length(line)) buffer := comment_edit.line(line) text_file.put_string(buffer) text_file.new_line line := line + 1 end
text_file.close end
set_window_title (file_name: STRING) is -- Set the window's title with `file_name'. local s: STRING do s := clone (Title) s.append (" - ") if file_name /= Void then s.append (file_name) else s.append ("Untitled") end set_text (s) end
Title : STRING is "WEL Calculator"
end -- class CALCULATOR_MAIN_WINDOW
Let's wrap this quickly................
The following are the changes added:
There you are, the tutorial is finally over! Granted, it's not easy to learn the WEL library in the beginning, but, the effort pays off since most of the process of building WEL applications is repetitious. Whatever you have done in this tutorial, you'll find yourself doing pretty much the same thing again in many of your projects (at-least as far as the user interface is concerned). The WEL library is large and rich. Take your time to learn the class internals and yes.....don't get lost navigating those myriad classes !! Happy learning :-)