695 lines
25 KiB
Plaintext
695 lines
25 KiB
Plaintext
|
|
======================================================================
|
|
Additional Turbo Vision Documentation
|
|
======================================================================
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Table of Contents
|
|
----------------------------------------------------------------------
|
|
A. Additional reference material
|
|
|
|
1. Enhancements to OBJECTS.PAS
|
|
a. New TCollection.AtFree method
|
|
b. Duplicate keys in sorted collections
|
|
c. Changes to TEmsStream.Init to support EMS 3.2
|
|
|
|
2. Enhancements to DRIVERS.PAS
|
|
a. MouseReverse variable
|
|
|
|
3. Enhancements to VIEWS.PAS
|
|
a. ErrorAttr variable
|
|
b. TWindow.Close method
|
|
c. cmListItemSelected constant
|
|
d. TListViewer.SelectItem method
|
|
|
|
4. Enhancements to DIALOGS.PAS
|
|
a. bfBroadcast constant
|
|
b. TButton.Press method
|
|
|
|
5. Enhancements to MEMORY.PAS
|
|
a. bfBroadcast constant
|
|
b. TButton.Press method
|
|
|
|
6. Stream RegisterXXXX procedures and ID codes
|
|
|
|
B. Additional explanatory material
|
|
1. Overlaying Turbo Vision applications
|
|
2. Ordering of inherited calls
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
This appendix contains additional explanatory and reference
|
|
material about Turbo Vision.
|
|
|
|
1. Enhancements to OBJECTS.PAS
|
|
------------------------------
|
|
|
|
TCollection.AtFree method
|
|
-------------------------
|
|
|
|
procedure TCollection.AtFree(Index: Integer);
|
|
|
|
Deletes and disposes of the item at the given Index. Equivalent to
|
|
|
|
Item := At(Index);
|
|
AtDelete(Index);
|
|
FreeItem(Item);
|
|
|
|
Duplicate keys in sorted collections
|
|
------------------------------------
|
|
|
|
TSortedCollection implements sorted collections both with or without
|
|
duplicate keys. The TSortedCollection.Duplicates field controls
|
|
whether duplicates are allowed or not. It defaults to False,
|
|
indicating that duplicate keys are not allowed, but after creating a
|
|
TSortedCollection you can set Duplicates to True to allow elements
|
|
with duplicate keys in the collection.
|
|
|
|
When Duplicates is True, the Search method returns the index of the
|
|
first item in the collection that has the given key, and the Insert
|
|
method inserts an item before other items (if any) with the same
|
|
key. The IndexOf method uses Search to locate the first item with
|
|
the key given by the Item parameter, and then performs a linear
|
|
search to find the exact Item.
|
|
|
|
TSortedCollection overrides the Load and Store methods inherited
|
|
from TCollection to also load and store the value of the Duplicates
|
|
field.
|
|
|
|
|
|
TEmsStream.Init method
|
|
----------------------
|
|
|
|
constructor TEmsStream.Init(MinSize, MaxSize: Longint);
|
|
|
|
EMS drivers earlier than version 4.0 don't support resizeable
|
|
expanded memory blocks. With a pre-4.0 driver, an EMS stream cannot
|
|
be expanded beyond its initial size once it has been allocated. To
|
|
properly support both older and newer EMS drivers, a TEmsStream's
|
|
Init constructor takes two parameters which specify the minimum and
|
|
maximum size of the initial EMS memory block allocation. Init will
|
|
always allocate at least MinSize bytes.
|
|
|
|
If the EMS driver version number is greater than or equal to 4.0,
|
|
Init allocates only MinSize bytes of EMS, and then expands the block
|
|
as required by subsequent calls to TEmsStream.Write. MaxSize is
|
|
ignored.
|
|
|
|
If the driver version number is less than 4.0, Init allocates as
|
|
much expanded memory as is available up to MaxSize bytes, and an
|
|
error will occur if subsequent calls to TEmsStream.Write attempt to
|
|
expand the stream beyond the allocated size.
|
|
|
|
|
|
2. Enhancements to DRIVERS.PAS
|
|
------------------------------
|
|
|
|
MouseReverse variable in Drivers
|
|
-----------------------------------
|
|
|
|
const MouseReverse: Boolean = False;
|
|
|
|
Setting MouseReverse to True causes Turbo Vision's event manager to
|
|
reverse the mbLeftButton and mbRightButton flags in the Buttons
|
|
field of TEvent records.
|
|
|
|
|
|
3. Enhancements to VIEWS.PAS
|
|
----------------------------
|
|
|
|
ErrorAttr variable
|
|
------------------
|
|
|
|
const ErrorAttr: Byte = $CF;
|
|
|
|
Contains a video attribute byte used as the error return value of a
|
|
call to TView.GetColor. If TView.GetColor fails to correctly map a
|
|
palette index into a video attribute byte (because of an
|
|
out-of-range index), it returns the value given by ErrorAttr. The
|
|
default ErrorAttr value represents blinking high-intensity white
|
|
characters on a red background. If you see this color combination on
|
|
the screen, it is most likely an indication of a palette mapping
|
|
error.
|
|
|
|
TWindow.Close method
|
|
--------------------
|
|
|
|
Calls the TWindow's Valid method with a Command value of cmClose,
|
|
and then, if Valid returns True, closes the window by calling its
|
|
Done method.
|
|
|
|
cmListItemSelected constant
|
|
---------------------------
|
|
|
|
A TListViewer uses the Message function to send an evBroadcast event
|
|
with a Command value of cmListItemSelected to its TView.Owner
|
|
whenever an item in the list viewer is selected (by double-clicking
|
|
on it, or by moving the selection bar to the item and pressing the
|
|
spacebar). The InfoPtr of the event points to the TListViewer
|
|
itself.
|
|
|
|
|
|
TListViewer.SelectItem method
|
|
-----------------------------
|
|
|
|
The default SelectItem method sends a cmListItemSelected broadcast
|
|
to its Owner as follows:
|
|
|
|
Message(Owner, evBroadcast, cmListItemSelected, @Self);
|
|
|
|
|
|
4. Enhancements to DIALOGS.PAS
|
|
------------------------------
|
|
|
|
bfBroadcast constant in Dialogs
|
|
-------------------------------
|
|
|
|
const bfBroadcast = $04;
|
|
|
|
This flag is used in constructing the AFlags bit mask passed to
|
|
TButton.Init. It controls whether TButton objects should generate
|
|
events using the PutEvent method or the Message function. If
|
|
bfBroadcast is clear, a TButton uses PutEvent to geneate an
|
|
evCommand event whenever it is pressed:
|
|
|
|
E.What := evCommand;
|
|
E.Command := Command;
|
|
E.InfoPtr := @Self;
|
|
PutEvent(E);
|
|
|
|
If bfBroadcast is set, a TButton uses Message to send an evBroadcast
|
|
message to its Owner whenever it is pressed:
|
|
|
|
Message(Owner, evBroadcast, Command, @Self);
|
|
|
|
|
|
TButton.Press method
|
|
--------------------
|
|
|
|
procedure TButton.Press; virtual;
|
|
|
|
This method is called to generate the effect associated with
|
|
"pressing" a TButton object. The default method sends an evBroadcast
|
|
event with a command value of cmRecordHistory to the button's owner
|
|
(causing all THistory objects to record the contents of the
|
|
TInputLine objects they control), and then uses PutEvent or Message
|
|
to generate an event (see description of bfBroadcast flag). You can
|
|
override TButton.Press to change the behaviour of a button when it
|
|
is pressed.
|
|
|
|
5. Enhancements to MEMORY.PAS
|
|
-----------------------------
|
|
|
|
New SetMemTop procedure
|
|
-----------------------
|
|
|
|
procedure SetMemTop(MemTop: Pointer);
|
|
|
|
Sets the top of the application's memory block. The initial memory
|
|
top corresponds to the value stored in the HeapEnd variable.
|
|
SetMemTop is typically used to shrink the application's memory block
|
|
before executing a DOS shell or another program, and to expand the
|
|
memory block afterwards. For an example of how to use SetMemTop, See
|
|
TVDEMO.PAS in the \TP\TVDEMOS directory.
|
|
|
|
|
|
6. RegisterXXXX procedures and ID codes
|
|
---------------------------------------
|
|
|
|
To allow easy interface with streams, the App, ColorSel, Dialogs,
|
|
Editors, Menus, Objects, StdDlg, and Views units each define a
|
|
procedure which registers all object types in the unit using a
|
|
sequence of calls to RegisterType. These registration procedures all
|
|
have names of the form RegisterXXXX where XXXX is the name of the
|
|
containing unit. The types and object ID values registered by the
|
|
RegisterXXXX procedures are show below.
|
|
|
|
RegisterApp
|
|
TBackground 30
|
|
TDeskTop 31
|
|
|
|
RegisterColorSel
|
|
TColorSelector 21
|
|
TMonoSelector 22
|
|
TColorDisplay 23
|
|
TColorGroupList 24
|
|
TColorItemList 25
|
|
TColorDialog 26
|
|
|
|
RegisterDialogs
|
|
TDialog 10
|
|
TInputLine 11
|
|
TButton 12
|
|
TCluster 13
|
|
TRadioButtons 14
|
|
TCheckBoxes 15
|
|
TListBox 16
|
|
TStaticText 17
|
|
TLabel 18
|
|
THistory 19
|
|
TParamText 20
|
|
|
|
RegisterEditors
|
|
TEditor 70
|
|
TMemo 71
|
|
TFileEditor 72
|
|
TIndicator 73
|
|
TFileWindow 74
|
|
|
|
RegisterMenus
|
|
TMenuBar 40
|
|
TMenuBox 41
|
|
TStatusLine 42
|
|
|
|
RegisterObjects
|
|
TCollection 50
|
|
TStringCollection 51
|
|
|
|
RegisterStdDlg
|
|
TFileInputLine 60
|
|
TFileCollection 61
|
|
TFileList 62
|
|
TFileInfoPane 63
|
|
TFileDialog 64
|
|
TDirCollection 65
|
|
TDirListBox 66
|
|
TChDirDialog 67
|
|
|
|
RegisterViews
|
|
TView 1
|
|
TFrame 2
|
|
TScrollBar 3
|
|
TScroller 4
|
|
TListViewer 5
|
|
TGroup 6
|
|
TWindow 7
|
|
|
|
If your application uses stream I/O, you should call the appropriate
|
|
RegisterXXXX procedures in the application's Init method, and in
|
|
addition use RegisterType to register your own types:
|
|
|
|
type
|
|
TMyApp = object(TApplication)
|
|
constructor Init;
|
|
...
|
|
end;
|
|
|
|
constructor TMyApp.Init;
|
|
begin
|
|
RegisterApp;
|
|
RegisterDialogs;
|
|
RegisterMenus;
|
|
RegisterObjects;
|
|
RegisterViews;
|
|
RegisterType(RStringList);
|
|
RegisterType(RMyFirstType);
|
|
RegisterType(RMySecondType);
|
|
TApplication.Init;
|
|
...
|
|
end;
|
|
|
|
Notice the explicit call to RegisterType(RStringList) to register
|
|
the TStringList type. The RegisterObjects procedures does not
|
|
register the TStringList and TStrListMaker types, since they have
|
|
the same object type ID (52). Depending on whether your application
|
|
is using or generating string lists, you must manually register
|
|
TStringList or TStrListMaker.
|
|
|
|
See TVRDEMO.PAS and TVFORMS.PAS in the \TP\TVDEMOS directory for
|
|
examples of applications that perform stream registration.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
B. Additional explanatory material
|
|
1. Overlaying Turbo Vision applications
|
|
2. Ordering of inherited calls
|
|
----------------------------------------------------------------------
|
|
|
|
1. Overlaying Turbo Vision applications
|
|
---------------------------------------
|
|
|
|
Turbo Vision was designed to work efficiently in an overlaid
|
|
application. All Turbo Vision units can be overlaid, except for the
|
|
Drivers unit, which contains interrupt handlers and other low-level
|
|
system interfaces.
|
|
|
|
When designing an overlaid Turbo Vision application, carefully
|
|
consider which objects constitute the various "working sets" of your
|
|
application. At any given moment, the user will be interacting with
|
|
a group of objects. Therefore, the code for all of these objects
|
|
need to fit in the overlay pool at the same time to avoid excessive
|
|
disk access. Since Turbo Pascal's overlay manager swaps in entire
|
|
units at a time, do not place unrelated objects in the same overlaid
|
|
unit. If you do, when you use only one of the objects, the code for
|
|
all the others will also be swapped into the overlay pool and will
|
|
take up valuable space. Remember--when a unit is brought into the
|
|
overlay pool, another unit may very well be squeezed out.
|
|
|
|
Consider an example in which you're designing a special dialog that
|
|
contains some customized controls. Your dialog is derived from
|
|
TDialog and your custom controls are derived from TListViewer and
|
|
TInputLine. Placing all three derived object types in the same unit
|
|
makes sense because they're part of the same working set. However,
|
|
placing other unrelated objects in that unit would require a larger
|
|
overlay pool to hold your working set and therefore may cause disk
|
|
thrashing when you run the program.
|
|
|
|
Within a Turbo Vision application, the App, Memory, Menus Objects,
|
|
and Views units total about 50 kbytes of code and will almost always
|
|
be part of the current working set. In addition, units containing
|
|
your derived application object and any windows or dialogs with
|
|
which the user is currently interacting will also be part of the
|
|
working set, bringing the typical minimum overlay pool size to about
|
|
64K bytes.
|
|
|
|
Through experimentation, you can determine the ideal size of the
|
|
overlay pool. In general, the presence of EMS makes code swapping
|
|
much faster and allows you to reduce the size of overlay pool by 25%
|
|
to 35%. Determining the best size of the pool depends on many
|
|
factors, however and generally involves a tradeoff of speed vs.
|
|
capacity. The best approach allows for runtime flexibility with some
|
|
reasonable, established limits. If possible, we recommend that you
|
|
support a command-line parameter or a configuration file to control
|
|
the size of the overlay pool at startup (like the /X command-line
|
|
option for TURBO.EXE).
|
|
|
|
The following skeleton program presents a typical overlaid Turbo
|
|
Vision application:
|
|
|
|
program MyProg;
|
|
|
|
{$F+,O+,S-}
|
|
{$M 8192,65536,655360}
|
|
|
|
uses Overlay, Drivers, Memory, Objects, Views, Menus, Dialogs,
|
|
HistList, StdDlg, App;
|
|
|
|
{$O App }
|
|
{$O Dialogs }
|
|
{$O HistList }
|
|
{$O Memory }
|
|
{$O Menus }
|
|
{$O Objects }
|
|
{$O StdDlg }
|
|
{$O Views }
|
|
|
|
const
|
|
ExeFileName = 'MYPROG.EXE'; { EXE name for DOS 2.x }
|
|
OvrBufDisk = 96 * 1024; { Overlay pool size without EMS }
|
|
OvrBufEMS = 72 * 1024; { Overlay pool size with EMS }
|
|
|
|
type
|
|
TMyApp = object(TApplication)
|
|
constructor Init;
|
|
destructor Done; virtual;
|
|
.
|
|
.
|
|
end;
|
|
|
|
procedure InitOverlays;
|
|
var
|
|
FileName: string[79];
|
|
begin
|
|
FileName := ParamStr(0);
|
|
if FileName = '' then FileName := ExeFileName;
|
|
OvrInit(FileName);
|
|
if OvrResult <> 0 then
|
|
begin
|
|
PrintStr('Fatal error: Cannot open overlay file.');
|
|
Halt(1);
|
|
end;
|
|
OvrInitEMS;
|
|
if OvrResult = 0 then OvrSetBuf(OvrBufEMS) else
|
|
begin
|
|
OvrSetBuf(OvrBufDisk);
|
|
OvrSetRetry(OvrBufDisk div 2);
|
|
end;
|
|
end;
|
|
|
|
constructor TMyApp.Init;
|
|
begin
|
|
InitOverlays;
|
|
TApplication.Init;
|
|
.
|
|
.
|
|
end;
|
|
|
|
destructor TMyApp.Done;
|
|
begin
|
|
.
|
|
.
|
|
TApplication.Done;
|
|
end;
|
|
|
|
var
|
|
MyApp: TMyApp;
|
|
|
|
begin
|
|
MyApp.Init;
|
|
MyApp.Run;
|
|
MyApp.Done;
|
|
end.
|
|
|
|
Notice how the overlay manager is initialized before calling the
|
|
inherited TApplication.Init--this is a requirement since the App
|
|
unit, which contains TApplication, is overlaid. Also notice the use
|
|
of ParamStr(0) to get the name of the .EXE file; that only works
|
|
with DOS version 3.0 or later. In order to support earlier DOS
|
|
versions, a test for a null string combined with the ability to
|
|
supply an .EXE file name is required. Finally, notice that
|
|
OvrSetRetry isn't called if EMS is present, since it generally only
|
|
improves performance when the overlay file is on disk.
|
|
|
|
The above example assumes that you've used the recommended practice
|
|
of appending the overlay file to the end of .EXE file. This is
|
|
easily done using the DOS COPY command:
|
|
|
|
REN MYPROG.EXE TEMP.EXE
|
|
COPY/B TEMP.EXE+MYPROG.OVR MYPROG.EXE
|
|
|
|
See TVRDEMO.PAS in the \TP\TVDEMOS directory for an example of an
|
|
overlaid Turbo Vision application. And always remember to place a
|
|
{$F+,O+} directive at the beginning of all overlaid units.
|
|
|
|
For further information on Turbo Pascal's overlay manager, please
|
|
refer to Chapter 13 in the Programmer's Guide.
|
|
|
|
|
|
2. Ordering of inherited calls
|
|
------------------------------
|
|
|
|
Turbo Vision is designed so that you can extend it to suit your
|
|
application's specific needs by deriving new descendants from
|
|
existing Turbo Vision objects. Sometimes, your new object will want
|
|
to completely replace the inherited behavior for a given method. For
|
|
example, when TInputLine is derived from TView, TInputLine.Draw does
|
|
not call its inherited method, TView.Draw. That's because TView.Draw
|
|
simply creates an empty rectangle. Instead, TInputLine overrides the
|
|
inherited Draw and defines a new one:
|
|
|
|
procedure TInputLine.Draw;
|
|
...
|
|
begin
|
|
{ Insert code to draw an input line here }
|
|
end;
|
|
|
|
In fact, calling TView.Draw would cause an unpleasant flicker on the
|
|
screen when first TView cleared the rectangle, and then TInputLine
|
|
filled it in. Methods like Draw are an exception, though.
|
|
Programming effectively with Turbo Vision involves making lots of
|
|
inherited method calls. For each method you're overriding, you must
|
|
know which to do first: Execute the code that you're adding? Or
|
|
first call the inherited method and then execute your new code?
|
|
Moreover, as you've just seen with the Draw method, sometimes you
|
|
don't call your inherited method at all. Doing the right thing in
|
|
the right order, of course, depends on where your new object falls
|
|
in the Turbo Vision hierarchy and which method you're overriding.
|
|
The rules for inherited call ordering break into 3 categories
|
|
|
|
1) Constructors. Call the inherited method first.
|
|
|
|
procedure MyObject.Init(...);
|
|
begin
|
|
{ Call inherited Init first }
|
|
{ Insert code to init MyObject }
|
|
end;
|
|
|
|
2) Destructors. Call the inherited method last.
|
|
|
|
procedure MyObject.Done;
|
|
begin
|
|
{ Insert code to cleanup MyObject }
|
|
{ Call inherited Done last }
|
|
end;
|
|
|
|
3) All other methods: It depends. See below for an explanation.
|
|
|
|
Overriding Init and Load: The Call First Rule
|
|
---------------------------------------------
|
|
You should always call your inherited constructor first and then
|
|
initialize any new fields your descendent object defines. This
|
|
advice applies to Init and Load constructors equally
|
|
|
|
type
|
|
MyObject = object(TWindow)
|
|
Value: Word;
|
|
Ok: Boolean;
|
|
constructor Init(var Bounds: TRect; ATitle: TTitleStr;
|
|
AValue: Word; AOk: Boolean);
|
|
end;
|
|
|
|
constructor MyObject.Init(var Bounds: TRect; ATitle: TTitleStr;
|
|
AValue: Word; AOk: Boolean);
|
|
begin
|
|
TWindow.Init(Bounds, ATitle, wnNoNumber);
|
|
Value := 16;
|
|
Ok := True;
|
|
end;
|
|
|
|
Here, MyObject calls its inherited Init method, TWindow.Init, to
|
|
perform initialization, first. Then MyObject puts meaningful values
|
|
into Value and Ok. If you were to reverse the order of these steps,
|
|
you'd be in for an unpleasant surprise: Value would be zero and Ok
|
|
would be False! That's because TWindow follows the Init convention
|
|
and calls its inherited method, TGroup.Init. TGroup.Init calls
|
|
TView.Init; which--finally--calls TObject.Init, the ultimate
|
|
ancestor to all Turbo Vision objects. TObject.Init zeros ALL the
|
|
fields in MyObject, including Value and Ok.
|
|
|
|
Your Init and Load methods can rely on this and refrain from zeroing
|
|
new fields--as long as you're deriving an object from some TView
|
|
descendant.
|
|
|
|
The Exception
|
|
-------------
|
|
Having said "always call the inherited constructor first", it's not
|
|
always true. When working with non-view objects like TCollection or
|
|
TStream descendants, you don't HAVE to call your inherited Init or
|
|
Load first. But you should, unless there is some compelling reason
|
|
to break the rule. And there might be, as in the following case when
|
|
an inherited constructor includes a call to a virtual method which
|
|
has been overridden. TCollection.Load relies on the virtual method
|
|
GetItem to get a collection item from the stream
|
|
|
|
constructor TCollection.Load(var S: TStream);
|
|
begin
|
|
...
|
|
for I := 0 to Count - 1 do AtPut(I, GetItem(S));
|
|
end;
|
|
|
|
Since GetItem is virtual, you may have overridden it and your
|
|
GetItem may rely on your descendent object's Load method to
|
|
initialize a field before GetItem is called. In this case, you'd
|
|
want your new Load method to read the field value first, then call
|
|
TCollection.Load, which would end up "calling back" to your GetItem.
|
|
Here's a partial implementation of a collection of binary data (not
|
|
objects). The size of a data item is fixed for the entire collection
|
|
and held in the new field, ItemSize
|
|
|
|
type
|
|
PDataCollection = ^TDataCollection;
|
|
TDataCollection = object(TStringCollection)
|
|
ItemSize: Word;
|
|
KeyType: KeyTypes;
|
|
constructor Init(ALimit, ADelta, AnItemSize: Integer);
|
|
constructor Load(var S: TStream);
|
|
function Compare(Key1, Key2: Pointer): Integer; virtual;
|
|
procedure FreeItem(Item: Pointer); virtual;
|
|
function GetItem(var S: TStream): Pointer; virtual;
|
|
procedure PutItem(var S: TStream; Item: Pointer); virtual;
|
|
procedure Store(var S: TStream); virtual;
|
|
end;
|
|
|
|
...
|
|
|
|
constructor TDataCollection.Load(var S: TStream);
|
|
begin
|
|
S.Read(ItemSize, SizeOf(ItemSize));
|
|
TStringCollection.Load(S);
|
|
end;
|
|
|
|
function TDataCollection.GetItem(var S: TStream): Pointer;
|
|
var Item: Pointer;
|
|
begin
|
|
GetMem(Item, ItemSize);
|
|
S.Read(Item^, ItemSize);
|
|
GetItem := Item;
|
|
end;
|
|
|
|
...
|
|
|
|
Load first reads the ItemSize off the stream, then it calls
|
|
TSTringCollection.Load, which "calls back" to GetItem. Now GetItem
|
|
knows how big the item it's supposed to load is and can allocate
|
|
heap and read data correctly. That's why the "call inherited first"
|
|
applies to TView descendants all the time and to all other objects
|
|
unless there's a compelling reason. And of course, Load and Store go
|
|
hand-in-hand, so in this example, Store would write data to the
|
|
stream in the same order as Load reads it. This code is extracted
|
|
from the DATACOLL.PAS unit in the \TP\TVDEMOS directory.
|
|
|
|
Destructors: call them last
|
|
---------------------------
|
|
A destructor's job is to undo the constructor's handiwork in reverse
|
|
order. Therefore, a destructor should always free its own dynamic
|
|
memory and then call its inherited destructor to do the same.
|
|
|
|
|
|
All other methods: it depends
|
|
-----------------------------
|
|
You saw how TInputLine doesn't call its inherited Draw method. If it
|
|
did, TView.Draw would have to be called first or else it would
|
|
obliterate any writing done by TInputLine.Draw. For the remaining
|
|
Turbo Vision methods, whether to make an inherited call or not--and
|
|
in what order--depends on which method you're overriding. In
|
|
general, call the inherited method first. We've covered the most
|
|
common methods to override: Init, Done, Draw, Load, and Store. Now
|
|
consider HandleEvent. Here's a skeleton of a descendent object's
|
|
HandleEvent method
|
|
|
|
procedure MyObject.HandleEvent(var Event: TEvent);
|
|
begin
|
|
{ Insert code to change inherited behavior }
|
|
{ Call inherited HandleEvent }
|
|
{ Insert code to add additional behavior }
|
|
end;
|
|
|
|
First, code that will CHANGE the inherited behavior is executed.
|
|
Then the inherited call is made. Finally, the code that will EXTEND
|
|
the inherited behavior is added.
|
|
|
|
If you want to change the way the inherited method behaves or filter
|
|
out events, then put this code ahead of the inherited call. Most
|
|
Turbo Vision views call their inherited HandleEvent and then add
|
|
code to handle new events
|
|
|
|
procedure TDialog.HandleEvent(var Event: TEvent);
|
|
begin
|
|
TWindow.HandleEvent(Event);
|
|
case Event.What of
|
|
evKeyDown:
|
|
...
|
|
evCommand:
|
|
...
|
|
end;
|
|
end;
|
|
|
|
TDialog's HandleEvent manages all keyboard and mouse events,
|
|
including tabs. But what if you need to define a new dialog that
|
|
ignores tabs? Since you want to change your inherited method's
|
|
behavior (the handling of tabs) you'll put this tab-eating code
|
|
BEFORE the call to TDialog.HandleEvent
|
|
|
|
procedure TNoTabsDialog.HandleEvent(var Event: TEvent);
|
|
begin
|
|
if (Event.What = evKeyDown) then
|
|
if (Event.KeyCode = kbTab) or (Event.KeyCode = kbShiftTab) then
|
|
ClearEvent(Event);
|
|
TDialog.HandleEvent(Event);
|
|
end;
|
|
|
|
That's it. Your TNoTabsDialog will throw away the tabs before
|
|
TDialog.HandleEvent can ever see them and the tab key will not move
|
|
from control to control when using your dialog.
|