AnsweredAssumed Answered

VRF-Handling_User_Prompts

Question asked by VRFuser on Feb 10, 1999
send q VRF-Handling_User_Prompts ww vrf evanstr60@hotmail.com

from: Greg Goebel / HP-MXD
      greg_goebel@hp.com / 800-452-4844
      website:  ftp://fcext3.external.hp.com/dist/mxd/index.html
to:   VRf-Thomas Evans
date: Wednesday, 10 February 1999 1643 MST

--------------------------------- cut here ----------------------------------
>From: "thomas evans" <evanstr60@hotmail.com>
Subject: VRF
Date: Tue, 09 Feb 1999 06:53:56 PST

Hello world,

I have a few problems with VEE.  I am a relatively new user, so forgive
me if my problems seem a bit elementary.

> 1. I'm trying to design a program with lots of user prompts, but I can't
> work out a way of giving the user the option of going back to earlier
> prompts if required.  I've tried using junctions to control program
> flow, but with no success.
>
> Tom Evans
> evanstr60@hotmail.com

Sir:  This looked like a nice subject for an article, so I wrote one.  This
is a first pass and likely needs some proofreading, but it's late in the
day and I might as well get it out, glitches and all.

[%%] regards -- gvg

--------------------------------- cut here ----------------------------------



[VEE] Using The VEE Sequencer To Prompt For User Inputs

* A VEE user on the VEE email reflector had an interesting question.  His
program was supposed to query the user for several different items of
information, and he wanted to figure out a way by which the user could cancel
on one query and go back to make changes in earlier queries.

I looked at this problem for a moment and realized that the VEE Sequencer
object could do the job.  This is a VEE transaction object that can step
through a set of transactions that call UserFunctions, and perform conditional
sequencing through the set on the results returned by the UserFunctions.

It is a relatively obscure object, mostly intended for manufacturing tests,
energetically used by a few and ignored by most others.  Some years ago an HP
field person suggested to me that it was such a flexible object that it
obviously had other uses, and I agreed, but I hadn't run into such
alternative applications that I couldn't do more easily some other way.

However, querying a user and performing different actions on the replies was
really no different from testing a machine and performing different actions
on the results, so the Sequencer seemed clearly the right tool for this job.
In fact, I managed to come up with an example in a surprisingly short time.

[%%]


[2] A SHORT SEQUENCER OVERVIEW

* To understand how the example works requires a simple understanding of how
the Sequencer operates.  While it is not fundamentally difficult to
understand, it has many options that make it a little intimidating.  However,
for this application only the most basic features are requried.

* The Sequencer appears to be an ordinary I/O transaction object:

   +----------------------------------------------------+
   |                      Sequencer                     |
   +---+---------------------------------------+--------+
   |   |[                                     ]|        |
   |   |                                       | Return |
   |   |                                       |        |
   |   |                                       |        |
   |   |                                       |        |
   |   |                                       |   Log  |
   |   |                                       |        |
   +---+---------------------------------------+--------+

For this application, the output pins are irrelevant and won't be discussed.
Unlike an I/O transaction object, the Sequencer transactions consist of the
sequence of tests to be performed, which for present purposes are
UserFunctions to get information from the user.

Click on the transaction field to set up a test and you get a complicated
dialogue:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ TEST: ] [ test1  ] [   ENABLED   ]                                    |
| SPEC NOMINAL:        [.5    ] [ RANGE ] [0   ] [ <= ] ... [ <= ] [1   ] |
| FUNCTION:      [testFunc(s) ] [ LOGGING ENABLED ]                       |
| [  IF PASS  ] [  THEN CONTINUE  ]                                       |
| [  IF FAIL  ] [  THEN CONTINUE  ]                                       |
| DESCRIPTION:  [                                ]                        |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This dialogue box has a number of fields and settings.  A full discussion of
is impractical here, but for present purposes we can discuss them in a
simplified fashion:

% The "TEST:" field just gives an arbitrary label for the transaction.  It
   can be any string and has no significance beyond being a label.

% The "ENABLED" flag allows you to disable the transaction, turning it into
   a NOP.  This is just a debugging tool.

% The "SPEC NOMINAL" field gives the value the test is expected to return.
   It is used in the compilation of test statistics.  Since we have no
   interest in test statistics in its case, this field is not very important
   for our purposes.

% The "RANGE" field specifies the range of PASS values for the test.  This
   can be changed to a "LIMIT" test against a single value, which is a bit
   more useful for our purposes.

% The "FUNCTION" field gives the function to be called, in this example the
   function "TestFunc(a)".  The function can be given parameters as part of
   its invocation, or as inputs from pins set up on the Sequencer object.

   The "function" can also be an input or a value from a variable, which is
   what the Sequencer will assume if the name in that field doesn't end in
   parantheses.

% "LOGGING ENABLED" is a flag that turns on or off the logging of test
   results statistics.  This is irrelevant for our purposes and will be set
   to "LOGGING DISABLED" in the rest of the discussion.  Since Sequencer
   logging is a complicated subject, eliminating logging as a concern greatly
   simplifies use of the Sequencer.

% The next two lines, "IF PASS" and "IF FAIL", specify the actions the
   Sequencer will take if the results from the UserFunction called pass or
   fail the test. 
  
   There are a number of different actions, but for the current example we
   only need to know about three of them: 
  
    - "THEN CONTINUE", which just tells the Sequencer to go on to the next
      test transaction.
      
    - "THEN GOTO", which will be given the name (label) of a test to go
      begin operation from that point.
 
    - "THEN RETURN", which executes Sequencer operation.  It also puts a
      user-defined value on the output pin, but we don't really care about
      that in this application.

% The "DESCRIPTION" field is just for a comment, which is handy on occasion.

The dialogue above specifies a "test" transaction.  You can also specify an
"execute" transaction (by clicking on the "TEST") field, which executes a
function but isn't evaluated for a test.  This gives a simplified dialogue
box:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ EXEC: ] [ test1  ] [   ENABLED   ]                                    |
| FUNCTION:      [testFunc(s) ] [  LOGGING DISABLED ]                     |
| [  THEN CONTINUE  ]                                                     |
| DESCRIPTION:  [                                   ]                     |
|                                                                         |
|                                                                         |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This does nothing more than execute the UserFunction "testFunc(s)".

[%%]


[3] SEQUENCER QUERY EXAMPLE

* Let's design a little query scheme using the Sequencer that asks the user
for name, rank, and serial number.  The user can back out of the sequence
at any step of the query, and none of the previous values will be changed.

In this example, these values are stored in three global variables, with the
simple names:

   name
   rank
   serno

Since the user can input new values but back out before completing input, the
input values he or she provides have to be buffered in a separate set of
variables, which can be conveniently named:

   newname
   newrank
   newserno

* The first thing our sample program has to do is initialize these variables.
It's not really necessary to initialize the buffer variables, since they
won't be used until somebody actually loads them, but it is a good
programming practice to always initialize variables to prevent unpleasant
surprises.

These variables can be initialized by a UserFunction named "Init".  It's
perfectly simple, consisting of six Set Global (or Set Variable) objects, one
for each variable, plus a text constant containing the arbitrary string
"NULL" that is fed into all six.

* Performing the queries is also simple.  They all basically request strings,
so we can use the same UserFunction for all three queries, and just provide
it with parameters giving the prompt string and the name of the variable to
be updated.

We'll call this UserFunction "GetReply()".  All it does is pop up a Text
Input dialogue box to query the user and get his or her reply.  To allow the
Sequencer to test the results of the query, the UserFunction returns "1" if
the user makes an entry, and "0" if the user cancels the dialogue box.

The resulting UserFunction is very simple:

   +------------------------------------------------------------------+
   |                            GetReply                              |
   +---------+--------------------------------------------------------+
   |         |                                                        |
   |         |                +---+                                   |
   | prompt  +---+            | 1 +--------------------+              |
   |         |   |            +-+-+                    |              |
   |         |   |              |                      |              |
   |         |   |   +----------+----------+           |              |
   |         |   |   |      Text Input     |           |              |
   |         |   |   +--------+---+--------+           +-->+-----+    |
   |         |   +-->| Prompt |   | Value  +--------+      | JCT +--->|
   |         |       |        |   | Cancel +---+    |  +-->+-----+    |
   |         |       +--------+---+--------+   |    |  |              |
   |         |                               +-+-+  |  |              |
   |         |                               | 0 +--|--+              |
   |         |                               +---+  |                 |
   |         |                                      |   +----------+  |
   |         |                                      +-->|   Set    |  |
   | varname +----------------------------------------->|  Global  |  |
   |         |                                     name +----------+  |
   |         |                                                        |
   +---------+--------------------------------------------------------+

The core is a Text Input dialogue box, with the "Prompt" pin brought out to
allow it to be defined as a parameter.  A Set Global (or Set Variable)
object allows you to set the value of a variable, whose name is also input
as a parameter.  The UserFunction puts a value of "1" on the output, which
is overwritten by a "0" if the "Cancel" pin is activated.

* This UserFunction is called by the Sequencer to perform the set of queries.
The first query asks for the user's name.  The transaction is set up as
follows:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ TEST: ] [ Query1 ] [   ENABLED   ]                                    |
| SPEC NOMINAL:        [ 1    ] [ LIMIT ]                   [ == ] [1   ] |
| FUNCTION: [GetReply("Your name?","newname")]       [ LOGGING DISABLED ] |
| [  IF PASS  ] [  THEN CONTINUE  ]                                       |
| [  IF FAIL  ] [  THEN RETURN:   ] [ 1 ]                                 |
| DESCRIPTION:  [                                ]                        |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This defines a test transaction named "Query1", which tests for a limit equal
to "1".  That is, the test succeeds if the value is "1", and fails if it is
anything else.  The "SPEC NOMINAL" is set to "1", since that's the expected
proper return value, but it's largely irrelevant in this case.

It calls "GetReply()", providing a prompt of "Your name?", and specifying
that the return value be stored in the variable "newname".  If "GetReply()"
returns a "1", the test passes and the Sequencer moves on to the next
transaction.  If "GetReply()" returns a "0", the test fails and the Sequencer
exits on the THEN RETURN, and also putting an arbitrary value of 1 on the
output pin.

In reality, you would probably want to use a somewhat more tactful prompt,
such as:  "Please input your name."  -- but I used a short prompt here just
to save space in the illustration.

* The second transaction asks for rank:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ TEST: ] [ Query2 ] [   ENABLED   ]                                    |
| SPEC NOMINAL:        [ 1    ] [ LIMIT ]                   [ == ] [1   ] |
| FUNCTION: [GetReply("Your rank?","newrank")]       [ LOGGING DISABLED ] |
| [  IF PASS  ] [  THEN CONTINUE  ]                                       |
| [  IF FAIL  ] [  THEN GOTO:     ] [ Query1 ]                            |
| DESCRIPTION:  [                                ]                        |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This is very similar to the first test transaction, except for a different
name ("Query2"), different parameters for "GetReply()" ("Your rank?"  and
"newrank"), and a different action on test failure.  Instead of performing a
THEN RETURN, it does a THEN GOTO and jumps back to the first transaction,
"Query1".

* The third transaction asks for serial number:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ TEST: ] [ Query3 ] [   ENABLED   ]                                    |
| SPEC NOMINAL:        [ 1    ] [ LIMIT ]                   [ == ] [1   ] |
| FUNCTION: [GetReply("Serial number?","newserno")]  [ LOGGING DISABLED ] |
| [  IF PASS  ] [  THEN CONTINUE  ]                                       |
| [  IF FAIL  ] [  THEN GOTO:     ] [ Query2 ]                            |
| DESCRIPTION:  [                                ]                        |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This is almost the same as the second transaction, except for the transaction
name ("Query3"), different parameters for "GetReply()" ("Serial number?" and
"newserno"), and a THEN GOTO on failure back to the second transaction,
"Query2".

* This creates a sequence that steps forward through all three queries,
moving backwards through the sequence if the user cancels on the inputs.

Remember, however, that the queries have put values into buffer variables,
so an "execute" Sequencer transaction has to be built to load the actual
variables from the buffer variables, calling a UserFunction named
"UpdateData()".

All "UpdateData()" does is use three Get Global (or Get Variable) objects to
get the values of "newname", "newrank", and "newserno", and three Set Global
(or Set Variable) objects to load the respective values into the variables
"name", "rank", and "serial number".

The fourth transaction looks like this:

+-------------------------------------------------------------------------+
|                          Sequence Transaction                           |
+-------------------------------------------------------------------------+
| [ EXEC: ] [ Update ] [   ENABLED   ]                                    |
| FUNCTION:      [ UpdateData()]  [  LOGGING DISABLED ]                   |
| [  THEN CONTINUE  ]                                                     |
| DESCRIPTION:  [                                   ]                     |
|                                                                         |
|                                                                         |
|                                                                         |
|                [  OK  ]                         [Cancel]                |
+-------------------------------------------------------------------------+

This transaction is named "Update", and all it does is call "UpdateData()".
Nothing to it. 

Since the Sequencer executes transactions step by step, and this is the last
of the four, this one will not be executed unless the previous three were.
This allows the user to cancel all inputs up to the very last one without
changing the original values of the "name", "rank", and "serno" variables.

* The final sample program is about as simple as you might want:

                       +-----------+
                       | Call Init |
                       +-----+-----+
                             |
   +-------------------------+--------------------------+
   |                      Sequencer                     |
   +---+---------------------------------------+--------+
   |   | Query1(1) == 1                        |        |
   |   | Query2(1) == 1                        | Return |
   |   | Query3(1) == 1                        |        |
   |   | Update                                |        |
   |   |                                       |   Log  |
   |   |                                       |        |
   +---+---------------------+-----------------+--------+
                             |
                  +----------+---------+    +-------------+
                  | Get Global: name   +--->| Wyle Coyote |
                  +----------+---------+    +-------------+
                             |
                  +----------+---------+    +-------------+
                  | Get Global: rank   +--->| Predator    |
                  +----------+---------+    +-------------+
                             |
                  +----------+---------+    +-------------+
                  | Get Global: serno  +--->| 1           |
                  +----------+---------+    +-------------+

This displays some representative user inputs.

[%%]


[3] COMMENTS

* This example was written using as conservative features of VEE as were
possible to allow it to work on older versions.  It can be mildly
streamlined by using the latest VEE features.

Of course, this is an example, and has to be simple.  A few comments for
changes and improvements can be provided:

% There is no need to use the same query UserFunction in all transactions.
   They could all call different UserFunctions.

% For many user input parameters, it might be more convenient to store them
   in an array, rather than keep track of multiple variables.

% Use of globals can be troublesome in large programs.  Current versions of
   VEE allow variables to be "local to context", and this program could be
   encapsulated in a UserFunction and rewritten to use local variables to
   keep it from having unpleasant side effects.

More complicated decision making could be performed by using additional
transactions to test the value of a variable and branch appropriately.
However, one of the problems with the Sequencer is that it has enough
features to create really complicated solutions when a simpler one might be
much easier to write and maintain.

However, in this case, the Sequencer proved to be clearly the very best tool
for the job.

[<>]

Outcomes