v5.0 / 1 of 23 / 01 sep 99 / gvg
* More interesting VEE examples!
* An HP factory person contacted me concerning a VEE problem he had, and it
fascinated me enough to want to pursue the solution in detail.
He had an instrument that he wanted to control under VEE. The user had to
input four different settings for the instrument using VEE, and some
combinations of these inputs were illegal. He wanted to create a "pop-up"
that would allow the user to select the four inputs, but would
immediately flag an invalid input, and would refuse to exit until the
error was corrected.
The four inputs are integers in the ranges:
* The UserObject itself accepts parameters to initialize the Enum object and
provides outputs to return their values:
As the Enum objects return an Enum value, which is effectively a string, you
can't do Boolean operations on them, so each is run through a From String
object containing the object's default action: READ TEXT X INT -- to convert
them to a number. There are other ways of doing this, but this works as well
as any.
* The test block is more complicated:
To this end, the output is shoved into a Shift Register object. An
If-Then-Else block checks the two Shift Register outputs and generates a
"strobe" signal when they are different, which then activates the
AlphaNumeric. (Note how a Next object is used to ensure that the Until-Break
in the entry block keeps on clocking.)
At the same time, the four inputs from the entry block are being tested by
a Formula box for validity:
* Saving the results of the test in a Global allows the exit block to know
about the results of the tests. The exit block is implemented as a separate
thread, with an Until Break driving an OK button:
Using a Global here has a drawback: it better not have the same name as any
other Global in the program (now I see why people ask for Local variables).
I tried to implement the same function with a Sample-and-Hold, but I got lost
in "strange VEE loop propagation problems" and decided I had to swallow that
inelegancy.
* In any case, the UserObject operated as specified. Case closed.
* A user in Israel had a program that was performing a login check with VEE.
That is, the program would query the user for a name and password and then
see if there was a match.
The user had a bug in the program. After some fruitless searching for it, I
decided it would be wiser to simply write a program that does the function
properly.
* The example I designed (see xlogin.vee for the source) uses an array of
records stored in a Global named UserData to provide UserName, FullName, and
PassWord for each of the users. It then allows the user to enter their name
and password (with up to three tries for each), and either acknowledges a
successful login or that the login failed. The user can cancel out of the
login process at any time.
The overall program appears as follows:
The first thing "Login" does is write a 0 to the output pin. If the
UserFunction completes without changing this value, that is what it will
return. It then calls "CheckName" to query the user for a UserName. If the
user cancels, or fails to enter a valid name, "CheckName" returns a -1, and
"Login" exits immediately.
Otherwise, "CheckName" returns an index to the matching UserData record,
which is used to obtain the corresponding password from the record using a
Formula box, password is then passed on the UserFunction "CheckPassWord" to
query the user for a matching password.
If the user cancels or fails to enter a matching password, "CheckPassWord"
returns a 0; otherwise it returns a 1. The output value of "CheckPassWord"
is passed to the output of "Login" to overwrite the 0 written at the
beginning of the UserFunction.
* Examining the UserFunction "CheckName" in more detail gives:
A Text Input dialogue is used to get the UserName. If the user cancels out,
the UserFunction exits. Otherwise it calls the UserFunction "ScanList" to
check for a UserName match.
"Scanlist" returns a -1 if there is no match, and the index of the matching
record if there is a match. If there is no match, the UserFunction goes to
the next iteration of the three queries; if there is a match, it gates the
index of the matching record to the output and then exits.
* The architecture of "ScanList" is a little simpler:
The Counter drives a Formula box that checks the UserData array element given
by the count against the username provide. If there isn't a match, it fires
off the next iteration of the loop, if there is a match, it gates the current
index count to the output of the UserFunction and then exits.
* The "CheckPassWord" UserFunction is somewhat similar to the "CheckName"
UserFunction:
On each request for password input, it uses a Text Input dialogue to query
the user. If the user cancels, the UserFunction exits. If the dialogue
obtains a valid password, it compares it to the password passed to
"CheckPassWord". If there is no match, it goes on to the next check in the
loop, if there is a match, it sends a 1 to the output and exits.
One last comment: the Text Input box used to obtain the password from the
user should be set to "Password Masking Enabled". This will substite a "*"
for each keypress the user makes while entering the password, keeping it
hidden.
* This example demonstrates structured and deterministic control-flow design
in a VEE program. The example divides decision-making structures into simple
individual UserFunctions, each one of which whose actions are well-defined,
each one of which can be tested and validated in a straightforward fashion.
[18.1] HANDLING DATA INPUTS TO VEE
[18.2] A LOG-IN PROGRAM IN VEE
[18.1] HANDLING DATA INPUTS TO VEE
DC: 0:9
DS: 0:11
TC: 0:9
TS: 0:11
The rules of interaction are:
IF(( DS == TS )
OR ( DS == 0 AND TS == 11 )
OR ( DS == 11 AND TS == 0 )
OR (( DC != TC ) AND (abs( TS - DS ) == 1 )))
{
combination = INVALID;
}
ELSE
{
combination = VALID;
}
* Moving on to implementation (see xdatapms.vee for the source). The
Pop-Up display contains four Enum objects, plus a display giving the current
settings, and a button allow you to exit when the selections are made:
+----------------------------------------------------------+
| UserObject |
+----------------------------------------------------------+
| |
| Parameter Setting Test Program |
| |
| +----------+ +----------+ +----------+ +----------+ |
| | DC | | DS | | TC | | TS | |
| +----------+ +----------+ +----------+ +----------+ |
| | <*> 0 | | <*> 0 | | <*> 0 | | < > 0 | |
| | < > 1 | | < > 1 | | < > 1 | | < > 1 | |
| | < > 2 | | < > 2 | | < > 2 | | <*> 2 | |
| | < > 3 | | < > 3 | | < > 3 | | < > 3 | |
| | < > 4 | | < > 4 | | < > 4 | | < > 4 | |
| | < > 5 | | < > 5 | | < > 5 | | < > 5 | |
| | < > 6 | | < > 6 | | < > 6 | | < > 6 | |
| | < > 7 | | < > 7 | | < > 7 | | < > 7 | |
| | < > 8 | | < > 8 | | < > 8 | | < > 8 | |
| | < > 9 | | < > 9 | | < > 9 | | < > 9 | |
| +----------+ | < > 10 | +----------+ | < > 10 | |
| | < > 11 | | < > 11 | |
| +----------+ +----------+ |
| +---------+ |
| | 0 0 0 2 | |
| +---------+ |
| +------+ |
| | QUIT | |
| +------+ |
+----------------------------------------------------------+
If there's a bogus input, a Message Box appears; you'll also get the same
message box if you try to exit without fixing the bogus input. (Note that a
bogus input will not be changed by VEE, you have to change it back
yourself.)
+------------+
DC --->| +---> DC
DS --->| UserObject +---> DS
TC --->| +---> TC
TS --->| +---> TS
+------------+
Internally, the UserObject can be divided into three parts: an entry block
to get the values, a test block to check the values, and an exit block to
allow exiting the UserObject:
+----------------------------------------------+
| UserObject |
+----------------------------------------------+
| +---------+ |
DC --->|------------->| +---------------------|---> DC
DS --->|------------->| entry +---------------------|---> DS
TC --->|------------->| block +---------------------|---> TC
TS --->|------------->| +---------------------|---> TS
| +-+-+-+-+-+ |
| | | | | |
| +--------+ | | | | +---------+ |
| | exit | | | | +-- dc -->| | |
| | block | | | +---- ds -->| test | |
| +--------+ | +------ tc -->| block | |
| +-------- ts -->| | |
| +---------+ |
+----------------------------------------------+
The entry block is simple but it has a few subtleties:
+-------+
| Until |
| Break +---------+ entry block
+-------+ |
+---+---+ +-------------+
DC -------------->| DC +---->| From String +-----------+--------> DC
+---+---+ +-------------+ |
enum | |
| |
+---+---+ +-------------+ |
DS -------------->| DS +---->| From String +--------+--+--------> DS
+---+---+ +-------------+ | |
enum | | |
| | |
+---+---+ +-------------+ | |
TC -------------->| TC +---->| From String +-----+--|--|--------> TC
+---+---+ +-------------+ | | |
enum | | | |
| | | |
+---+---+ +-------------+ | | |
TS -------------->| TS +---->| From String +--+--|--|--|--------> TS
+-------+ +-------------+ | | | |
enum | | | |
| | | +--> dc
| | +-----> ds
| +--------> tc
+-----------> ts
The four Enum objects are "clocked" continously by an Until Break object.
This allows the UserObject to check their status on a continuous basis. The
Enum objects are not set to "Auto-Execute" (which, as this exercise
confirmed, is a nearly useless VEE feature for anything but the most idiotic
VEE programs and should be avoided) nor to "Wait For Input" (which is more
useful but in this case violates the requirement that the inputs be
continually checked).
+--------+
dc ---+----------->| | +----------+ +---------+ A!=B
ds ---|--+-------->| To +--+->| Shift +-->| If-Then +----+
tc ---|--|--+----->| String | | | Register +-->| -Else | |
ts ---|--|--|--+-->| | | +----------+ +----+----+ |
| | | | +--------+ | | |
| | | | | +---+---+ |
| | | | | | Next | |
| | | | test | +-------+ |
| | | | block | |
| | | | | +-------+-------+
| | | | +----------------------->| AlphaNumeric |
| | | | +-------+-------+
| | | | |
| | | | +---------------------------------------+
| | | | |
| | | | +----+----+
| | | +-->| | +--------------+ A==1
| | +----->| formula +--+-->| If-Then-Else +---------+
| +-------->| | | +--------------+ |
+----------->| | | |
+---------+ | +------------+ +-----+-----+
+-->| Set Global | | Message |
| [ tmp ] | | Box |
+------------+ +-----------+
The four inputs to this block are converted to a nice string display format
using a To String box containing the transactions:
WRITE TEXT A," " INT
WRITE TEXT B," " INT
WRITE TEXT C," " INT
WRITE TEXT D INT EOL
This is shoved into an AlphaNumeric object for display. However, since the
inputs from the entry block are being clocked continuously, just driving this
output into the AlphaNumeric would cause the display to flicker annoyingly.
It should only be updated when its value actually changes.
((( DS == TS ) OR ( DS == 0 AND TS == 11 )
OR ( DS == 11 AND TS == 0 )
OR (( DC != TC ) AND (abs( TS - DS ) == 1 ))))
The Formula box is also hooked up to the "strobe" signal. When activated, it
checks the inputs and returns a "1" if they are bad. This in turn activates
a Message Box and is also stored in a Global variable named "parmerr". Note
that if the Formula box wasn't being strobed, once you had an error you'd
never get rid of the Message box -- tell it to go away and it would just pop
back up at you again.
+-------+
| Until |
| Break +---+
+-------+ |
|
+---+---+
| QUIT +------+ exit block
+-------+ |
OK |
+------------+------------+
| If/Then/Else |
+---------------+---------+
|[(parmerr==0)] | Then +--------------------------+
|[(parmerr==1)] | Else If +-----------+ |
| Else | | | |
+---------------+---------+ +-----+-----+ +-----+------+
| Message | | Exit |
| Box | | UserObject |
+-----+-----+ +------------+
|
+---+---+
| Next |
+-------+
If the QUIT button is activated, the If/Then/Else checks the value of the
Global variable "parmerr". If there's been an error, you get a message box,
if not, the UserObject is exited.
[18.2] A LOG-IN PROGRAM IN VEE
+------------------------------+
| Record |
+------------------------------+
| +--------------------------+ |
| | [ 0 ] in: [0 .. 5 ] | | +-------------+
| +--------------------------+ | | Set Global: |
| Field name Value +---->| UserData |
| ---------------------------- | +------+------+
| [ UserName ] [ bugs ] | |
| [ FullName ] [ Bugs Bunny ] | +--------+--------+
| [ PassWord ] [ whatsupdoc ] | | Call Function: |
| ---------------------------- | | Login +--+
| [First][Prev ][Next ][Last ] | +-----------------+ |
+------------------------------+ |
|
+---------------------------------------------------+
|
| +-----------------+
| | If/Then/Else |
| +---+------+------+
+-->| A | A==1 | Then +-------------------------+
| | | Else +--+ |
+---+------+------+ | |
| |
+--------+--------+ +----------+----------+
| Message Box: | | Message Box: |
| "Login Failed." | | "Login Succeeded." |
+-----------------+ +---------------------+
The program is built around a UserFunction named "Login" that returns a 1 for
a successful login and a 0 for an unsuccessful login. It has the
architecture:
+--------------------------------------------------------------------+
| UserFunction: Login |
+----------------------------------------------------------------+---+
| | |
| +-----+ Integer return value here | |
| | 0 +-------------------------------------->+-----+ | |
| +--+--+ | JCT +-->| X |
| | +-->+-----+ | |
| +--------+--------+ query user for name | | |
| | Call Function: | | | |
| | CheckName +--+ | | |
| +-----------------+ | +---------+ | |
| | | | |
| +--------------------+----+ | | |
| | | | | |
| | +----------------+ | | | |
| | | If/Then/Else | | | | |
| | +---------+------+ | | | |
| +->| A != -1 | Then +-----|-------------+ | | |
| | | Else +--+ | | | | |
| +---------+------+ | | +----------+-----------+ | | |
| | +->| UserData[A].PassWord +--+ | | |
| +-----------+ +----------------------+ | | | |
| | Formula | | | |
| +--------+--------+ +-------------------+ | | |
| | Exit UserObject | | | | |
| +-----------------+ | +----------------+ | | |
| +->| Call Function: +--+ | |
| | CheckPassWord | | |
| +----------------+ | |
| | |
+----------------------------------------------------------------+---+
This UserFunction relies on two lower-level UserFunctions, "CheckName" and
"CheckPassWord", to perform the checks on the UserName and the PassWord
respectively.
+--------------------------------------------------------------------+
| UserFunction: CheckName |
+----------------------------------------------------------------+---+
| | |
| +-----+ Integer | |
| | -1 +-------------------------------------->+-----+ | |
| +--+--+ | JCT +-->| X |
| | +---------------+ +-->+-----+ | |
| | | If/Then/Else | | | |
| +-------+-------+ +--------+------+ | | |
| | For Count: 3 +-->| A >= 1 | Then +--+ | | |
| +-------+-------+ | | Else | | +---------+ | |
| | +-------++------+ | | | |
| | | | | | |
| +--------+--------+ | +-------+-------+ | | |
| | Message Box: | | | Message Box: | | | |
| | "Access Denied" | | | "Try Again" | | | |
| +--------+--------+ | +---------------+ | | |
| | | | | |
| +--------+--------+ | | | |
| | Exit UserObject | | | | |
| +-----------------+ | | | |
| | | | |
| +-------------------+ | | |
| | | | |
| | +----------------+ | | |
| +-------+-------+ | Call Function: | | | |
| | Text Input: +----->| ScanList +--+ | | |
| | "Enter Name" +--+ +----------------+ | | | |
| +---------------+ | | | | |
| Cancel | | | | |
| +--------+--------+ +-------+ | | |
| | Exit UserObject | | | | |
| +-----------------+ | | | |
| | | | |
| +-----------------------------------+ | | |
| | | | | |
| | +----------------+ | | | |
| | | If/Then/Else | | | | |
| | +---------+------+ | | | |
| +-->| A != -1 | Then +--------------|-------------+ | | |
| | | Else +--+ | | | | |
| +---------+------+ | | index +---+---+ | | |
| | +-------->| Gate +---+ | |
| +---+---+ +---+---+ | |
| | Next | | | |
| +-------+ +--------+--------+ | |
| | Exit UserObject | | |
| +-----------------+ | |
| | |
+----------------------------------------------------------------+---+
"CheckName" begins by writing a 0 to the output as the default value, and
then uses a Counter to query the user three times. If the user fails to enter
a valid UserName after three tries, he or she gets an "Access Denied"
message, and the UserFunction exits. Note how "CheckName" uses a message box
to tell the user "bogus UserName, please try again" before the second and
third tries.
+---------------------------------------------------------------------+
| UserFunction: ScanList |
+------+----------------------------------------------------------+---+
| | | |
| | +-----+ Integer | |
| | | -1 +------------------------------->+-----+ | |
| | +--+--+ | JCT +-->| X |
| | | +-->+-----+ | |
| | +---------+---------+ +-----------+ | | |
| | | totsize(UserData) +-->| For Count +--+ | | |
| | +-------------------+ +-----------+ | | | |
| | | +---------+ | |
| | +--------------------------------------+ | | |
| | | | | |
| | | +--------------------------------------+ | | |
| | | | If/Then/Else | | | |
| | | +---+---------------------------+------+ | | |
| | +-->| A | UserData[A].UserName == B | Then +-----+ | | |
| Name +--|-->| B | | Else +--+ | | | |
| | | +---+---------------------------+------+ | | | | |
| | | | | | | |
| | | +---------------+ | | | |
| | | | | | | |
| | | +---+---+ +-------+ | | |
| | | | Next | | | | |
| | | +-------+ | | | |
| | | +---+---+ | | |
| | +----------------------------------->| Gate +------+ | |
| | +---+---+ | |
| | | | |
| | +--------+--------+ | |
| | | Exit UserObject | | |
| | +-----------------+ | |
| | | |
+------+----------------------------------------------------------+---+
This UserFunction begins by writing a -1 to the output as the default value,
and then gets the "totsize()" of the UserData array. This is used as a
Counter value to allow checking each record of the UserData array.
+----------------------------------------------------------------------+
| UserFunction: CheckName |
+------+-----------------------------------------------------------+---+
| | | |
| | +-----+ Integer | |
| | | 0 +---------------------------------->+-----+ | |
| | +--+--+ | JCT +-->| X |
| | | +---------------+ +-->+-----+ | |
| | | | If/Then/Else | | | |
| | +-------+-------+ +--------+------+ | | |
| | | For Count: 3 +-->| A >= 1 | Then +--+ | | |
| | +-------+-------+ | | Else | | +---------+ | |
| | | +-------++------+ | | | |
| | | | | | | |
| | +--------+--------+ | +-------+-------+ | | |
| | | Message Box: | | | Message Box: | | | |
| | | "Access Denied" | | | "Try Again" | | | |
| | +--------+--------+ | +---------------+ | | |
| | | | | | |
| | +--------+--------+ | | | |
| | | Exit UserObject | | | | |
| | +-----------------+ | | | |
| | | | | |
| | +-------------------+ | | |
| | | | | |
| | +--------+--------+ | | |
| | | Text Input: +---------------+ | | |
| | |"Enter Password" +--+ | | | |
| | +-----------------+ | | | | |
| | Cancel | | | | |
| | +--------+--------+ | | | |
| | | Exit UserObject | | | | |
| | +-----------------+ | | | |
| | | | | |
| | +--------------------------------+ | | |
| | | | | |
| | | +--------------------+ | | |
| | | | If/Then/Else | | | |
| | | +---+---------+------+ | | |
| | +-->| A | A == B | Then +-------------------+ | | |
| Pass +----->| B | | Else +--+ | | | |
| | +---+---------+------+ | +---+---+ | | |
| | | Integer | 1 +---+ | |
| | +---+---+ +---+---+ | |
| | | Next | | | |
| | +-------+ +--------+--------+ | |
| | | Exit UserObject | | |
| | +-----------------+ | |
| | | |
+------+-----------------------------------------------------------+---+
This UserFunction begins by writing a 0 to the output, then checks three
times for input. If there is no valid input, it announces "Access Denied"
and exits. (It announces the erroneous input for the second and third
attempts.)