Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Newsgroups  |  Paradox Web Sites  |  Paradox Book List  |  FAQs From The Corel FAQ Newsgroup  



Subject: FAQ:PdoxWin:Events:2002.03.11

Version 1.0 (2001.05.26)
Replacement of lost attachment 2002.03.11
edited by Paradox FAQ Team

====================
0. Introduction
====================

This FAQ addresses Paradox Events.

Thanks to Mark Pauker (for devising the Event Model) and
Gwan Tan and Cary Jensen for some valuable insights into it
in my earlier Paradox for Windows days.

The contents of this FAQ originally appeared in a thread
"Events 101", starting on Thu, 10 Feb 2000 11:17:24 EST.

-------------------------------
 0.1 Legal Info and Disclaimers
-------------------------------

Paradox is a trademark of Corel.
Borland Database Engine (BDE) is a trademark of Inprise.

The information provided in this FAQ is provided "as is"
and is not warranted in any way. The information provided
in this FAQ is not endorsed or authorized by Corel or
Inprise in any shape, form, or manner.

The editors claim NO responsibility for ANY illegal
activity regarding this file, or as a result of someone
reading this file.

You may distribute this file, as long as the copies are
complete, unaltered, and are in electronic form only.

-------------
 0.2 Feedback
-------------

Please send feedback in a Corel Paradox newsgroup or the
news:comp.databases.Paradox newsgroup to any of the FAQ
Team mentioned in the "FAQ: FAQ FAQ" document.
Please preface the subject of your post with the string
"PDXWIN FAQ" to alert Team members to the function of
the message.

Please specify the FAQ name and section number the
comment applies to, if any.

-------------
 0.3 Attachments
-------------

There is an executable attached to this FAQ. It is a
self-extracting zip, but will not run any other program. It
will deliver its contents to c:\temp. It contins the
following files

Events101.Fsl    see section 1.2
Events102.Fsl    see section 2.2
SelectList.*     8 files for the table for Events102.fsl

==============================
1. Events 101
==============================

Warning: this is heavily simplified and probably looks
wrong to some people. However, it works, which is what
matters <g> !

-------------------
1.1 Part 1
-------------------

Events are things that happen internally when you do
something in a Paradox system. The easiest way to see what I
mean by this is to open the trace window on a blank form and
watch the events streaming by !

-------------------
1.1.1 Practice
-------------------

1. Create a new form
2. Open the Form::Init method
3. Drop down the View menu and select Trace
4. Run the form. Enlarge the trace window, but don't
   cover the form.
5. Click on the Properties menu of the Trace window;
   choose Built-in Events, and select ONLY the set/remove
   focus and mouse enter/exit methods. We'll trace whenever
   they fire.
6. Make sure Properties | Show Code is checked.
7. Run the form
8. You'll immediately see two lines

BuiltIn:setFocus, self = "#FormData1",
target="#FormData1"
BuiltIn:setFocus, self = "#Page2", target="#Page2"

These show you that an event was triggered when you started
the form that resulted in the form and then the page getting
focus. You can look in the setFocus() methods for those two
objects (via the Object Explorer) to see what the event was
and get info about it. You'll see that the code for these
methods starts:

method setFocus(var eventInfo Event)

-------------------
1.1.2 Tip
-------------------
Click on the type ("Event" and press F1 for help !


Why the practice ? Well, it shows you how to see which
methods fire when something happens. Look up the Trace
methods in the help and you'll see how to turn tracing on
and off in your code, so you only get it at the point you're
interested in. General trace is useful for finding bugs when
you don't know where they're located, and also for playing
with something to find out what happens. Once you know what
happens, you know the places you can put code to be run when
that thing occurs again in the future.

-------------------
1.2 Part 2
-------------------

Paradox' events exhibit a behaviour called "bubbling".
The container of an object will receive an event if the
contained object doesn't know how to deal with it.

Containership is defined by the way things are drawn on the
form or report.

-------------------
1.2.1 Practice
-------------------

1. Start another new form
2. Put a button in the middle of the form
3. Open the Object Explorer (OE) and set it to view
   "Both" and "Pin".
4. You'll see a tree-like structure in the left side of the
   OE. Expand this as far as it'll go. The Form contains the
   Page. The Page contains the button. The Button contains
   the Label.
5. Each item has a "noise name". The Form's is #Form1. The
   number is the sequence number of creation of the object,
   and the rest is a "#" sign followed by the type of the
   object. As another example, the label on the button
   should be #Label4.
6. Draw a Box around the Button.
7. Look in the OE and you'll see that the Box now contains
   the button.
8. Move the box on the form, and containership will cause
   it _and_ the button to move together.

-------------------
1.2.2 Note
-------------------
All Paradox objects MUST have a name, so Paradox assigns
them noise names. These names can - and very often do -
alter at runtime, so you cannot use them in code. Instead,
you rename an object, giving it a static name, and use that.

-------------------
1.3 Part 3
-------------------

In Part 2 we created a simple form with a button in a box.
Now add some _very_ simple code and see what happens.

-------------------
1.3.1 Practice
-------------------

1. Click on the Form in the OE tree and, on the right-hand
   side of the OE, select Events.
2. Double-click on the MouseClick method. You'll get a
   Window with this code:

if eventInfo.isPreFilter() then
   ;// This code executes for each object on the form
else
   ;// This code executes only for the form
endIf

3. Replace the two comments so you see this code instead:

if eventInfo.isPreFilter() then
   msgInfo("Form", "PreFilter")
else
   msgInfo("Form", "PostFilter")
endIf

4. Run the form
5. Click on the Button. You'll see the "PreFilter" message.
6. Click anywhere else and you'll see both messages.

-------------------
1.3.2 Q & A
-------------------

Two questions:
A. Why two messages ?
B. Why the difference between #5 and #6 ?

A. Every object has some built-in default behaviour.
   Clicking a button makes it depress and then raise again.
   The default behaviours only concern themselves with
   events that they're interested in. If an object isn't
   interested in an event, its default behaviour for that
   event is just to pass it on to its container. A button is
   interested in a mouseclick, but a box isn't.

B. The difference is because Paradox has an event model that
   is handled by the Form. EVERY event goes to the Form
   first, so that's why we saw "PreFilter" in both cases.
   The Form offers you a chance to intercept an event before
   it gets to its intended target. The default behaviour of
   the form if it's PreFiltering an Event is to pass it on
   to its intended target, so you can see that the code you
   write _always_ happens _before_ the default behaviour.

Once an Event has been directed to its intended target, it's
up to the target and code to deal with it. Let's see how
this happens.

-------------------
1.3.3 Practice
-------------------

1. With the same form again, go to each object on the form
   and open its mouseClick method.
2. Add in a message like the ones in the
   Form::mouseClick, but stating which object the message is
   coming from.
3. Now run the form. Click on everything and see what
   messages you get!

You can add other objects now and see bubbling with
them, too. TableFrames can be interesting to investigate !

-------------------
1.4 Part 4
-------------------

Lets look at four important ObjectPAL commands now:

doDefault
disableDefault
enableDefault
passEvent

Briefly:

doDefault is an instruction you put into your code that
says to the object "do your default behaviour NOW ! Then let
me resume this code"

disableDefault prevents the default from happening, and
enableDefault allows it again

passEvent makes a copy of the event and passes it on up the
containership tree.

-------------------
1.4.1 Practice
-------------------

1. In your same form. open the button::mouseClick()
   and add this code to the end:

disableDefault

2. test the form. Nothing appears to have happened. This is
because the button "eats" the event anyway, so you can't
stop it passing it on. The button still depresses because it
isn't the mouseClick (but the mouseDown) that makes it
depress.

3. Now add this code as well:

passEvent

4. Test the form. You'll see that now the button appears
   to act like all the other objects.

==============================
1. Events 102
==============================
This part was originally entitled

"Where to Put Code, and Why"

From the examples, practice, and general fun that you
(should) have had with Events 101, you know now the basics
of _how_ to watch events happening, and the tools to use to
see what events happen when you do something.

This is a very important concept - that Paradox and your
objects _react_ ! Until the advent of SmallTalk in 1980
nobody had actually worked out how to get a GUI to work
properly. Unfortunately, it's also a concept that many
people tend to forget, and, as a result, they end up writing
a heap of ObjectPAL to do something that is being done by
Paradox anyway <g> !

When I first started working with Paradox I was incessantly
lectured by my "betters" on the importance of doing things
"the Paradox way". However, none of them actually spelled
out what that way was, so I made a complete mess of my first
app. Oh, it was architecturally superior to anything else
they were writing - but it also ran about 300 times slower !
This was because I was doing everything explicitly, instead
of just letting Paradox do it. The Paradox Way is, indeed,
a Paradox - to get the best from your code, do the least.

So, where should you place your code. Well, according to the
Gods, Idols, and SoothSayers of OOP, you should place the
code in the Object. Great. Lets take a look at a form and
I'll describe where the code is, and why.

-------------------
2.1 Setup
-------------------

We'll look at a Lookup form. Please see the section on
attachments to find the form. This consists of a form, page,
tableFrame, and a box with a set of control and navigation
buttons. The box of buttons is copied and pasted from another
form. We're going to use this form as a prototype for all
lookup forms in the app, saving it with a new name whenever
we want to use it.

In Form::init() we find some SQL that creates a table by
obtaining a subset of another table according to some
criteria. This is somewhat exceptional, but the code is
commented out and left here for later re-use if need be.
The table created is the table that the form is based on,
so what we're doing is creating a lookup on the fly. This is
particularly useful if you want to offer only those values
not already in use, or not sold out, or ... This _has_ to go
into the Form::init() because that method runs before the
form code reads its own data model.

Form::open() is the next to run, and its code controls
opening libraries for the form, and assigning totally global
variables. I usually get a form variable called foGMySelf to
attach to the form, so I can refer to the form itself later
on, if need be. Obviously, I've commented out the library 
opening code, but, to satisfy your curiosity, MenuLib handles
menus and InfoLib handles information exchange. You can find 
a cut-down version of InfoLib in another FAQ here.

Page::arrive() is pretty much the last method to run before
you can start using the form. Here you should do all your
last-minute work, like going into edit mode, making the form
visible, etc. Some people like to use Form::arrive(), but,
IMHO, that method isn't late enough.

So, that's the bare essentials for you. Now we have to
explore where to trap events ...

-------------------
2.2 Responding to Common Events
-------------------

Now we know where to put the start-up code, lets look
at common things that might happen on this form.

One definition first: the concept of "state". The "state"
of an object is the set of values of its variables. Change
one or more and its state is altered. In Paradox you can
declare variables within a method (between the method() and
endMethod statements) and they are strictly transient, being
destroyed as soon as the method ends. However, if you
place them outside those statements or, better still, in
some superior container, it will persist until the container
closes. If you place them at the form level, they will
persist until the form closes.

The user is looking at a list of values and a button bar
with navigation buttons and an OK and a Cancel button.
Because I use the same button bar for all forms like this,
The code for the bar goes into the buttons themselves. There
are one or two variables that I want to store for the button
bar. If one of the navigation buttons hits the end (record
1, for example) I will remember that until I'm moved off it,
and trap the use of navigation that might cause an error
because of trying to go past the end of a table. This
doesn't appear to happen in Pdx 9, but has in some older
versions. These variables are declared in the Var section of
the Box that surrounds the button bar, so they will persist
and they will be globally available to all the buttons
because they are in the container of all the buttons.

The above is a good example of placing code in an object. It
is also good use of it in Paradox terms, because the object
(the toolbar) is cut and pasted as a single unit.

On the other hand, the code that moves the cursor on the
tableFrame is held within the buttons themselves. I can
change the name and table of the tableFrame and I won't have
a problem with this because these buttons act on a uiObject
variable attached to the tableFrame.

The uiObject is attached to the tableFrame in the
Form::open() method, but I never have to edit that, because
it is attached to a named constant in Form::Const. All I
have to do is set the constant when I attach the tableFrame
to a new table during form design, and all works like magic.

The above is an example of doing less. I open the form,
change the data model to accomodate a new table, redefine
the tableFrame to show the fields I want, and change one
constant accordingly. Navigation now works !

To accomodate OK and Cancel I have a little more work to do.
I use a library to transfer values back and forth between
forms, so Cancel is always the same - it returns a "False"
for a value called "Lookup", indicating that nothing was
done. In the code for OK that variable is set to "True", and 
here I don't even have to return any values ... why not ?
Because they're in a table, and the Lookup variable indicates 
whether or not the waiting code should look for results in
the table ! This is an example of doing lots with very little
effort !

-------------------
2.2 Selecting values
-------------------

Frequently I need to offer a range of values and allow the
user to select 0, 1, or many of them. To do this I very
often generate a table filled with a column of all the valid
values (usually selected from a master table) and a second
column filled with a logical False falue. I then arrange to
allow the user to toggle the value of the second column
with a mouse click. The calling form uses a simple piece of
SQL to get an in-memory tCursor to a table holding just the
selected key values

(select <keyval> from <tableName> where Selected = True).

The code to toggle the value of the "Selected" column is so
simple as to be trivial - once you get it to work <g> ! You
put this into the Form::mouseClick() method:

   eventInfo.getTarget(uiTarget)
   if uiTarget'class = "Field" then
      if uiTarget'name = "Selected" then
         doDefault
         uiTarget.edit()
         uiTarget'value = not uiTarget'value
         uiTarget.postRecord()
         uiTarget.endEdit()
      endIf
   endIf

The amusing thing is that if you don't have the doDefault
then you'll end up with a lock on the table that you have to
close Paradox to get rid of :-)

You'll notice that I have placed this code at the Form level,
and not in the tableFrame. This is because I can make it more
generic at this level if I so wish. More importantly, I also
find that being able to _find_ people's code is often tougher
than finding the bugs. If we all place the code in the same
place then we know where to go and look, which will make
life much much easier in 3 years' time !

Other famous pieces of code that can best go at Form level
are the code to trap for lookups (especially in Paradox 7 &
8 runtimes on NT machines, which crash otherwise), and the
code for re-ordering the appearance of the data in a
tableFrame by applying filters when the user clicks on the
header.


It's not because things are hard that we don't dare
them; it's because we don't dare them that they are hard
 - Seneca.
events101.exe


Paradox Community Newsgroups


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 15 May 2003
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2003 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.