Eiffel for Visual FoxPro Programmers: From Data to Presentation
The previous example compared getting similar data from a Visual FoxPro database container loaded and ready for presentation with the same task in Eiffel. The next step is to take the prepared data and present it to the user. The caution for both FoxPro and for Eiffel is this -- In production code you most likely won't do things this way. The examples are for demonstration purposes alone and are meant to provide comparison and flavor rather than a description of, "This is how we do it in production." Nevertheless, it is helpful for Visual FoxPro people to see a simple, understandable and straight-forward example for purposes of comparison. So, with that stated, here is some FoxPro code to read through.
I have taken the previous example and expanded it with a form and a grid. The same will be true of the Eiffel code, but let's focus on the Fox code first:
CLOSE DATABASES ALL
CLOSE TABLES ALL
SET ASSERTS ON
LOCAL ;
lo_form as Form, ;
lc_dbc as String, ;
lc_dir as String, ;
lo_exception as Exception, ;
ll_has_first_name as Boolean, ;
ll_has_last_name as Boolean
LOCAL ARRAY la_fields[1]
lc_dbc = "northwind.dbc"
lc_dir = "C:\PROGRAM FILES (X86)\MICROSOFT VISUAL FOXPRO 9\SAMPLES\NORTHWIND\"
TRY
OPEN DATABASE (ADDBS(lc_dir) + lc_dbc) SHARED NOUPDATE
USE employees IN 0 SHARED
ln_field_count = AFIELDS(la_fields, "employees")
FOR ln_field = 1 TO ln_field_count
IF INLIST(la_fields[ln_field, 1], "firstname", "lastname")
IF la_fields[ln_field, 2] = 'C'
ELSE
THROW && The expected datatype is Character for first and last name.
ENDIF
ENDIF
ENDFOR
CATCH TO lo_exception
DO CASE
CASE lo_exception.ErrorNo = 1520 && No database is open or set as the current database.
ASSERT .f. MESSAGE "check: The DBC did not load properly."
CASE lo_exception.ErrorNo = 1 && Result of THROW
ASSERT .f. MESSAGE "check: There is a problem with the table field structure."
OTHERWISE
ASSERT .f. MESSAGE "check: Some unknown error with data load."
ENDCASE
ENDTRY
lo_form = CREATEOBJECT("main_window")
READ EVENTS
RETURN
DEFINE CLASS main_window as Form
Width = 800
Height = 600
PROCEDURE Init
LOCAL lo_person_grid as Grid, lo_col as Column, lo_hdr as Header
THISFORM.Caption = "AFUG Simple in VFP"
THISFORM.AddObject("o_person_grid", "grid")
lo_person_grid = THISFORM.o_person_grid
lo_person_grid.ColumnCount = 0 && Remove all pre-existing columns
lo_person_grid.ColumnCount = 4
lo_col = lo_person_grid.Columns(1)
lo_hdr = lo_col.header1
lo_hdr.Caption = "First"
lo_col.ControlSource = "employees.firstname"
lo_col = lo_person_grid.Columns(2)
lo_hdr = lo_col.header1
lo_hdr.Caption = "Last"
lo_col.ControlSource = "employees.lastname"
lo_col = lo_person_grid.Columns(3)
lo_hdr = lo_col.header1
lo_hdr.Caption = "Birth date"
lo_col.ControlSource = "employees.birthdate"
lo_col = lo_person_grid.Columns(4)
lo_hdr = lo_col.header1
lo_hdr.Caption = "Hire date"
lo_col.ControlSource = "employees.hiredate"
lo_person_grid.Top = 0
lo_person_grid.Left = 0
lo_person_grid.Height = THISFORM.Height
lo_person_grid.Width = THISFORM.Width
lo_person_grid.Anchor = 64+128+1+2
lo_person_grid.Visible = .t.
THISFORM.AutoCenter = .t.
THISFORM.Show
THISFORM.Visible = .t.
ENDPROC
PROCEDURE destroy
CLEAR EVENTS
ENDPROC
ENDDEFINE
In reality, the FoxPro code will not be handled like this. It will NOT appear in the form of a PRG file, but the essential ingredients will still remain as property settings and method code. In this case, the PRG format provides a simple, direct and executable form in which to see all that is done in order to achieve the resulting form.
Fox people -- first note the additional code just below the TRY-CATCH construct:
lo_form = CREATEOBJECT("main_window")
READ EVENTS
RETURN
The form "main_window" is created. It is formatted, extended and displayed in its "Init" method. Once the form is aboard in memory, FoxPro is placed into a READ EVENT cycle where it processes user interaction through the GUI. The RETURN command is not required, but is included to show a logical break in the code logic.
The most interesting code is in the DEFINE CLASS section of the code. It is here, the window is formatted, controls are loaded, formatted and finally displayed and made visible. Grid columns are created, linked to their data, sized and other formatting is applied. Finally, the form is prepared for destruction and the CLEAR EVENTS command, which brings FoxPro home from event watching and handling. From here the form will close and the application will shut down.
The Eiffel story is sort of the same, but the parts are different as well as how those parts come about.
Everything in Eiffel is a class. Eiffel has no notion of a procedure (e.g. PRG) or a screen (e.g. SCX). Everything is a class. All classes have features -- attributes and routines (commands and queries). So, structurally, the places where the two systems are similar are around notions in computer science and how our computers work rather than similarities of language constructions or notions.
One matter that is similar in one respect has to do with the "Visual" part of Visual FoxPro. In the example above, the main_window form is constructed entirely in code. This is NOT the normal method for creating forms (e.g. SCX or VCX). Most FoxPro people construct forms visually and not in code. In Eiffel, visual items are built in code rather than visually -- at least in the end.
Eiffel does include a tool called Eiffel Build, which allows a software engineer to construct complex visual layouts visually. I am personally finding this tool to be excellent for do "what-if" analysis and discovering and playing. It's great for tinkering. However, when it comes time to build the final interface for a complex system, the best way to work with the GUI code is in code and not visually. Mostly, this is because what the code does visually is based on the code text, which is well beyond the power of any visual tool. This is true of any language. It is true of FoxPro as well.
In Visual FoxPro, there is a limit to what the visual development IDE can present. Nuances of the forms, controls, behaviors and run-time functionality are utterly beyond the scope of the developers IDE. So, even in VFP we're forced to run our software and test our changes in small incremental steps, where we bounce between design-time and run-time all the time. Eiffel is the same way for the same reasons. Thus, the Eiffel Build tool is a great place to design, play, tinker, discover and explore. Ultimately, we gravitate to code text and leave the GUI tool behind us.
Having written all of this, what does a similar GUI presentation task look like in Eiffel?
initialize_person_grid
-- Initialize `person_grid' with data from Current and then format layout.
local
l_person: PERSON
do
data_list.do_all (agent person_row_setup)
person_grid.column (1).set_title ("First")
person_grid.column (2).set_title ("Last")
person_grid.column (2).set_width (150)
person_grid.column (3).set_title ("Sex")
person_grid.column (3).set_width (35)
person_grid.column (4).set_title ("DOB")
person_grid.column (4).set_width (150)
person_grid.column (5).set_title ("Inactive")
person_grid.column (5).set_width (150)
end
person_row_setup (a_person: PERSON)
-- Add `a_person' to `person_grid' of Current.
do
person_grid.set_item (1, a_person.num, create {EV_GRID_LABEL_ITEM}.make_with_text (a_person.firstname))
person_grid.set_item (2, a_person.num, create {EV_GRID_LABEL_ITEM}.make_with_text (a_person.lastname))
person_grid.set_item (3, a_person.num, create {EV_GRID_LABEL_ITEM}.make_with_text (a_person.gender_code))
person_grid.set_item (4, a_person.num, create {EV_GRID_LABEL_ITEM}.make_with_text (a_person.birth_dttm.out))
person_grid.set_item (5, a_person.num, create {EV_GRID_LABEL_ITEM}.make_with_text (a_person.inactive_date.out))
end
As stated earlier -- I cheated a little and used the MAIN_WINDOW class generated from Eiffel Build and then added code to that class (e.g. the example above) to it. Eiffel Build actually builds two classes: MAIN_WINDOW and MAIN_WINDOW_IMP. The "_IMP" class is where the _IMP stands for implementation. It is intended that the programmer (me and you) never touch this class and do all our specialization in the MAIN_WINDOW code. So, the code above is in MAIN_WINDOW. Let's see what some of the code (with respect to our VFP example) is in the IMP class:
initialize
-- Initialize `Current'.
do
Precursor {EV_TITLED_WINDOW}
-- Build widget structure.
extend (person_grid)
set_minimum_width (800)
set_minimum_height (600)
set_title ("AFUG Simple with Manager")
set_all_attributes_using_constants
-- Connect events.
-- Close the application when an interface close
-- request is recieved on `Current'. i.e. the cross is clicked.
close_request_actions.extend (agent destroy_and_exit_if_last)
-- Call `user_initialization'.
user_initialization
end
create_interface_objects
-- Create objects
do
-- Create all widgets.
create person_grid
...
end
How does this code work, by the numbers?
First: extend (person_grid)
This is similar to the FoxPro THISFORM.AddObject method. Essentially, there is a collection of GUI widgets and the "extend" adds our `person_grid' to that collection.
Second: set_minimum_width (800)
set_minimum_height (600)
set_title ("AFUG Simple with Manager")
This next block is just like THISFORM.Width = 800 and so on. In FoxPro, we say THISFORM.Caption and in Eiffel, the window has a title feature with a `set_title' feature for setting the title text.
Third: close_request_actions.extend (agent destroy_and_exit_if_last)
At this point, we're going to go to a place that is really foreign for FoxPro engineers. FoxPro has no notion of an agent. So, for you FoxPro folks, this will require some explanation. More precisely: What is an agent?
Before explaining what an agent is, let's talk about the code above. Many items in Eiffel have collections of actions. These actions are sometimes built around events. In this case, the MAIN_WINDOW class as a collection of actions to execute when the window gets a close request from the operating system. The action to execute is a "destroy" (e.g. `destroy_and_exit_if_last'). Thus, this code is telling the form what to do when the user clicks the close button. This is precisely the same idea as what is in the VFP code in the example Fox code above:
PROCEDURE destroy
CLEAR EVENTS
ENDPROC
The idea is the same, the execution is most decidedly NOT! Hence -- What is an Agent in Eiffel?
An agent is description of a routine or function that is delayed in execution. It can be executed once (as in our close or destroy action) or repeatedly -- or -- even not at all (e.g. a button having a click action agent response, but the button is never clicked at run time). Therefore, in our case we have a function called `destroy_and_exit_if_last' that is called and executed whenever the close request actions are trigger by a signal from the operating system.
What good is an agent you ask? Consider the following from our example:
data_list.do_all (agent person_row_setup)
The data_list' is our list of PERSON instances in an array (e.g. ARRAYED_LIST [PERSON]). The agent
person_row_setup' is applied to each instance of PERSON in the data_list'. Moreover, look at the signature of the
person_row_setup' function:
person_row_setup (a_person: PERSON)
Notice how the function takes one argument of `a_person; of type PERSON. The question immediately arises: How does this argument get passed in based on the agent code? The answer has to do with the magic of the Eiffel compiler, object theory and a little understanding.
The data_list' is a list filled with items. Each item in this case is an instance of PERSON, which is the same as the argument on the
person_row_setup' function. Moreover, note the function call on data_list', which is
do_all'. The net affect is how the code iterates over the items in data_list', send each item in the list into the
person_row_setup' call by way of the argument `a_person: PERSON'.
An alternate style for the agent call might make this more clear:
data_list.do_all (agent person_row_setup (?))
While the question mark is redundant in this case, it does help us see what's happening. The question mark is simply a placeholder for the argument of the function. We simply have to understand how the data_list' is a list of items iterated over by the
do_all' and each item in the list is sent in the place of the argument.
To be more precise -- the compiler builds C code that does this precise task. Unlike FoxPro, where the code is compiled to byte code and executed through interpretation and a run-time library of C functions, the Eiffel compiler builds actual C code that is then compiled to an executable and performs the steps described about the agent so far.
Summary
What is happening in FoxPro to present the data in a GUI is very much like what happens in Eiffel, but with different controls running in a different way (e.g. compiled Vs interpreted). Viewed from a high code altitude, both share about the same complexity, but the Eiffel code has powerful and distinct differences: Chiefly, it brings powerful notions in computer science to the table: Multiple Inheritance, Generics, Agents, Design by Contract and a host of other powerful and elegant tools. While agents are not a silver bullet (nor are they intended to be) for anything, they do offer a powerful modelling tool not found in any other language or method. So far, only Eiffel has them. They truly deserve their own specific treatment to gain an appreciation for Eiffel Agents and how they would look if coded full-out in FoxPro.
The resulting forms are very similar in look and feel. What is important to note is how Eiffel grids are much more expressive and are deeply customizable, whereas grids in FoxPro are troublesome and as a FoxPro programmer you have no ability to change or modify their behavior to the full extent. This is true of ALL the FoxPro base classes. You get to change NOTHING. Eiffel is different. Eiffel is YOURS!
Again -- Have FUN!