home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Stars of Shareware: Programmierung
/
SOURCE.mdf
/
programm
/
msdos
/
pascal
/
totdoc11
/
chapt20.txt
< prev
next >
Wrap
Text File
|
1991-02-11
|
35KB
|
799 lines
Extending
Input
Field
Types
"Stay humble. Always answer the phone - no matter who else is in the
car."
Jack Lemmon
One of the most used elements of the Toolkit is the full screen input
facility. If the Toolkit field types do not meet your exact needs, you
can create your own custom field types. This chapter explains how.
The Input Object Hierarchy
The objects FormOBJ and WinFormOBJ are used to manage and control full
screen input. You may recall that the method AddItem is used to add
individual input fields to the form. AddItem accepts any of the input
fields shown in the TOTIO Object Hierarchy on page 11.5. As the diagram
illustrates, all IO objects are descended from the root object Base-
IOOBJ. If you want to create new input field objects which can be
managed by the form objects, the new objects must be descended from
BaseIOOBJ, or any object descended from BaseIOOBJ.
The BaseIOOBJ object includes the following data and methods, which are
inherited by all descendant objects:
ItemIOOBJ = object
vBoundary: tCoords;
vHotKey: word;
vID: word;
vActive: boolean;
{methods ...}
constructor Init;
procedure SetActiveStatus(Selectable:boolean);
function Active:boolean;
function GetHotKey: word;
procedure SetHotkey(HK:word);
function GetID: word;
procedure SetID(ID:word);
function Visible: boolean; VIRTUAL;
procedure Display(Status:tStatus); VIRTUAL;
function Select(K:word; X,Y:byte):tAction; VIRTUAL;
function ProcessKey(InKey:word;X,Y:byte):tAction; VIRTUAL;
function Suspend:boolean; VIRTUAL;
destructor Done; VIRTUAL;
end; {ItemIOOBJ}
Note: the BaseIOOBJ also includes signal-related methods. These are
discussed in a later section.
20-2 Extending the Toolkit
--------------------------------------------------------------------------------
The vBoundary variable identifies the (X1,Y1) and (X2,Y2) coordinates
of the field. When the user clicks the left mouse button during full-
form input, the form object scans the list of active input objects and
moves the user to the input object with coordinates corresponding to
the mouse cursor position. Any descendant field should therefore update
the vBoundary variable to indicate the physical location of the field.
The other three BaseIOOBJ variables identify the field's hotkey, ID and
whether the field is active or selectable. These variables are managed
by the BaseIOOBJ methods SetActiveStatus, Active, GetHotkey, SetHotkey,
SetID and GetID. All these methods are suitable for any field type, and
should not need modification in descendant objects. Just inherit them
and use them!
The virtual methods, highlighted in bold, are specific to each descen-
dant object. As a minimum, any descendant objects should redefine these
bold methods -- they are the main methods called by the form object
during full-screen input.
Apart from special hotkeys and navigation control keys, all the user
input fields are visible. That is, the user can see them. As the TOTIO
Hierarchy Diagram illustrates, all visible fields are descended from
VisibleIOOBJ, which is, in turn, descended from BaseIOOBJ. In addition
to the BaseIOOBJ objects just discussed, the VisibleIOOBJ objects
inherit the following methods:
procedure SetLabel(Lbl:string);
procedure SetMessage(X,Y:byte; Msg:string);
procedure WriteMessage;
procedure WriteLabel(Status:tStatus); VIRTUAL;
As their names suggest, these methods are used to set and display
labels and messages. Labels are displayed to the immediate left of a
field and act as a field title. A message is the field's descriptive
text which is displayed when the user moves to the field. Under normal
circumstances you will not need to modify these methods. They are
appropriate to any field type.
Creating New Field Types
When you want to create a new field object, you must decide which
existing field object has the properties most closely resembling the
new field type you want to create. For example, if the field includes
data input, then you would probably create a descendant of CharIOOBJ.
However, if the field has multiple lines (like a radio button or list),
then the new object would best be a descendant of MultiLineIOOBJ. If
none of the existing fields come anywhere close, create a descendant
from VisibleIOOBJ.
Extending Input Field Types 20-3
--------------------------------------------------------------------------------
To illustrate the principles, a new boolean object will be created.
This object will display two different options, e.g. Yes or No, True or
False, Live or Die, etc. The field will display one of the options, and
when the user presses the space bar or clicks the mouse, the field will
flip to the other option.
Since the boolean object does not process individual character input,
and does not occupy multiple lines, the best object to descend from is
VisibleIOOBJ. The following methods are inherited from VisibleIOOBJ and
do not need to be modified:
procedure SetActiveStatus(Selectable:boolean);
function Active:boolean;
function GetHotKey: word;
procedure SetHotkey(HK:word);
function GetID: word;
procedure SetID(ID:word);
procedure SetLabel(Lbl:string);
procedure SetMessage(X,Y:byte; Msg:string);
procedure WriteMessage;
procedure WriteLabel(Status:tStatus); VIRTUAL;
function Visible: boolean; VIRTUAL;
As well as replacing Init and Done, the primary inherited methods which
need to be over-written are Display, Select, Processkey and Suspend.
These four methods are called by the form object during full screen
input. Additionally, if you want the new boolean object to function
stand-alone, i.e. without being part of a form, an Activate method
should be added. Activate will display the field and process user input
until [KEYCAP] or [KEYCAP] is pressed.
In keeping with the Toolkit style, SetValue and GetValue methods should
also be added. These methods are used to set the object's default
value, i.e. which option to display when the field is activated, and to
get the user-selected value after input is complete.
The new boolean object will need to include three data variables: the
two strings that represent the true and false settings, and a boolean
to record the object's actual value.
After all the methods and data have been included, the definition of
the new BooleanIOOBJ is as follows:
BooleanIOOBJ = object (VisibleIOOBJ)
OnString: StringBut;
OffString: StringBut;
vInput: boolean;
{methods...}
Constructor Init(X,Y:byte; Yes,No:stringbut);
function GetValue: boolean;
procedure SetValue(On:boolean);
procedure Activate;
20-4 Extending the Toolkit
--------------------------------------------------------------------------------
procedure Display(Status:tStatus); VIRTUAL;
function Select(K:word; X,Y:byte):tAction; VIRTUAL;
function ProcessKey(InKey:word;X,Y:byte):tAction; VIRTUAL;
function Suspend:boolean; VIRTUAL;
destructor Done; VIRTUAL;
end; {BooleanIOOBJ}
In the following sections, each method is individually discussed:
Extending Input Field Types 20-5
--------------------------------------------------------------------------------
Init
The primary responsibilities of the Init method are to set the values
of the true and false strings, and to update the vBoundary variable
with the location of the field. In keeping with the other input fields,
the Init method is passed the (X,Y) coordinate of the leftmost charac-
ter. By finding the length of the longest string, the method can com-
pute the rightmost (X,Y) coordinate. The method detail is, therefore,
as follows:
constructor BooleanIOOBJ.Init(X,Y:byte; Yes,No:stringbut);
{}
var L:byte;
begin
VisibleIOOBJ.Init;
OnString := Yes;
OffString := No;
L := length(OnString);
if L < length(OffString) then
L := length(OffString);
with vBoundary do
begin
X1 := X;
X2 := X + pred(L);
Y1 := Y;
Y2 := Y;
end;
vInput := true;
end; {BooleanIOOBJ.Init}
SetValue and GetValue
These methods are short and to the point. All they do is set or return
the value of the field, and are defined as follows:
function BooleanIOOBJ.GetValue: boolean;
{}
begin
GetValue := vInput;
end; {BooleanIOOBJ.GetValue;
procedure BooleanIOOBJ.SetValue(On:boolean);
{}
begin
vInput := On;
end; {BooleanIOOBJ.SetValue}
20-6 Extending the Toolkit
--------------------------------------------------------------------------------
Display
Display is a virtual method which must be declared with a single passed
parameter of type tStatus. tStatus is an enumerated type with the mem-
bers HiStatus, Norm and Off, and the value is used to indicate whether
the field is highlighted (the field the user is currently editing),
normal (one of the other fields in a form), or inactive (cannot be
selected).
The primary responsibility of Display is to display the field contents
in the appropriate color. The first task is to decide the display
attribute. To be consistent with the other input objects, the field
should ascertain the attribute by calling a TOTIO^ function method.
TOTIO controls the colors for field labels and messages, button fields,
group fields, list fields, and single line fields. In this case, the
single line field colors are appropriate. Refer to page 11-40 for a
full discussion of IOTOT.
The BooleanIOOBJ method Display is implemented as follows:
procedure BooleanIOOBJ.Display(Status:tStatus);
{}
var Att: byte;
begin
case Status of
HiStatus: Att := IOTOT^.FieldCol(2);
Norm: Att := IOTOT^.FieldCol(1);
Off: Att := IOTOT^.FieldCol(4);
end; {case}
with vBoundary do
if vInput then
Screen.WriteAT(X1,Y1,Att,padleft(OnString,succ(X2-X1),' '))
else
Screen.WriteAT(X1,Y1,Att,padleft(OffString,succ(X2-X1),' '));
end; {BooleanIOOBJ.Display}
Select
The Select method is called by the form object whenever the user tries
to enter the field. The method is responsible for displaying the field
contents as well as the field's label and message. Select is also
responsible for moving the cursor to the field.
Select is actually a function method which returns a member of the
enumerated type tAction. tAction is used to instruct the form object on
how to proceed, and includes the members None, NextField, PrevField,
Finished, Escaped, Refresh, Signal, Enter, Help, Stop1..Stop9. Under
normal circumstances, Select should return a value of None. This
Extending Input Field Types 20-7
--------------------------------------------------------------------------------
instructs the Toolkit to proceed as normal. The majority of the other
members are used by "buttons", which the user selects to finish the
input session or to ask for help.
The BooleanIOOBJ method Select is implemented as follows:
function BooleanIOOBJ.Select(K:word; X,Y:byte):tAction;
{}
begin
Display(HiStatus);
WriteLabel(HiStatus);
WriteMessage;
Screen.GotoXY(vBoundary.X1,vBoundary.Y1);
Select := none;
end; {BooleanIOOBJ.Select}
ProcessKey
When a field is active, the form object repeatedly passes the user
input to the highlighted object. This continues until the user moves to
another field, or presses a key which indicates the user wants to fin-
ish the input session.
The form object calls the field's method ProcessKey and passes the user
input to it. The ProcessKey method then updates the value of the field
based on the user's input. In the case of the BooleanIOOBJ field, the
field will flip to the other string whenever the keys [KEYCAP] [KEYCAP]
or [KEYCAP] are pressed. The field will also flip if the mouse is
clicked on the field. The Toolkit responds extremely quickly to a mouse
press, and it is a good idea to delay for a tenth of a second when the
mouse has been clicked. This overcomes the problem of the field flip-
ping a dozen or more times each time the user clicks the mouse.
ProcessKey is a function method which returns a member of the enumer-
ated type tAction. Under normal circumstances, the function should
return a value of None, which indicates that the form object should
continue passing keys to the field.
Note: if you were creating a different input field type, you might
want to return a value of NextField when the current field became
full. This instructs the form object to suspend the current field
and select the next field.
The BooleanIOOBJ method ProcessKey is implemented as follows:
20-8 Extending the Toolkit
--------------------------------------------------------------------------------
function BooleanIOOBJ.ProcessKey(InKey:word;X,Y:byte):tAction;
{}
begin
if (InKey = 513)
or (InKey = 32)
or (inKey = 328)
or (InKey = 336) then
begin
vInput := not vInput;
Display(HiStatus);
end;
if InKey = 513 then {absorb mouse}
delay(100);
ProcessKey := None;
end; {BooleanIOOBJ.ProcessKey}
Suspend
The Suspend method is called by the form object when the user wants to
terminate input or move to another field. Suspend is responsible for
displaying the field and label in the normal attribute, and for remov-
ing the field message. All these tasks are performed by the inherited
VisibleIOOBJ method Suspend.
Suspend is actually a function method which returns a boolean value.
This provides a mechanism for not allowing the user to leave the field.
If Suspend returns False, the form object stays in the field. This
facility should only be used when the user has entered some invalid
input, and it is good practice to display a message stating how the
user can correct the problem.
A user cannot enter an invalid value in a BooleanIOOBJ, and so Suspend
will always return True. The method Suspend is implemented as follows:
function BooleanIOOBJ.Suspend:boolean;
{}
begin
Suspend := VisibleIOOBJ.Suspend;
end; {BooleanIOOBJ.Suspend}
Activate
The Activate method provides a way to use the object as a stand-alone
field, i.e. not as part of a form. Activate is responsible for select-
ing the field, getting input, and passing the input to ProcessKey.
Activate should repeatedly pass input to ProcessKey until the user
presses [KEYCAP] or [KEYCAP] to indicate the end of input. The method
Suspend should then be called to deselect the field.
Extending Input Field Types 20-9
--------------------------------------------------------------------------------
The BooleanIOOBJ method Activate is implemented as follows:
procedure BooleanIOOBJ.Activate;
{}
var
Action: tAction;
begin
Action := Select(0,0,0);
with Key do
begin
repeat
GetInput;
Action := ProcessKey(LastKey,LastX,LastY);
until ((LastKey = 324) or (LastKey = 13)) and Suspend;
end;
end; {BooleanIOOBJ.Activate}
Done
Since BooleanIOOBJ has no dynamic data of its own, all Done needs to do
is call VisibleIOOBJ's method Done, as follows:
destructor BooleanIOOBJ.Done;
{}
begin
VisibleIOOBJ.Done;
end; {BooleanIOOBJ.Done}
That's the new BooleanIOOBJ defined. The full source code is contained
in the file EXTIO.PAS.
Using BooleanIOOBJ
Since BooleanIOOBJ is inherited from BaseIOOBJ, it can be used in full
form input just like any other input object. Listed below is the demo
program EXTDEM7.PAS which shows the new object in action. This demo
program is actually an adaptation of DEMIO2.PAS discussed in chapter
11. Figure 20.1 illustrates the generated display.
Program ExtendedDemoSeven;
Uses DOS,CRT,
totFAST, totIO1, totIO2, extIO;
20-10 Extending the Toolkit
--------------------------------------------------------------------------------
Var
Name: LateralIOOBJ;
Phone: PictureIOOBJ;
Price: FixedRealIOOBJ;
Status: BooleanIOOBJ;
Keys: ControlkeysIOOBJ;
Manager: FormOBJ;
Result: tAction;
procedure InitVars;
{}
begin
with Name do
begin
Init(35,5,20,40);
SetLabel('Vendor Name');
end;
with Phone do
begin
Init(35,7,'(###) ###-####');
SetLabel('Tel');
SetRules(JumpIfFull);
end;
with Price do
begin
Init(35,9,8,2);
SetLabel('Unit Price');
SetValue(250.0);
SetMinMax(0.1,12250.0);
SetRules(EraseDefault);
end;
with Status do
begin
Init(35,11,' Nice Guy ',' Jerk ');
SetLabel('Category');
end;
Keys.Init;
end; {InitVars}
begin
ClrScr;
Screen.TitledBox(15,3,65,13,76,79,78,2,' Quicky Input Demo ');
Screen.WriteCenter(25,white,'Press TAB to switch fields
and press ESC or F10 to end');
InitVars;
with Manager do
begin
Init;
AddItem(Keys);
Extending Input Field Types 20-11
--------------------------------------------------------------------------------
AddItem(Name);
AddItem(Phone);
AddItem(Price);
AddItem(Status);
Result := Go;
if Result = Finished then
{update the database..}
else
{call Esc routine};
end;
end.
Figure 20.1 [SCREEN]
Using
BooleanIOOBJ
Understanding Signals
In sophisticated input forms, the data input by a user in one field may
affect the data of some related fields on the form. Signals provide
this capability in the Toolkit.
Signal Theory
The BaseIOOBJ object includes the following three signal-related meth-
ods:
procedure RaiseSignal(var TheSig:tSignal); VIR-
TUAL;
procedure ShutdownSignal(var BaseSig:tSignal);
VIRTUAL;
procedure HandleSignal(var BaseSig:tSignal; var NewSig:tSignal); VIR-
TUAL;
The totIO1 unit includes the record tSignal, which is defined as fol-
lows:
tSignal = record
ID: word;
MsgType: word;
case word of {variant record}
0: (MsgPtr: pointer);
1: (MsgLong: longint);
2: (MsgWord: word);
3: (MsgInt: integer);
20-12 Extending the Toolkit
--------------------------------------------------------------------------------
4: (MsgByte: byte);
5: (MsgChar: char);
end;
tSignal is a variant record which can be used to store any data which
needs to be communicated between input fields.
An input field's object methods Select, ProcessKey, and ProcessEnter
are function methods which return a value of type tAction. If any of
these methods return a value of SIGNAL, the form object will immedi-
ately call that input object's RaiseSignal method. This method is
passed a variable parameter of type tSignal. The variable is updated
with the information which needs to be passed to other fields. Each
signal has an ID, and the ID should be assigned a non-zero value. In a
situation where more than one signal can be raised, this ID will indi-
cate to the other fields which signal is being raised. The signal's
MsgType field can be used to provide further information about the
signal, and usually indicates the format of the data being passed with
the signal, e.g. a 1 might indicate a longint, a 2 might indicate a
word, etc.
Note: Input objects which are descended from CharIOOBJ inherit the
virtual function method ProcessEnter. This method is passed no
parameters, and returns a value of type tAction. The method is
called whenever the user presses [KEYCAP]. It is typically used to
raise a signal or move the user to the next field.
When a field raises a signal, the form manager passes the signal to the
next field in the form. This is achieved by calling the next field's
method HandleSignal. This method is passed the tSignal variable raised
by the originating field. The HandleSignal method can inspect the sig-
nal ID and, if appropriate, respond to the signal. If the field han-
dling the signal knows that the signal is not intended for any other
field, it can update the signal ID with a value of 0. This tells the
form object that the signal has been handled, and the signal is dis-
carded. Otherwise, the signal is passed to each input field in turn
until one of the fields sets the ID to 0, or until all the fields have
been passed the signal.
When the signal has been handled or passed to every other field, the
originating field's ShutdownSignal method is called. This method can be
used to dispose of any data that was created specifically for the sig-
nal, and for any other housekeeping.
Extending Input Field Types 20-13
--------------------------------------------------------------------------------
Any object which handles a signal can optionally raise a signal of its
own. The HandleSignal method is passed two parameters of type tSignal.
The first parameter is the original signal raised by another field. The
second parameter is an empty signal which the handling field can update
with its own signal. The form object inspects the second signal
returned by the field's HandleSignal method, and if the ID is set to a
non-zero value, a new signal is raised and passed to the other fields.
Only when this new signal has been handled will the form manager con-
tinue with the processing of the original signal.
A Signal Example
The way to use signals is best illustrated by example. In this section
a demo program will be developed which prompts the user to input some
directories, as a precursor to installing some software. The user is to
be prompted to input five different directories, one for the programs,
one for the doc files, etc. Like Turbo Pascal's own Install program,
each of the input fields needs to be updated if the user enters a new
default directory into the first field.
To solve this problem, two new field objects must be created, and both
of them will be descended from StringIOOBJ. One object will be used to
prompt the user to input the default directory, and will raise a signal
when the user changes the field value. The other object will be used
for the input of the other directories, and will include a method to
handle the signal raised by the first object.
In this example, the first object is called MasterStringIOOBJ, and it
will raise a signal whenever the user enters a new directory. The new
object is declared as follows:
TYPE
MasterStringIOOBJ = object (StringIOOBJ)
vLastInput: string;
{methods}
constructor Init(X,Y,FieldLen: byte);
function ProcessEnter: tAction; VIRTUAL;
function Select(K:word; X,Y:byte): tAction; VIRTUAL;
procedure RaiseSignal(var TheSig:tSignal); VIRTUAL;
procedure ShutdownSignal(var BaseSig:tSignal); VIRTUAL;
function Suspend:boolean; VIRTUAL;
destructor Done; VIRTUAL;
end; {MasterStringIOOBJ}
The new object should only raise a signal when the user has changed the
value of the field. The new string variable vLastInput is used to
record the value of the string when the field is selected. The value of
20-14 Extending the Toolkit
--------------------------------------------------------------------------------
vLastInput can then be compared to vInputStr (the edited field value)
when the user tries to leave the field or presses [KEYCAP]. The method
Select is therefore declared as follows:
function MasterStringIOOBJ.Select(K:word; X,Y:byte): tAction;
{}
begin
vLastInput := vInputStr;
Select := StringIOOBJ.Select(K,X,Y);
end; {MasterStringIOOBJ.Select}
The object needs to raise a signal when the user presses [KEYCAP]. The
method ProcessEnter is implemented as follows:
function MasterStringIOOBJ.ProcessEnter: tAction;
{}
begin
if vLastInput <> vInputStr then {need to signal}
ProcessEnter := Signal
else
ProcessEnter := none;
end; {MasterStringIOOBJ.ProcessEnter}
If the value of the string has changed, SIGNAL is returned, otherwise
NONE is returned.
The object also needs to raise a signal when the method Suspend is
called, and the value of the field has changed. Now we are faced with a
problem, because Suspend cannot directly raise a signal. Suspend
returns a boolean value to indicate whether the field can be suspended,
not a tAction value. The trick is to return a boolean value of False,
indicating that the user may not leave the field, and then stuff the
keyboard with the keystrokes [KEYCAP] [KEYCAP]. The Toolkit will not
allow the user to leave the field, the [KEYCAP] key will then be pro-
cessed, thereby raising a signal via the ProcessEnter method, and
finally, the [KEYCAP] key will be processed to move the user to the
next field. The Suspend method is implemented as follows:
function MasterStringIOOBJ.Suspend:boolean;
{}
begin
if vLastInput <> vInputStr then {need to signal}
begin
Suspend := false;
Key.StuffBuffer(13); {Enter}
Key.StuffBuffer(9); {Tab}
end
else
Suspend := StringIOOBJ.Suspend;
end; {MasterStringIOOBJ.Suspend}
Extending Input Field Types 20-15
--------------------------------------------------------------------------------
The RaiseSignal method must update the signal variable with the infor-
mation required by the other fields, i.e. the string representing the
new directory entered by the user. The RaiseSignal method is
implemented as follows:
procedure MasterStringIOOBJ.RaiseSignal(var TheSig:tSignal);
{}
begin
with TheSig do
begin
ID := SignalNewDirectory;
MsgType := length(vInputStr);
MsgPtr := @vInputStr;
end;
vLastInput := vInputStr;
end; {MasterStringIOOBJ.RaiseSignal}
The signal ID is set to SignalNewDirectory -- a constant assigned the
value of 1. The MsgType field is set to indicate the length of the
string, and the variant record MsgPtr is updated to point to the user
input string. This signal, therefore, provides sufficient data for the
dependent fields to ascertain the new directory.
In this example, no dynamic data is created for the signal, and so the
ShutdownSignal method doesn't need to do anything.
Now let's turn our attention to the object which needs to respond to
the signal raised by MasterStringIOOBJ. In this example, we will name
the new object SlaveStringIOOBJ, and it will inherit all the properties
of StringIOOBJ. The only method (in addition to Init and Done) which
needs to be updated is HandleSignal. This method needs to check the
value of the Signal and update the value of the field with the new
directory string. The signal field MsgType stores the length of the new
string, and the field MsgPtr points to the new string.
The HandleSignal method is implemented as follows:
procedure SlaveStringIOOBJ.HandleSignal(var BaseSig:tSignal; var NewS-
ig:tSignal);
{}
var temp:string;
begin
with BaseSig do
begin
if (ID = SignalNewDirectory) then
begin
move(MsgPtr^,Temp,succ(MsgType));
if Temp <> vInputStr then
begin
vInputStr := Temp;
20-16 Extending the Toolkit
--------------------------------------------------------------------------------
Display(Norm);
end;
end;
end;
end; {SlaveStringIOOBJ.HandleSignal}
To recap, two new field objects have been created. A MasterStringIOOBJ
field raises a signal when its value is changed, and SlaveStringIOOBJ
fields change their value accordingly. The on-disk demo file EXT-
DEM8.PAS includes the full solution to the problem. Figure 20.2 illus-
trates the output generated by this program.
Figure 20.2 [SCREEN]
Raising
Signals
Review the source code of the DirWinOBJ object in totDIR for another
example of how fields can communicate with signals.