Claudio Lassala is an independent Software Developer who currently works mostly building Ruby on Rails applications. Previously, he has worked for several years developing .NET applications, presented several lectures at Microsoft events such as PDC Brazil, TechEd Europe, and various other Microsoft seminars, as well as several conferences and user groups across North America, Europe and Brazil. He is a multiple winner of the Microsoft MVP Award since 2001 (for Visual FoxPro in 2001-2002, and for C# ever since). He has articles published on several magazines, such as MSDN Brazil Magazine and CoDe Magazine. He started the Virtual Brown Bag meetings (www.virtualbrownbag.com) in 2009 and have been hosting it weekly since then.
When not writing code, Claudio is probably rocking out with his band, Descent Into Madness (http://www.descentintomadness.com).
In a previous life, Claudio authored and presented several training videos that can be found on the Universal Thread.
Following our course (which began on the 8th Issue of RapoZine), we will learn
to create Forms. Visual FoxPro give us tools that make Form creation very simple.
Let's start.
Creating an "About..." Form.
A very common Form, and also one of the simplest to create, is an "About..."
Form, where we place authorship information about our applications. So we will
start such a Form.
To start building a Form we could select File->New from the VFP Main menu;
then, on the dialog box that comes up we select the "form" File Type,
and click over New File (if we click over New Wizard, a wizard would open to
help us in basic tasks, but as all good developers does, we would create our
Form manually).
Alternatively, we could simply type right into the Command Window the command:
Create Form
Figure 1: Creating a new Form.
Once our new Form got opened in editing mode, we could insert controls (many times
referred as "objects"), as Command Buttons, Edit Boxes, etc. To do
that, we use the "Form controls" toolbar. We also have the "Form
Designer" toolbar that bring some other options to help us create Forms.
Figure 2: The "Form Controls" and "Form Designer" toolbars.
If you're not seeing your toolbars, you can make them visible trought the
menu View-> Toolbars... selecting them and clicking on the Ok button. Our
goal is to create a Form simmilar to the one show in Figure 3:
Figure 3: Our first goal - An "About..." Form.
Alright, let´s start placing an Image control, that can contain
pictures of the most common formats as JPEG, GIF, Bitmap, etc. Essentially,
it's enough to click the Image button on the "Form Controls" toolbar,
and the click over our Form. The control would be inserted and then we have
to go to the Properties Window to tell which picture have to be used with this
control. If you don't see the Properties Window, just right-click the control
and select the "Properties" option. For your understandig, Properties
are object characteristics. Properties allow us to define the appeareance (color,
size, width, etc) and state (visibility, enabling, etc) of objects. For the
Image control, we have a Picture property where we must type the path and filename
of the picture that we wish to show. Or we can click on the "..."
button to navigate our directories and choose the desired picture.
Figure 4: Inserting an Image Control.
Now we'll go to insert some text Labels on our Form. Let's click over the
Label button on the tollbar, and then click our Form. Over the Properties Window,
we'll search for the Caption property, and enter the text "SIFIN - Financial
Controlling System". The Caption property of the Label control allows us
to indicate the text that should appear on the Form. You'll notice that the
text appears incomplete on the Form. It's just a matter of increasing its size
manually, or set its AutoSize property to .T. This property determines that
the Label size would be dinamically increased or decreased to fit the size of
its content (defined by the Caption property).
Figure 5: Inserting a Label Control.
To give your Label with the appeareance of the Form in Figure 5, set the following
properties with the values shown bellow:
Property
Value
AutoSize
.T.
Caption
SIFIN - Financial Controlling System
FontBold
.T.
FontName
Tahoma
FontSize
11
You should have noticed that the Label is now too big, and it doesn't fit
anymore inside the Form. We'll then set some of the Form properties to give
it a better appeal.
Property
Value
Goal
AutoCenter
.T.
Automatically centers the Form on the screen when executed.
BorderStyle
2 - Fixed Dialog
Changes Form's border so the user can't resize it.
Caption
About SIFIN
Defines Form's title that appear on the tilte bar.
Defines the Form's name, by which you can refer programmatically.
Width
414
Sets Form's width to 414 pixels.
WindowType
1 - Modal
Defines the Form's as Modal, so when it becomes active, no other application's
Form or Menu can be accesed until this Form is closed.
The remaining Lables of our Form are quite simmilar, changing essentially
their placement, Caption and color (the ForeColor property). Maybe the most
importante trick here: after writing a long text in a Label's Caption, for it
to break in multiple lines according to its width, just set its WordWrap property
to .T. I'm sure that you'll get the remaining Labels without problems. Anyway,
the full source code for this month's project can be downloaded from the foot
of this article.
Now, let's beautify a little our Form's appeareance adding some Shape controls.
Just click the Shape button on the toolbar and then on the Form. Create two
Shapes and place them around our logo and also around the area with the
application's name. To get the 3D effect on the Shape, just set the SpecialEffect
property to: 0 - 3D.
Figure 6: Inserting a Shape Control.
You should notice that the Shapes are drawn over our text. That happens because
of the order in which the controls were inserted on the Form. However, we certainly
want that the Shapes get behind so we can see the labels. We need to use the
Layout Toolbar, which can be invoked from the Layout button on the Form Designer
toolbar, or from the View ->Layout Toolbar menu.
With the Layout toolbar we can work on the alignment and positioning of the
controls on our Forms. For example, let's align the Shapes horizontally (Align
Horizintal Centers), and send them behind the Labels (Send to Back). First select
the two controls (select one control, keep the Shift key pressed, and select
the other one). The Layout toolbar buttons would be enabled. Just press the
referred buttons.
Figure 7: Adjusting controls with the Layout Toolbar.
To finish adding controls on our Form, let's place a Command Button. Just
click the Command Button on the toolbar, and then over the form. After resizing
and placing the button over the Form, enter in its Caption's text: "Ok".
Figure 8: Inserting a Command Button Control.
Until now, all we made was visually created. No single line of code was typed.
However, all that we visually did can be done with programming, but we will
see this later in our course.
Well, our OK Command Button is useless by now, as if we execute the Form and
click on the button, nothing happens. We will associate one line of code to
it, so when we click on it, the Form gets closed.
Double-click the button, and a window for code editing will open. In this
window you'll see that the selected Object is Command1 (later we'll see how
to name more properly our controls), and the Method must be the Click method.
Methods are actions an object can execute. The Click method is automatically
associated with the Click Event. That means that every time the user clicks
on the button, the Click Event is triggered. This event would search for the
Method of the same name associated to it; in this case, the Click Method. Finally,
the Click Method will be executed and consequently, the code associated to it.
Don't be scared, because we will talk a lot about methods and events during
the course.
The line of code needed to close the Form is simple: Thisform.Release.
This line executes the Release Method of the Form in which we are.
Figure 9: Code to be executed when the button is clicked.
Perfect! We can execute our Form now and see the results. To execute it, just
click the Exclamation (!) sign on Visual FoxPro Standard Toolbar (if you don't
see it, just call it from the View->Toolbars... menu, selecting Standard).
Figure 10: Executing our Form.
The first time the Form is executed, VFP will ask you to save it, selecting
a directory and giving it a file name (the SCX extension would be automatically
assumed). Next time VFP wouldn't ask to save it, because it would be automatically
saved and executed. Then look at our Form in execution. You can try to select
any other VFP option, and you'll see that's not possible because we defined
this as a Modal Form. Finally, click on the OK button, and you'll see the Form
gets closed.
Figure 11: Our "About..." Form executing.
A Data-Entry Form
If you're coming from Clipper, you must be waiting to create a Data-Entry
Form in VFP (to insert, edit or delete records, etc). And without any doubt
our applications will ever have tons of Forms of this type. So let's create
a Form like this.
Begin creating a new Form as we did in the previous example. With the new
Form opne for editing, we will work within its Data Environment. In the Form's
Data Environment, we determine the tables with which the Form will work, which
indexes will be active, which will be the relationship, etc. All you used to
do in Clipper with lots of Use, Set Order and Set Relation, we will do in the
Data Environment visually, without even a single line of code (obviously, if
you want to do it programatically, there's no problem).
To open the Form's Data Environment, click the Data Environment button on
the Form Designer toolbar, or right-click the Form and select Data Environment
from the opening menu.
As our Data Environment is empty at this time, it will automatically open
a window for us to start selecting the ones that would be attached to our Form.
Even if at least a table exist, to add more tables, just right-click on the
Data Environment and select Add... on the opening menu. You would notice that
if you have a Database open, you don't need to navigate through your directories
to search for the tables, because all the tables in your Database would be automatically
listed.
For this example, select the Customers table.
Figure 12: Inserting a table in the Data Environment.
There are several properties for a table added to the Data Environment that
we can set. For example, the table Alias, a Filter, an active Index, etc. Once
again, we could do all this trough code, but it's far easier to set some properties
visually, isn't it? Then let's define as our tables's active index "Name".
Figure 13: Setting Table properties in the Data Environment.
Every time we execute a Form, its Data Environment is automatically opened,
and all its tables are opened as well, toghether with its indexes, relations,
and everything that has been defined. It is possible to "detach" this
behavior and leave to our criteria the moment in which tables have to be opened.
Now it's the time to create the Labels and Text boxes corresponding to the
fields of the table we will edit. In the Clipper era, how many time we spent
with @SAY and @GET to build a screen able to capture values and store them in
fields? It used to take some time, remember? In VFP, all is far easier. To insert
all the table's fields in the Form, just click in Fields over the table inside
the Data Environment, and drag it inside the Form. If you want only some of
the fields, just select them individually, keeping pressed the Ctrl key, and
then drag them with the mouse's right button, selecting in the menu the "Create
Multiple Controls Here" option.
Figure 14: Inserting controls for editing table's fields.
Very easy, isn't it? All those lines of code replaced by a simple drag-and-drop
operation.
As you can see, several controls were created automatically for us (obviously,
all this could be done manually). Remember that we set Caption properties when
we created our tables? Now the Labels created got this Captions. And for showing
and editing the values of the table's fields, controls like TextBox or ChekBox
were created. You can see that for text or date fields, Textbox controls were
created, while for a logic field a Checkbox were created. Beside all that, the
controls were created in an intelligent way, using the "txt" prefix
for textboxes and "chk" for the checkbox, followed by the field name.
No words about that VFP makes an auto-alignment for us.
What else? Do you remember that in Clipper we needed to create variables to
store the values from the table, work with that variables, and at the end of
the process, unload this variables contents again to the table? In VFP you don't
have to worry for this anymore. Look that the ControlSource property of every
control in the Form has been automatically completed with the table and field
name.
Figure 15: Controls automatically linked to table's fields.
The ControlSource property keeps a link between the Control and its data source,
in this case, some field in a table. This way, when we alter the value of controls,
the table's field value is automatically altered without the need of executing
any replace command.
You can alse see that the comments we entered when we created the table in
the Database were also transferred to the controls. This way, is easy for the
developer having a quick documentation on every field without the need of going
to see the table structure.
This VFP feature, dragging fields from a table onto a Form, is called IntelliDrop,
and can be customized by the developer, as we'll see later in our course.
Now is the time to insert some command buttons. Let's create, for example,
those traditional buttons of a data-entry screen, such as buttons to go to the
first record, the previous record, the next record, and the last record, plus
a button to close the Form.
Add the five buttons to the Form as you already learned. No, instead of use
text for the buttons, leave the Caption property empty and let's use pictures.
You need to set the Picture property for each button, pointing to the proper
image, following the table bellow (take the chance to give more intuitive names
to the buttons):
Control
Picture filename
cmdPrimeiro
..\..\libs\graph\bmp\primeiro.bmp
cmdAnterior
..\..\libs\graph\bmp\anterior.bmp
cmdProximo
..\..\libs\graph\bmp\proximo.bmp
cmdUltimo
..\..\libs\graph\bmp\ultimo.bmp
cmdFechar
..\..\libs\graph\bmp\fechar.bmp
Watch that using a relative path to the files (..\..\libs\...) gives you more
flexibility in the sense of no getting nailed to "C:\" or"D:\", for example.
If you're using VFP 7, set the buttons' SpecialEffect property to 2 - Hot
Tracking (that would give a flat/hot effect to them).
Very well, now is the time to program a little. We need to implement code
in the buttons to give them functionality. So let's double-click the cmdPrimeiro
button, and select the Click Method. Now we just enter there the following lines
of code:
Go TOP
Thisform.cmdPrimeiro.Enabled = .F.
Thisform.cmdAnterior.Enabled = .F.
Thisform.cmdProximo.Enabled = not Eof()
Thisform.cmdUltimo.Enabled = not Eof()
Thisform.Refresh
The code is fairly simple. With Go TOP, we move
the pointer to the top of the table. With Thisform.cmdPrimeiro.Enabled
= .F., we tell VFP to disable the cmdPrimeiro control of the Form in which
we are. The remaining lines are simmilar, just pointing to other controls. Finally,
we order the Form to update itself with the Thisform.Refresh
instruction. Remember: the table pointer was moved to other record, so we want
the shown values in the controls reflecting the currently selected value.
Now, fill the Click method of the cmdAnterior with the following code:
Skip -2
Thisform.cmdPrimeiro.Enabled = not Bof()
Thisform.cmdAnterior.Enabled = not Bof()
Thisform.cmdProximo.Enabled = not Eof()
Thisform.cmdUltimo.Enabled = not Eof()
If Not Bof()
Skip
EndIf
Thisform.Refresh
If you come from Clipper, you certainly already recognized the Skip command and
the Bof() and Eof() functions. the Skip command allow us to walk between records
(wiathout any argument, it advances 1 record, with a positive argument, it advance
that number of records, and with a negative one -as in the code above-, it moves
back the number of records indicated). The Bof() and Eof() functions test if we
are at the beggining or the end of the table, respectively.
Here is a trick: I'm initially walking back two records because I want that,
if we reached the first record, the "First" and "Previous"
buttons gets disabled, as it doesn't make sense leaving them enabled when we
are at the first record (if we are already in the first record, why should we
wish to go there again? And it is also impossible to go to the "previous"
record).
Let's go to the Click of cmdProximo button:
Skip +2
Thisform.cmdPrimeiro.Enabled = not Bof()
Thisform.cmdAnterior.Enabled = not Bof()
Thisform.cmdProximo.Enabled = not Eof()
Thisform.cmdUltimo.Enabled = not Eof()
If not Eof()
Skip -1
Else
Go BOTTOM
EndIf
No big difference... Just the fact that we're going forward. And at last,
the cmdUltimo button.
Go BOTTOM
Thisform.cmdPrimeiro.Enabled = not Bof()
Thisform.cmdAnterior.Enabled = not Bof()
Thisform.cmdProximo.Enabled = .F.
Thisform.cmdUltimo.Enabled = .F.
thisform.Refresh
Once again, very much like the rest, with the exception that we use now Go
Bottom to go to the end of the file.
The button to close the Form have to be named cmdFechar, and I think that
you already learned the code to close the Form, didn't you?
Execute the Form and experiment clicking the buttons. You'll see that you
can navigate between records. How many lines of code do you need to do the same
in Clipper?
Very good. I don't particularly like the way in which records are exposed,
because the user can freely alter them. I prefer to disable all the fields,
so the user can watch at their content, but not alter it. That leave us margin
to create a security system.
Let's disable all the controls at the Form's initialization. For that purpose,
double-click the Form and select the Init method. This method is associated
to the Form's Init event, and is triggered when the Form initializes. Later,
in the appropiate moment, we'll see the event execution sequence.
Complete the Form's Init method with the following code:
In this code, you see different things. At the first line we have a reference
to a "this" object. When we use the reference "This" we
indicate that we are accessing something on the object in question. So, in this
example, we are accessing the nStatus property of the Form. The nStatus property
is also a novelty, because it is not part of the native properties of the Form
base-class (we'll talk more about classes as the course follows). This is a
custom property that I created to keep a flag that will indicate the current
status of the Form. Look at the possible values for the nStatus property on
Table 1.
Table 1: Possible values to nStatus property.
Value
Status
0
Display
1
Append
2
Edit
In the following four lines of code we are just adjusting the Enabled property
of the navigation buttons. And next, another novelty: the With...EndWith structure.
See, we will need to make reference to Thisform for every control, as this is
were the controls are contained, right? Using the With Thisform command we can
access any of its controls, properties or methods just beginning with a dot
(.), because VFP knows that it have to complete the command line with Thisform.
What do we gain with that? Well, besides a cleaner code to read, we have a
smaller file, because we decrease considerably the typed text (when we have
bigger hierarchical structures, you'll realize this even more accurately), and
finally, a performance gain, because instead of searching all the hierarchy
until reach an object that we want, VFP already knows where the object is, and
then it starts its search from this point.
Don't get scared, as you'll get used to all this. You can execute the code
again now, and you'll see that all controls are protected, as they are disabled.
Now let's create the nStatus property. In the Form menu, select New Property...
Type the nStatus name, and in the Description field, assign it a description
like "Keeps the form's state". This description is shown in the Properties
Window.
Figure 16: Creating a New Property.
Now's the time to insert another three essential buttons to allow us Add,
Edit, Save and Delete records, and cancel an operation as adding or editing.
As the buttons Save and Cancel only make sense once the Add or Edit buttons
have been pressed, we'll use each button to do two tasks.
So add another three buttons and name them cmdNovoSalvar, cmdEditarCancelar
and cmdExcluir. For the Picture property of each one select the images "Novo.bmp",
"Editar.bmp" and"Excluir.bmp" respectivelly.
In the Click method of the cmdNovoSalvar button, enter the following code:
This piece of code is the longer one by now, but anyway, it is still quite
easy- The idea is simple: Once the cmdNovoSalvar is pressed, we check for the
nStatus property to know the state the Form is in. If it is in Display state,
then it means that we are trying to add a record. The first thing that we do
in this case is to update the nStatus property, setting its value to 1 (Add).
Immediately, we change the button's image to show a "diskette" representing
the button is now for Save, and we also change the image of the cmdEditarCancelar
button to represent a "Cancel" type button.
After that, you can see we are accessing a Form property called nReg that
doesn't exist, and we have to create. This property should store de current
table position (the Recno() function returns the physical number of the record
on the table). Now that you already know how to do it, create this Form variable.
The reason for the existence of the nReg property is that, if a user gives up
about adding a record, we'll need to know which record she was looking at before pressing
the "Add" button, and then go back to that record.
Then comes the Append Blank command (also known for the Clipper people), that
adds a blank record to the table.
And then we simply disable the remaining buttons, because doesn't make sense,
for example, to click the "Next" button when we are in the process
of editing a record.
Further on, if nStatus is not 0, either being 1 or 2, we are in the middle
od and add or an edit. In such case this button have the responsability of saving
a record (being an add or edit). Beside the change of images of the buttons,
and the enabling of the disabled buttons, we use the function TableUpdate().
Let's talk a bit about that.
In the Clipper ages, a good deal of the table corruption problems came from
the fact that we were all the time reading and wroting right to the file. If
the system was abruptly shut down, because of a bug or a simple power outgage,
while writing a record, it was very likely that our file got corrupted.
In VFP we have the concept of Buffering. With buffering, we no more read and
write all the time from the table, and instead, we start working with records
right into the memory of our computer. That way, the reading happens just once
for bringing the record to memory and the subsequent readings are made right
over the computer memory. And the writing happens when the TableUpdate() function
is executed.
The TableUpdate() function's purpose is to tell VFP: "Paste the records
that are in memory (buffered) and wrote them to the source table".
For buffering to be working, we need to activate it. There are several ways
to do this, but we would use the simplest one now. Go to the Form's BufferMode
property and set it to 2 - optimistic. That will instruct VFP to just block
a record when the TableUpdate() command is executing. The buffering concept
is wider and more complex than that, but to avoid scaring you, we'll stop here,
and we'll go deeper later in our course.
Now let's go to the Click method of the cmdEditarCancelar button:
Very simmilar to the code of the Save button, isn't it? The only big news
are two things. First, the TableRevert() function. It does the opposite that
TableUpdate(): simply discards the changes made over the record (or records,
depending on how the buffering is set - we'll talk more abpout that in the appropiate
moment). That way, if the user cancels and addition, the record added is cancelled,
or if she cancels an edition, the changes made to the record are discarded.
The Go Thisform.nReg comand just instructs VFP to move the record pointer
to where it was before the Add or Edit operation started.
And finally, go to the Click method of the Delete button:
If MessageBox("Do you want to delete this record?",4+32+256,"Atention") = 6 && Yes
Delete
TableUpdate()
MessageBox("Record deleted!")
Skip
If Eof()
Go bott
EndIf
Thisform.Refresh
EndIf
Let's understand the code: first we use the MessageBox() function to ask the
user if she really want to delete the record (see more details about this function
in the VFP Help). If the user clicks the Yes button, this function returns the
value 6 (if the No button is clicked, we simply ignore the rest).
If the action is confirmed, we perform the Delete command to mark the current
record for deletion, and then the TableUpdate() function to send the update
to the table. After telling the user the record has been deleted, we just advance
to the next record in the table (if there is not a "next" record,
we just go to the last record).
At this moment you can execute the Form, and test all the buttons. Add records,
edit and delete some, make all the common operations in this type of screens.
Figure 17: Our Data-EntryForm.
Public Session and Private Session
Try to execute two instances of the Form: just go to the Command Window and
execute the command Do Form Clientes. twice.
Now, in one of the instances of the Form, advance to any record (10 records
forward, for example). Then go to the other form being executed, pay atention
to the Customer code in which you are, click the Edit button, and finally, click
on the Customer code. You'll see that the control is updated and it shows the
customer code that's in the other Form.
But why this happens?!?
That happens because we are using a public (default) data session for the Form. That
way, both are using the same "copy" of the table in which it is based.
Then, when we move around the records in one form, the pointer is changed also
in the other one. In some circumstances this is exactly what we want, but not
in this case for our Form. What we need here is that each one of the Forms have
a Private Data Session. This means that one instance of the Form has nothing
to do with the other, and they are totally independent.
To configure the Form to use a Private Data Session, close the two Forms,
and set the DataSession ro the value 2 - Private Data Session. Create two instances
of the same Form again (with the Do Form command), and make the test. you'll
see that each Form is really independent from the other.
Why create Properties instead of variables?
Maybe this question is in your head. The reason is: Imagine that instead of
a property nStatus for the Form we had created a public variable called gnStatus.
Then, as in the last example, we execute two instances of the Form. If in one
of the Forms we make click in the Edit button, and in the other one we click
Add, how can we set the value of the gnStatus variable???
Each Form can have its own state, and for that reason, a variable would just
cripple it all. That's why we create Form properties, allowing each Form to
keep its own state, absolutely independent from the other Form (or from all
we would have in execution).
Another reason: the life scope of properties is exactly the life scope of the
object. Thus, once an object is destroyed, its properties are automatically
destroyed too. With that, we don't need to remember to release memory variables
all the time.
During all the course we will be creating Properties and Methods, and the
concept will get stronger on you.
Conclusion
In this chapter, you learned the basic principles for Form creation, indeed
creating the most common Form in our applicactions, a Data-Entry Form. If you
came from Clipper, you can be a little lost, but without any doubt, you already
saw that with many fewer lines of code we get the results that in Clipper took
us hundreds of lines. In the next chapters of our course, you'll learn to decrease
even more the lines of code needed to perform some tasks, and fundamentally,
you'll learn to reuse code. The world of application development will never
be the same for you!