Subject |
Re: Form to Form |
From |
Lee Grant <camilee@nospam.comcast.net> |
Date |
Mon, 12 Jun 2023 02:02:13 -0400 |
Newsgroups |
dbase.getting-started |
Attachment(s) |
FORMVARS.HOW |
Peter,
Well, I think because of some of your lookup routine and also the second
form having it's own query, using the _app variable to interchange info
had it's own issues, and that's why I was a little hesitant to get into
changing someone else's code. :(
Anyway, just a suggestion before I do this, but if you haven't read
FORMVARS.HOW yet, I suggest you check it out and read it completely. The
communication between forms is built up in that article from the top to
the bottom where it goes through actually implementing the
communication, and I'll include a copy of that at with this message for
you to download and read if you don't already have it.
What I wanted to do here, was show you the pertinent parts of my own
program that uses multi form communication of parent(prosheet) query's
being used by the child(TLF1) form. The first funtion/method was the
parent form opening where I created an instance of the child form in memory:
function form_onOpen()
set procedure to TripList.wfm
form.TLF1 = new TripListForm() //create instance of second
form in memory
form.TLF1.mdi = false //must be false for modal form
form.TLF1.parent = this //makes it possible for TripListForm to
write back to this form if needed
//shell(false)
return
Then in the form, I have a button that I can use to create a new
TripList data entry for the database, using an external form to enter
the information. The comments in it should give you an idea of what I
was doing, but the pertinent info is at the end where I manually link
the entryfields that are in the child form to the query of the parent's
query, prior to opening the form.
function PBPRONEWTRIP_onClick()
/* Check to see if a Pro Number has been assigned, entered in the
prosheet database
before allowing a trip sheet entry to be created in the triplist
database and if not
show an error box stating that.*/
tlpn = form.prosheet1.rowset.fields["cprono"].value
if tlpn == NULL
msgbox("There is no PRO# So you can't add a triplist entry", "No
PRO Number", 0)
return
else
//change any properties before launching second form triplist.wfm
form.TLF1.prosheet1 = form.prosheet1 //prosheet rowset via query
form.TLF1.triplistvisual = form.triplistvisual // triplist rowset
visual representation via query
form.TLF1.triplistdata = form.triplistdata //triplist rowset
data representation via query
endif
/* Now check to see if there are any trip sheet entries for the
current Pro Number,
and if there isn't, create a new triplist record with the current
Prosheet Pro Number
and empty fields or setup the datalinks to access the current
record and connect the
other datalinks between forms before opening the triplist form to
enter new or edit a current
trip sheet entry.*/
if empty(form.triplistdata.rowset.fields['cprono'])
form.TLF1.EFtripcprono1.value = tlpn
else
form.TLF1.EFTRIPCPRONO1.DATALINK =
form.triplistdata.rowset.fields["cprono"]
form.TLF1.EFTLSTATUSORDER.DATALINK =
form.triplistdata.rowset.fields["TLStatOrder"]
form.TLF1.EFTRIPDATE1.DATALINK =
form.triplistdata.rowset.fields["TripDate"]
form.TLF1.EFTRIPSTATUS1.DATALINK =
form.triplistdata.rowset.fields["TripStatus"]
form.TLF1.EFTRIPSTATE1.DATALINK =
form.triplistdata.rowset.fields["TripState"]
form.TLF1.EFTRIPCITY1.DATALINK =
form.triplistdata.rowset.fields["TripCity"]
form.TLF1.EFTRIPFUELQTY1.DATALINK =
form.triplistdata.rowset.fields["TripFuelQty"]
form.TLF1.EFTRIPVENDNAMEADDRESS1.DATALINK =
form.triplistdata.rowset.fields["TripVendNameAddress"]
form.TLF1.readmodal()
endif
return
I hope this helps you see how you might want to structure your form
communication, but I'll include the document that shows how to set these
up, which I believe was written by Alan Katz, who ushered dBASE into the
OOP world.
Lee
On 6/11/2023 10:46 AM, Peter wrote:
> Peter wrote:
>
> Thank you, Lee, for your input. Somewhere in prior replies, I was advised NOT to use the same query in main and child forms. I was going to ask about that because, in one of Mervyn’s recent replies, he used the same query name (Customers1). If you check Function Pushbutton_onClick from 6/2/23, Mervyn shows exactly how he coded the passing of pat_no value from grid to edit form. But….
>
> You have discovered the precise problem I was having. If you select the first row in main form (Harry Potter), the edit form opens correctly. However, if you select any other person, either by pat_no or last name, the edit form opens with new pat_no but uses first row information to display datalinked entryfields in edit form. The rowset is not changed.
> It was this issue why Mervyn was advising me to sent all the code so he could debug.
> Peter
<snip>
| --------------------------------------------------------------------
DISCLAIMER: the author is a member of TeamB for dBASE, a group of
volunteers who provide technical support for Borland on the DBASE
and VDBASE forums on Compuserve. If you have questions regarding
this .HOW document, or about dBASE/DOS or Visual dBASE, you can
communicate directly with the author and TeamB in the appropriate
forum on CIS. Technical support is not currently provided on the
World-Wide Web, via the Internet or by private E-Mail on CIS by
members of TeamB.
.HOW files are created as a free service by members of TeamB to
assist users to learn to use Visual dBASE more effectively. They are
posted first on the Compuserve VDBASE forum, edited by both TeamB
members and Borland Technical Support (to ensure quality), and then
may be cross-posted to Borland's WWW Site. This .HOW file MAY NOT BE
POSTED ELSEWHERE without the explicit permission of the author, who
retains all rights to the document.
(c) 1995 A.A.Katz, All Rights Reserved
--------------------------------------------------------------------
FORMVARS.HOW
by A.A.Katz
10-14-1995
Revised 10-28-1995
HOW TO - Vars, Params and Forms
How to (and how not to) use variables and parameters
with forms. How to get forms to "talk" to each other.
-----------------------------------------------------------
Contents: The Problem With Variables
When You -Can- Use Variables
The Problem With Parameters
When You -Can- Use Parameters
The Problem With .WFMs
The Solution: Properties
The Object Reference
Where To Store the Object Reference
Where To Instantiate Your Forms
Getting Forms to Talk to Each Other
Summary
Sample Code - Parent "reads" Child
Sample Code - Child Updates Entryfield in Parent
-----------------------------------------------------------
Introduction
------------
The primary purpose of this How-to is to help you, the Visual dBASE
programmer, manage the data you send to or share between forms.
I suppose the easiest and fastest way to help you get your code up-
and-running would be to post a set of rules rather than deal with a
lot of theory. But, unless you really understand the interaction between
variables, parameters, properties and objects, you are bound to "hit the
wall" sooner or later.
Probably sooner.
I am assuming that you are a relatively new Visual dBASE programmer and
that you have some experience coding in a procedural language. Even if you
are an experienced object-oriented programmer, don't skip over the
"theoretical sections" at the beginning of this How-To. The Visual dBASE OOP
implementation may be very different from whatever OOP platforms you used
in the past.
The Problem With Variables
--------------------------
Variables and Object-Oriented Programming do not coexist particularly
well.
Variables are declared in procedures. Their visibility is defined by
procedures. Their lifespan is determined by procedures.
Consider a private variable:
1. It is visible in the declaring procedure and all subroutines
called from the declaring procedure.
2. It ceases to exist when you return up the procedure chain from
the procedure in which it was declared.
The visibility and the lifespan of a private variable is totally depen-
dent on the relationship between procedures.
In OOP, that relationship does not exist because Objects are not
procedures.
Objects are independent entities floating around your user's screen
or lurking in memory awaiting calls to their methods. They are not
subroutines. You cannot "call down" to an object, nor do objects
"return" to the procedures in which they were created.
In truth, objects do not get "called" at all. They are "Instantiated",
created on-the-fly from a Class "blueprint". Each actual, functioning
object is said to be one "instance" of a Class.
You can create any number of objects from a single Class: ten different
Entryfields from Class Entryfield, ten different forms from
Class Form - each with different attributes and behaviors.
And it's this potential for multiple instances of a Class that poses the
biggest problem using variables with Forms. If you enter data in
Entryfield1, you certainly don't want it to show up in Entryfield6.
If you change the value of CustNo on Form 1, you certainly don't want
it to change the value of CustNo in Form 2.
To prevent one object from overwriting the data of another, each object
is "encapsulated" - isolated and protected from all other objects
and instances.
The downside is that encapsulating objects prevents variables from being
seen or changed from any other object. A variable declared in a method
of Pushbutton1 is not visible to Pushbutton2. It is not "in scope".
A variable declared in a procedure cannot be seen by a Form - eveny20a
Form launched from that very same procedure. A variable declared in a
Form's OnOpen event is totally invisible to every other object on the
same Form.
Hint: When learning Object-Oriented programming, there's a
tendency to fall back on procedural techniques to try to
manage these new challenges. It is not uncommon for new
OO programmers to try to use Public variables to circumvent
the restrictions imposed by encapsulation. Don't do it.
Public variables have their own special problems in OOP.
They can be referenced from anywhere in your program. They
can also be overwritten from anywhere in your program. A newer
instance of an object can easily overwrite a variable declared
in an earlier instance.
Avoid using Public variables with Forms and other objects.
When To Use Variables
---------------------
Using variables or not using variables is not a "religious" issue.
Variables are not yet history. They still have great value - within
your objects' methods.
"Methods" are procedures and functions encapsulated within an object.
They provide the "behavior" of your object. Despite their new name and
their link to their parent object, methods are still procedures and
functions. They will scope variables exactly like any other procedures
and functions.
Feel free to implement variables in methods:
Procedure Pushbutton1_OnClick
Local cVar
cVar = substr(Form.Combobox1.Value+space(35),1,35)
Sele MyTable
Seek cVar
Or in macros:
Procedure PushButton1_OnClick
Private n,cNum
For n = 1 to 3
cNum = str(n,1)
Sele table
Repl Field&CNum. with Form.Entryfield1.value
EndFor
The Problem with Parameters
---------------------------
Parameters are data sent directly between processes (in Visual dBase,
processes are procedures, objects and functions). Unlike variables,
parameter data is sent explicitly. The target object or procedure must
have a structure built-in to receive this data and use it.
Some processes already have these structures pre-defined. Space() takes a
specific type and number of parameters, as do Keymatch(), Recno() and all
the other built-in functions. You cannot add a parameter to a built-in
function.
The same is true for standard built-in Classes. The only parameters that
you can send to an object instantiated from a Visual dBASE Standard Class
(like Forms, Menus, Pushbuttons and Entryfields) are the pre-defined
parameters built in by Borland. In short, you cannot pass your data as
a parameter to a built-in Class or even to a Class - derived - from a
built-in Class.
Hint: Custom from-scratch Classes are a notable exception. These
Classes, like UDFs, do not have built-in parameters. Parameters
are specified and used by you, the programmer. You can send any
data you wish to a from-scratch Class with the following syntax:
Class MyClass(Var1, Var2, Var3)
But you cannot send your data as parameters to a custom Class
derived from another:
Class MyButton(f,n) of Pushbutton(f,n)
The example above has two parameters built in: Form (f) - so the
custom Class knows what Form is its parent, and Name (n) - so that
an object derived from the Class can be referenced by name. You
cannot add additional parameters to Class MyBbutton. They will not
be accepted.
Even if you could pass a parameter to a Form, it wouldn't serve any
purpose. Once a parameter is passed, it becomes a variable. As we noted
earlier, encapsulation will probably render your variable useless. It
wouldn't be in scope anywhere you'd need it.
When You Can Use Parameters
---------------------------
You can use parameters freely, like variables, in the methods of your
forms. Once again, methods are, at heart, just procedures and functions.
They receive parameters exactly the same way non-OOP procedures and
functions do.
Class::SetFields(.t.) &&call a method of the form
Procedure SetFields(lSwitchOn) && Param received by the method
Form.Entryfield1.Enabled = lSwitchOn
Form.Entryfield2.Enabled = lSwitchOn
Form.Entryfield3.Enabled = lSwitchOn
Form.Combobox1.Enabled = lSwitchOn
You can send parameters from any object on a Form to any other method,
procedure or function. You can send variables, calculations, or even
properties of your objects - by reference or by value.
Procedure Pushbutton1_OnClick
Form.Entryfield1.Value = Upper(Form.Entryfield1.Value)
&& Property sent to built-in function
MyFunc(Form.Entryfield1.value-Form.Entryfield2.Value)
&& Calculation sent to UDF
CLASS::Recalc(Form.Entryfield1.Value)
&& Property sent to Class Method
The Problem With the .WFM
-------------------------
One thing you cannot do is send parameters to a form when you call a .WFM:
Do My.WFM with .f., MyData && Will not send parameters!
Nor can you declare a variable within your .WFM that will get anywhere near
the object defined by its Class code.
The .WFM is a new file format that first appeared in dBASE 5 for Windows.
It is a hybrid - both an executable procedure file and a repository for
Class definitions. This multi-purpose file format can be confusing
when you try to visualize the flow of your data.
The following chart may help in sorting out the contents of the .WFM and
the flow of code that executes when you issue: Do My.WFM.
The .WFM is composed of five sections:
Section | Used For
------------------------------------|----------------------------------
1. The Header - everything above: | Any code entered here is executed
**END HEADER Do Not Remove | first when the .WFM is called.
this line |
| Code in this area is NOT over-
| written by the Form Designer.
|
2. Instantiaton code: | This code is executed second
Local f | when the .WFM is called.
f = new Myform() |
If bModal | It creates an object from the
f.MDI = .f. | Class definition in the .WFM
f.ReadModal() | and stores its address in a
Else | local variable (f).
f.Open() |
Endif | Code in this area WILL be over-
| written by the Form Designer
|
3. Class Constructor code: | This code DOES NOT EXECUTE
Class Myform of Form() | when the .WFM is called
Define... |
Define... | This is the "blueprint" that
Define... | will be used when an object is
| created using "New Myform()".
|
| You may add objects (DEFINE),
| change values of properties
| (Height 1.06), add function
| pointer calls (Class::Form_onOpen).
| You may NOT add custom properties
| or executing code in this area.
| These will be overwritten by the
| Form Designer.
|
4. Methods - everything after the | This code DOES NOT EXECUTE
last DEFINE: | when the .WFM is called.
|
Procedure FORM_OnOpen | This area contains the procedures
... | and functions that describe the
Procedure Pushbutton1_OnClick | behavior of the object. (Methods)
... |
| This code will NOT be overwritten
EndClass | by the Form Designer unless you
| bring it up and change it in the
| Procedure Editor.
|
5. General | This code DOES NOT EXECUTE
| when the .WFM is called.
|
| This section is useful for storing
| UDFs or procedures that are not
| methods of your Class, but get
| called from within the Class.
|
| Code in this area will not be over-
| written by the Form Designer.
--------------------------------------------------------------------------
You will note from the chart that the Class...Endclass code never executes.
It's read by Visual dBASE each time an object is created from this Class,
but is ignored completely when the .WFM itself is run.
Which makes perfect sense if you understand that, in the same way a
blueprint is not a house, a Class is not a Form. An object comes into
being only when it is instantiated - created from the Class blueprint.
Therefore Variables declared in your .WFMs or parameters sent to your
.WFMs never make it to the actual Form. The Form doesn't yet exist.
Hint: There is no reason why a Class definition has to be
in a .WFM. It can be in any disk file as long as it's loaded
into memory with SET PROCEDURE.
The .WFM format exists only as a convenience to make it easier
for you and the Navigator to find your Form files.
In fact, the following sections will demonstrate that you can even
put instantiation code in other objects. It's a common Visual dBASE
convention to instantiate a child Form from within its parent
without ever calling the .WFM.
The Solution: Properties
------------------------
Object-oriented languages have a new data structure called a "Property".
Properties are data-containers similar to variables. But, unlike variables,
they are attached to an object. Properties are the - data - of the object,
they define the object's attributes:
This.Height = 27
This.ColorNormal = "B/W"
This.StartingNum = 1
This. MDI = .f.
You should think of Properties as variables that are scoped to their
parent object and addressed directly via their "family tree".
Property: Height (numeric)
Parent: Pushbutton1
GrandParent: Form
Form.Pushbutton1.Height
Property: Height (numeric)
Parent: Form
Form.Height
This family-tree style addressing is called "Dot-Notation". Using Dot
Notation, any object can be referenced or changed from anywhere in your
program - from any other object, procedure or function - as long as
you know your object's "family tree".
Visual dBASE even provides generic "placeholders" to substitute for
objects whose "lineage" is not known at design-time.
This --- is the placeholder that substitutes the address of
the current executing object:
Procedure FORM_OnOpen
This.Height = 20 && "This" references the Form
Procedure Pushbutton1_OnClick
This.Height = 2 && "This" references Pushbutton1
Form --- is the placeholder that - always - refers to the
parent Form of the current object:
Procedure FORM_OnOpen
Form.Height = 20 && References the Form
Procedure Pushbutton1_OnClick
Form.Height = 20 && Still references the Form
Directly-addressed data has many advantages over floating variables with
their procedural dependencies. Properties make data much easier to scope
and your code much easier to read and debug. Properties are both more
explicit and cleaner than variables.
Hint: Use properties anywhere you can instead of variables.
You can even emulate Public variables by attaching custom
properties to the application object (_app) built into Visual
dBASE. "_app.MyCustomProperty" is visible from every procedure,
function and object within your program. But use these
global properties with care - you can overwrite a property
attached to _app just as easily as you overwrite the value
of a Public variable.
Visual dBASE supports "dynamic properties". Many OO languages do not.
Dynamic properties are properties added on-the-fly after an object has
been created (as opposed to being defined in the original Class).
This capability is extremely valuable for "interprocess communications",
the sending and receiving of data between two objects or between
procedures and objects. (See "Getting Forms To Talk To Each Other" later
in this HOW TO)
Hint: You cannot create dynamic properties in the Constructor
(Define) section of the Class code. You have two possible places
where you can add dynamic (sometimes called custom) properties:
1. In the OnOpen event of your form; or 2. After instantiation,
but before you Open() your form.
For esthetic reasons, it is almost always better to add a dynamic
property before you Open() your Form. If you use the OnOpen event,
your form may just sit onscreen doing nothing while the new prop-
erties are created. If your dynamic properties affect the display,
you may see an extra redraw before your Form stabilizes.
By creating your properties before your form's Open(), everything
is set up before the Form is drawn. Makes for a much cleaner Form
paint.
1. Created in the OnOpen event of a Form
Procedure Form_OnOpen
This.Firstname = 'Alan' && Create dynamic property of
&& a Form in its OnOpen Method.
Form.Pushbutton1.MyData = 'Alan'
&& Create dynamic property of
&& an object on a Form.
2. Created before the form is Open()ed
Procedure CallNewFormButton_OnClick
Set Proc to New.Wfm Additive
Form.oChild = New NewForm()
Form.oChild.FirstName = 'Alan'
&& Create dynamic property of
&& a Form before Open()ing
Form.oChild.Pusbutton1.MyData = 'Alan'
&& Create dynamic property of
&& an object on a Form
&& before Open()ing.
Form.oChild.Open()
The Object (Instance) Reference
--------------------------------
We've already established that you can reference any property of any
object if you know its "family tree". Within a form, that's a pretty simple
proposition. The built-in variables "This" and "Form" make it easy to
identify the current object and the form that contains it.
However, if you're looking at an object from outside the object it's
not quite so simple.
It's all a matter of perspective.
If you're standing in my kitchen and want to know its color, you only
have to ask:
What color is - this room - ?
But if you're at my office and you want to know the color of my kitchen,
you're going to have to give some more information to pinpoint which
particular kitchen you're referring to:
What color is - Alan's kitchen at home- ?
The information required to reference the color of my kitchen varies
depending on where you're standing. The same is true of referencing
properties on a Form.
To reference a Form or an object on a Form from within the Form, you
use "This" or "Form", as in "This Kitchen".
To reference a Form from anywhere outside itself, you must know the unique
identifier of that particular Form instance. You need to know the "address"
of the object, as in "Alan's kitchen at his house in Upstate New York"
In Visual dBASE, every object has a unique address called the Object
Reference. Each pushbutton has its own Reference, distinct from every other
pushbutton. Each instance of a Form has its own Reference, distinct from
every other instance of this Form and every other Form.
Hint:This reference is sometimes called an Object Reference,
sometimes called an Instance Reference - they mean exactly the
same thing.
The Object Reference is returned automatically by Visual dBASE whenever an
object is instantiated.
oMy = New MyForm() && Create new object, store address in
&& Object Reference Variable "oMy"
The Object Reference, which is an entirely new type of data (type "O"),
can be passed, copied and manipulated just like strings, dates, logicals,
and numerics. It can also be stored just like the other data types:
Variable - You can store your form's Object Reference in a variable, in
which case it's called the Object Reference Variable.
Private oMy
oMy = New MyForm() && Reference stored in variable "f"
oMy.Open()
Property - You can store your Object Reference in a property of
another object:
Form.oChild.oMy = New MyForm() && Reference stored in Form Property
Form.oChild.oMy.Open()
Global Property - You can store it in a dynamic property of the
application itself:
_app.oMy = New MyForm() && Reference stored in property
&& of _app
_app.oMy.Open()
Array Element - You can store it in an array element:
aForms[1] = New MyForm() && Reference stored in array element
aForms[1].Open()
The Object Reference is your key to manipulating your data, controlling
your forms, to gluing your entire program together. Taken together, your
Object References are the outside perspective on all your individual
objects.
Hint: In special cases, the reference can be stored using the name
property of the object rather than an external Object Reference
but only when the object is "contained" in a Form:
Define Pushbutton Pushbutton1 Of This
&& Reference is now Pushbutton1
The NEW command always returns an external Object Reference.
The DEFINE command uses the Name Property as the object's
Reference.
To read a property of a Form from outside of the form, use the Object
Reference to establish the "family tree":
Perspective: From outside of the Form:
oMyForm.Myvar = 'Hello'
oMyForm.Pushbutton1.Height = 2
Perspective: From within the Form:
Form.MyVar = "Hello'
Form.Pushbutton1.Height = 2
The Object Reference can also be used to determine the status of a Form.
To test whether a variable is an Object Reference:
? Type('form.oMyForm') && "O" if object exists
To test whether an object is instantiated:
? Empty(Form.oMyForm) && If Empty() returns .t., object
&& is not instantiated
To test whether a Form is Open:
? Form.oMyform.wHnd && If "handle" is greater than 0,
&& the form is open
Hint: Type() returns "O" on an Object Reference even
after its object is released. The Object Reference is still
an Object Reference even though it no longer contains a valid
object.
To make it easier to test the status of Forms, You may want to
get rid of the Reference completely when you release an object,
by changing the value of the variable, property or array:
Form.oMyForm = '' or
Form.oMyForm = .f. or
_app.aForms[1] = ''
Where To Store The Object Reference
-----------------------------------
The answer to this question is a good topic for a lengthy book.
The structure you use to store your Object References depends entirely
upon the needs of your application. Selecting and managing these
structures should become an integral stage of all future program
design. Some Visual dBase programmers create their own custom Class
to manage Form References. Whether you create a custom Class or not,
you must establish a strategy so that you'll always know how to
access any Form in your program.
It is imperative to store your references in -accessible- data structures.
If you can't find the variable, property, or array containing your form's
address, you cannot access your form from outside. Your Object Reference
MUST BE IN SCOPE!
Here's a few suggestions. They are just suggestions, not rules.
Your final structure must be determined by your application.
Consider them guidelines:
-----Single Instances
If you are creating SDI Forms - Forms of which there will be only one
single instance - you may want to use a property of the application
object to store your references:
_app.oCust = New CustomerForm() && Instantiate.
_app.oCust.Pushbutton1.height = 2 && Reference.
_app.oCust.Open() && Open.
This is similar to storing your Object Reference in a Public variable.
-----Multiple Instances
If you are writing an MDI program - where there can be multiple instances
of a Form, I strongly suggest that you store your Forms' Object References
in an array. You have no way of knowing at design time how many instances
of your Form the user will have onscreen simultaneously. An array is a
structure that can grow to store each new Object Reference as the user
launches each new Form.
_app.aForms = New Array() && Declare array.
_app.aForms.Grow(1) && Add element for each
&& instance.
_app.aForms[1] = New CustomerForm() && Instantiate-
_app.aForms[1].Pushbutton1.Height = 2 && Reference...
_app.aForms.Open() && Open.
------When calling a Modal Dialog
If you are "calling" a child modal dialog (like a customer lookup Form), I
suggest you store the child's Object Reference in a custom property
of the parent Form.
Form.oChild = New CustomerForm() && Instantiate into
&& a Custom Property.
Form.oChild.Pushbutton1.height = 2 && Reference.
Form.oChild.ReadModal() && Open
Where to Instantiate Your Form
------------------------------
If you are calling your forms with "Do My.WFM", you're probably using the
default instantiation code written by the Form Designer to create and open
your Form:
Local f
Param bModal
f = New MyForm()
If (bModal)
f.MDI = .f.
f.ReadModal()
Else
f.Open()
Endif
This code is wonderful for testing programs, and it does encapsulate the
object it creates, protecting it from all other forms or instances. But
it has a huge drawback. Its Object Reference Variable, "f", is Local!
As a Local variable, it cannot be addressed from any other Form, object,
procedure or function in your program.
Hint: You should get into the habit - from the beginning -
of writing your own instantiation code. Let the Form Designer
use its default code for visual design sessions. But do not
rely on this code to launch your Forms from within your program.
You never know when you may need to address a Form. With the
default Visual dBASE code, you can't.
Once you hone your strategy, you may want to build it into
an object - a custom Form Manager Class to automate your
Form instances. Or you may wish to browse the Compuserve
Forum for a custom Class you can adapt to your own style
and strategy.
So if you can't use the default Form Designer code, where do you put
your "launch" code?
There are two options:
1. Instantiate your Forms from within the .WFM
If you check the .WFM chart in a previous section of this HOW TO,
you'll note that there is a Header code area above the line:
**End Header Do not Remove This Line
This area of the .WFM is for your code. The Form Designer will not
overwrite Header code. You may use this to instantiate your form
as long as you remember to add a "Return" as the last line. If
you don't, the .WFM will continue right on, executing the Form
Designer instantiaton code, opening your form twice:
My.wfm:
Set Procedure to Program(1) && Explicitly load this
&& procedure into memory to
&& ensure that My.WFM will be
&& available after the "Return".
_app.oMy = New MyForm() && Instantiate Form
_app.oMy.Open() && Open Form
Return && Go back.
To launch a Form using Header code, call: DO My.WFM
2. Instantiate your forms from outside the .WFM.
There are many places from which you can instantiate Forms
directly without calling the .WFM:
A menu OnClick event
A pushbutton OnClick event
A Procedure
A UDF
A Custom Class
When you instantiate directly, make sure that you've
explicitly loaded the .WFM file containing the Class Definition
for your Form:
Procedure MyMenu_Run_Form_OnClick
Set Proc to C:\Mydir\My.WFM Additive
Form.oChild.MyForm = New MyForm()
Form.oChild.Open()
It's as simple as that. Load the Class file into memory,
instantiate the object into a property of the current object
and then Open() the form using the Object Reference.
Controversy. There is a great deal of discussion as to whether
instantiation code should be put at the top of each .WFM or
sprinkled throughout your program, as needed.
The "WFM Advocates" rightly point out that using the .WFM stores the
instantiation code in the same file as the Class definition, which offers
a certain kind of logical encapsulation. More important, you never have to
go looking for the instantiation code, you always know where it is.
However, I am a great advocate of instantiating your Forms from outside
the .WFM for a number of reasons:
1. The instantiation code is not a part of your object, any
more than a call to a UDF is a part of the UDF. When you
write User-Defined Functions, you certainly spread their calls
around as needed.
2. Instantiating in the header of the .WFM makes reusability
more difficult. If you need to set properties on a form each
time you instantiate - like title or color or even datalinks -
you will have to send that information to the .WFM as
parameters and convert them to properties in your Header code:
Setting properties in a direct instantiation:
Set Procedure to My.WFM Additive
Form.oChild = New MyForm()
Form.oChild.Titletext = 'Hello'
Form.oChild.Open()
To perform the exact same instantiation from the Header area
of the .WFM:
Do My.WFM with .f., 'Hello'
My.Prg:
Param bModal,cTitle && Param statement
_app.oChild = New Myform() && Instantiate
If type('cTitle') = 'C' && If this param is sent
_app.oChild.titletext.text = cTitle
&& Set text property
Endif
_app.oChild.Open() && Open Form
Return && Return
You have to test, as above, for each parameter sent to your
.WFM. In addition, calling the .WFM makes it more difficult
to "attach" your Form's Object Reference to the calling
object. In order to do that, you will have to send the
parent Forms's Object Reference as yet another parameter.
For the reasons above, I recommend that you do not instantiate
your Forms using the Header of the .WFM. However, this is indeed
just one opinion. You should use whichever method fits your
application and your personal coding style.
Getting Forms To Talk To Forms
------------------------------
We've defined Interprocess Communications earlier: Sending data back
and forth between objects, procedures and functions. In Visual dBASE
(and most OOP languages), you will communicate between Forms and/or
procedures by setting or reading the values of a Form's properties from
outside that Form.
There is a virtually limitless number of possible "modes" of communicating
between processes. The three most common you will run across in Visual
dBASE programming are:
1. Parent Form setting or reading a value in its child Form.
2. Child Form setting or reading a value of its parent Form.
3. Form calling a procedure which must read or change a value
of its calling Form.
------From Parent to child.
This is the easiest model because all communication is done from the
the parent Form, which already knows both its own Object Reference and
its child's Object Reference.
Procedure ChildButton_OnClick
Set proc to Child.Wfm Additive && Load procedure.
Form.oChild = New ChildForm() && Instantiate child
&& into a custom property
&& of the parent Form.
Form.oChild.Pushbutton1.height = 2 && Set property of child.
Form.oChild.CustTotal = Form.Entryfield1.value
&& Create dynamic property
&& of child to "send"
&& data.
Form.oChild.ReadModal() && Open Child
Form.Entryfield1.Value = Form.oChild.CustTotal
&& Read back from
&& the child Form.
Close Proc Child.Wfm && Unload Procedure.
------From Child to Parent
This mode is different from the previous in that the child does not
know the parent's Reference. You have to "send" the parent's Object
Reference to the child in a dynamic property:
In the parent Form:
Form.oChild = New CustomerForm()
Form.oChild.oParent = Form && Create a dynamic property in
&& the child Form that stores
&& the parent's Object
&& Reference (Form).
In the child Form:
Form.oParent.Pushbutton1.Height = 2
&& Set parent Form's property
&& from within the child.
Form.oParent.CustTotal = Form.Entryfield1.value
&& Creating a dynamic property
&& of the parent with a value
&& from the child.
------Forms and Procedures/Functions
Sometimes you want an outside procedure or function to be able to change
a property of a Form. This is accomplished, like the Child-to-Parent example,
by sending the Form's Object Reference as a parameter:
In the Form:
Procedure ProcessButton_OnClick
Do Process with Form &&Send Form Reference as a
&¶meter
In the Procedure:
Procedure Process(oForm) &&Received Reference as a
&&Parameter
oForm.Text1.Text = 'Processing item # 1'
&&Updates Form using the
&&Reference to address the
&&Text1 object from within
&&The external procedure.
Hint: Remember the "perspective" discussion. You can
use the "placeholders" "Form" and "This" instead of the
Object Reference Variable when calling an outside procedure
or function from within a form. As we do in the example
above.
You can apply the previous examples to all kinds of situations where
forms talk to forms, UDFs update their calling Forms or objects talk to
other objects in Custom Classes. As long as you have an external Object
Reference stored in an accessible data structure, you can pass it back
and forth as a property of parent and child Forms or as a parameter of
a UDF. And once you have access to the Object Reference, you can change
any property from anywhere, run any method from anywhere. You have
complete control of your Forms.
Hint: You will remember that we stated previously that
"objects do not return" as procedures do. That statement
was accurate. However, there is one important case in which
objects emulate "return" behavior - a Form opened with
ReadModal().
ReadModal() causes all program execution to halt and focus
to be given exclusively to the resulting modal dialog. The
user cannot click anywhere else, no other Forms can be
opened, no other procedures run until the modal Form is closed.
At that point, focus is given back to the Form that was active
prior to opening the modal dialog. Program execution continues
at the line following ReadModal().
This gives the effect of "returning" from a child Form to a
Parent. If you need to associate multiple Forms - as in
"lookup" dialogs - all child forms must be opened with
ReadModal() rather than Open().
Summary
-------
Learn to instantiate all your own Forms so you're not bound by the default
code generated by the Form Designer. You'll soon become comfortable with
Object References and "passing" references back and forth between objects
and procedures.
Once you substitute properties for variables, you'll find you almost
never use parameters or variable declarations to store or pass
significant data with the exception of Object References. Your programs
will become more Object-Oriented, more encapsulated and easier to
control.
The Object Reference Variable is the "glue" that holds your Forms
together as a program.
All Interprocess Communications in OOP are accomplished by setting
properties of an object from outside the object.
************************************************************************
Sample Code 1 - Parent reads Child Form's Entryfield
************************************************************************
The following sample consists of two simple Forms using Customer.Dbf
from the Visual dBASE samples directory. The parent Form calls the child
form and then reads the value of the child's CustomerNoField when the child
is closed - but before the child is released.
************************************************************************
*------------------------------------------------------------
* Copy this code into a file called FormDemo.prg
* This short program sets up the environment
* and then calls the first form: CustomerForm
*
* To start this demo program type "Do Formdemo"
* from the Command Window.
*------------------------------------------------------------
SET TALK OFF && Set up environment
SET BELL OFF
SET CUAENTER ON
Set Procedure to Customer.Wfm Additive && Load into memory
_app.Cust = New CustomerForm() && Instantiate
_app.Cust.Open() && Open Form.
*--------------------------------------------Customer Form-----------
* Copy this code into a file called CUSTOMER.WFM
*
* This Form requires Customer.dbf from the Visual dBASE samples-
* Change the View property of the form to the appropriate drive and
* directory to access Customer.DBF
*--------------------------------------------------------------------
** END HEADER -- do not remove this line*
* Generated on 09/25/95
parameter bModal && This code never runs because
local f && .WFM is never executed
f = new CUSTOMERForm()
if (bModal)
f.mdi = .F. && ensure not MDI
f.ReadModal()
else
f.Open()
endif
Class CUSTOMERForm OF Form &&Customer Class "blueprint"
this.Left = 23
this.Top = 0
this.Width = 60
this.View = "C:\VISUALDB\SAMPLES\CUSTOMER.DBF"
this.Text = "Parent Form"
this.Height = 10.8232
DEFINE RECTANGLE RECTANGLE1 OF THIS;
PROPERTY;
Left 2.333,;
Top 1.0586,;
Width 55.5,;
Text "",;
BorderStyle 2,;
Height 8.2939
DEFINE ENTRYFIELD CUSTOMERNOFIELD OF THIS;
PROPERTY;
Left 26.833,;
Top 4.1758,;
Width 11.667,;
Value " ",;
Height 1
DEFINE TEXT TEXT1 OF THIS;
PROPERTY;
Left 12.833,;
Top 4.2344,;
Width 12.5,;
Text "Customer No.",;
Height 0.7656
DEFINE PUSHBUTTON LOOKUPBUTTON OF THIS;
PROPERTY;
Left 40.166,;
Top 4.1172,;
Width 12.167,;
ColorNormal "R/W",;
Text "&Look Up",;
Group .T.,;
OnClick Class::LOOKUPBUTTON_ONCLICK,;
Height 1.1172
DEFINE PUSHBUTTON CLOSEBUTTON OF THIS;
PROPERTY;
Left 40.166,;
Top 6.0586,;
Width 12.167,;
ColorNormal "B/W",;
Text "&Close",;
Group .T.,;
OnClick Class::CLOSEBUTTON_ONCLICK,;
Height 1.1172
Procedure CLOSEBUTTON_OnClick
Form.Close()
Form.Release()
Procedure LOOKUPBUTTON_OnClick
Set Procedure to CustLook.Wfm Additive && Load Procedure.
Form.oChild = New CustLookForm() && Instantiate.
Form.oChild.Mdi = .f. && Make sure it's not MDI.
Form.oChild.ReadModal() && Open the lookup Modal.
If Form.oChild.OKButtonPressed && See if OK was pressed in child
Form.Customernofield.Value = Form.oChild.Entryfield1.value
Endif && If so, read child entryfield
&& value into this Form's
&& entryfield value.
Form.oChild.Release() && Release child here, not in
&& child form
Close Proc CustLook.Wfm && Unload Procedure File.
ENDCLASS
*-----------------------------------------------------------------
* Copy this code into Custlook.Wfm
*
* This Form is the child lookup Form for Customer.Wfm
* It has a Browse and a disabled Entryfield tor display
* of the currently selected Customer No.
*-----------------------------------------------------------------
** END HEADER -- do not remove this line*
* Generated on 10/14/95
*
parameter bModal
local f
f = new CUSTLOOKForm()
if (bModal)
f.mdi = .F. && ensure not MDI
f.ReadModal()
else
f.Open()
endif
CLASS CUSTLOOKForm OF FORM
this.Top = 1.5879
this.Width = 60
this.OnOpen = CLASS::FORM_OnOpen
this.Height = 10.0586
this.Text = "Child Lookup Form"
this.MDI = .F.
this.Left = 33.833
DEFINE RECTANGLE RECTANGLE1 OF THIS;
PROPERTY;
Top 0.5879,;
Width 57,;
Height 7.1172,;
BorderStyle 1,;
Text "Rectangle1",;
Left 0.833
DEFINE BROWSE BROWSE1 OF THIS;
PROPERTY;
Top 1.3516,;
Alias "CUSTOMER",;
Width 52.6689,;
Fields 'CUSTOMER->CUSTOMER_N\H="Cust No.",;
CUSTOMER->NAME\H="Cust Name"',;
CUATab .T.,;
Height 5.2354,;
FontBold .T.,;
Left 3.3311
DEFINE PUSHBUTTON OKBUTTON OF THIS;
PROPERTY;
Top 8.293,;
Width 11.002,;
Height 1.1172,;
Text "&OK",;
OnClick CLASS::OKBUTTON_ONCLICK,;
ColorNormal "B/W",;
Group .T.,;
Left 33.8311
DEFINE PUSHBUTTON CANCELBUTTON OF THIS;
PROPERTY;
Top 8.293,;
Width 11,;
Height 1.1172,;
Text "&Cancel",;
ColorNormal "B/W",;
Group .T.,;
Left 46.5
DEFINE ENTRYFIELD ENTRYFIELD1 OF THIS;
PROPERTY;
Top 8.4111,;
Width 13,;
DataLink "CUSTOMER->CUSTOMER_N",;
Height 1,;
When {;Return .f.},;
ColorNormal "N/W",;
Left 16.833
DEFINE TEXT TEXT1 OF THIS;
PROPERTY;
Top 8.5293,;
Width 13.166,;
Height 1,;
Text "Customer No.",;
Left 2
Procedure OKBUTTON_OnClick
Form.OkButtonPressed = .t. && Set property if button
Form.Close() && is pressed.
Procedure FORM_OnOpen
Form.OKButtonPressed = .f. && Set up dynamic property
&& to determine later if
&& OK Button has been clicked.
ENDCLASS
************************************************************************
Sample Code 2 - Child Updates Parent Form's Entryfield
************************************************************************
The following sample has two simple Forms using Customer.Dbf from the
Visual dBASE samples directory. These are the same Forms as the previous
code sample. Only this time, the parent sends its Object Reference to
the child Form so that the Child Form can directly change the value
of the parent's CustomerNoField.
************************************************************************
*------------------------------------------------------------
* Copy this code into FormDemo.prg
* This short program sets up the environment
* and then calls the first form: CustomerForm()
*
* To start this demo program type "Do Formdemo"
* from the Command Window.
*------------------------------------------------------------
SET TALK OFF && Set up environment
SET BELL OFF
SET CUAENTER ON
Set Procedure to Customer.Wfm Additive && Load into memory
_app.Cust = New CustomerForm() && Instantiate
_app.Cust.Open() && Open Form.
*--------------------------------------------Customer Form-----------
* Copy this code into a file called CUSTOMER.WFM
*
* This Form requires Customer.dbf from the Visual dBASE Samples
* Change the View property of the form to the appropriate
* drive and directory to access Customer.DBF
*
*--------------------------------------------------------------------
** END HEADER -- do not remove this line*
* Generated on 09/25/95
parameter bModal && This code never runs because
local f && this .WFM is never "called"
f = new CUSTOMERForm()
if (bModal)
f.mdi = .F. && ensure not MDI
f.ReadModal()
else
f.Open()
endif
Class CUSTOMERForm OF Form &&Customer Class "blueprint"
this.Left = 23
this.Top = 0
this.Width = 60
this.View = "C:\VISUALDB\SAMPLES\CUSTOMER.DBF"
this.Text = "Parent Form"
this.Height = 10.8232
DEFINE RECTANGLE RECTANGLE1 OF THIS;
PROPERTY;
Left 2.333,;
Top 1.0586,;
Width 55.5,;
Text "",;
BorderStyle 2,;
Height 8.2939
DEFINE ENTRYFIELD CUSTOMERNOFIELD OF THIS;
PROPERTY;
Left 26.833,;
Top 4.1758,;
Width 11.667,;
Value " ",;
Height 1
DEFINE TEXT TEXT1 OF THIS;
PROPERTY;
Left 12.833,;
Top 4.2344,;
Width 12.5,;
Text "Customer No.",;
Height 0.7656
DEFINE PUSHBUTTON LOOKUPBUTTON OF THIS;
PROPERTY;
Left 40.166,;
Top 4.1172,;
Width 12.167,;
ColorNormal "R/W",;
Text "&Look Up",;
Group .T.,;
OnClick Class::LOOKUPBUTTON_ONCLICK,;
Height 1.1172
DEFINE PUSHBUTTON CLOSEBUTTON OF THIS;
PROPERTY;
Left 40.166,;
Top 6.0586,;
Width 12.167,;
ColorNormal "B/W",;
Text "&Close",;
Group .T.,;
OnClick Class::CLOSEBUTTON_ONCLICK,;
Height 1.1172
Procedure CLOSEBUTTON_OnClick
Form.Close()
Form.Release()
Procedure LOOKUPBUTTON_OnClick
Set Procedure to CustLook.Wfm Additive && Load Procedure
Form.oChild = New CustLookForm() && Instantiate
Form.oChild.oParent = Form && Add new property to child
&& with this parent's Reference
Form.oChild.Mdi = .f. && Make sure it's not MDI
Form.oChild.ReadModal() && Open the lookup Modal
Close Proc CustLook.Wfm && Unload Procedure File.
ENDCLASS
*-----------------------------------------------------------------
* Copy this code into Custlook.Wfm
*
* This Form is the child lookup Form for Customer.Wfm
* It has a Browse and a disabled Entryfield tor display
* of the currently selected customer
*-----------------------------------------------------------------
** END HEADER -- do not remove this line*
* Generated on 10/14/95
*
parameter bModal
local f
f = new CUSTLOOKForm()
if (bModal)
f.mdi = .F. && ensure not MDI
f.ReadModal()
else
f.Open()
endif
CLASS CUSTLOOKForm OF FORM
this.Top = 1.5879
this.Width = 60
this.OnClose = CLASS::FORM_ONCLOSE
this.Height = 10.0586
this.Text = "Child Lookup Form"
this.MDI = .F.
this.Left = 33.833
DEFINE RECTANGLE RECTANGLE1 OF THIS;
PROPERTY;
Top 0.5879,;
Width 57,;
Height 7.1172,;
BorderStyle 1,;
Text "Rectangle1",;
Left 0.833
DEFINE BROWSE BROWSE1 OF THIS;
PROPERTY;
Top 1.3516,;
Alias "CUSTOMER",;
Width 52.6689,;
Fields 'CUSTOMER->CUSTOMER_N\H="Cust No.",;
,CUSTOMER->NAME\H="Cust Name"',;
CUATab .T.,;
Height 5.2354,;
FontBold .T.,;
Left 3.3311
DEFINE PUSHBUTTON OKBUTTON OF THIS;
PROPERTY;
Top 8.293,;
Width 11.002,;
Height 1.1172,;
Text "&OK",;
OnClick CLASS::OKBUTTON_ONCLICK,;
ColorNormal "B/W",;
Group .T.,;
Left 33.8311
DEFINE PUSHBUTTON CANCELBUTTON OF THIS;
PROPERTY;
Top 8.293,;
Width 11,;
Height 1.1172,;
Text "&Cancel",;
ColorNormal "B/W",;
Group .T.,;
Left 46.5
DEFINE ENTRYFIELD ENTRYFIELD1 OF THIS;
PROPERTY;
Top 8.4111,;
Width 13,;
DataLink "CUSTOMER->CUSTOMER_N",;
Height 1,;
When {;Return .f.},;
ColorNormal "N/W",;
Left 16.833
DEFINE TEXT TEXT1 OF THIS;
PROPERTY;
Top 8.5293,;
Width 13.166,;
Height 1,;
Text "Customer No.",;
Left 2
Procedure OKBUTTON_OnClick
&& Update parent Form's Custnofield
&& using the parent Form's Object Reference,
&& which is stored in the oParent property
&& of this Form.
Form.oParent.CustomerNoField.Value = Form.Entryfield1.Value
Form.Close()
Procedure FORM_OnClose
Form.Release() && This Form may be released here as
&& it will not be addressed again in
&& the parent Form.
ENDCLASS
*------End of HOW TO
|
|