home *** CD-ROM | disk | FTP | other *** search
-
- ======================================================================
- 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.
-