The Quickpascal in Quickpascal

Microsoft used QuickPascal itself to write the QuickPascal user interface. Joseph and Mike show you how they did it, and how you can take advantage of some of the same techniques.


December 01, 1989
URL:http://www.drdobbs.com/windows/the-quickpascal-in-quickpascal/184408249

Figure 1


Copyright © 1989, Dr. Dobb's Journal

Figure 2


Copyright © 1989, Dr. Dobb's Journal

DEC89: THE QUICKPASCAL IN QUICKPASCAL

THE QUICKPASCAL IN QUICKPASCAL

A look at how the windows in QP's User Interface use their own object-oriented technology

Joseph Mouhanna and Michael Vose

Michael Vose authors the monthly newsletter OS REPORT: News and Views on OS/2. He can be reached at P.O. Box 3160, Peterborough, NH 03458. Joseph Mouhanna is the program manager for Pascal products and has been with Microsoft since 1984. Prior to transferring to the language group in 1989, he spent 18 months developing the Microsoft right-to-left technology. He can be reached at Microsoft Corp., 1 Microsoft Way, Redmond, WA 98052-6399.


Writing the user interface for a compiler imposes a different set of problems than creating a code generator. Presenting the applications programmer with an orderly set of windows and menus streamlines his interaction with the compiler. When designing this interface, the compiler's authors need to consider visual elements, and not machine internals.

A windowing user interface uses a limited number of variations on a few visual elements. These elements include windows, dialog boxes, and menus. When these components are analyzed, one realizes that dialog boxes and menus are specialized windows.

Therefore, a windowing interface lends itself to the hierarchical structure of object-oriented programming (OOP). The match between graphical user interfaces and OOP emerges because the techniques of OOP allow the creation of new windows as children of existing windows. Doing this requires little additional code. With a few generic window classes, specialized subclasses implementing variations on the overall window theme can be created.

Microsoft used QuickPascal itself to write the QuickPascal user interface. They did so to obtain the many advantages of OOP. In addition to code reusability, OOP techniques cut down on debugging and testing time. By mixing object-oriented and standard programming, Microsoft produced, in a short span of time, a user interface that will be easy to update for future releases of QuickPascal.

Design Considerations

Good OOP design uses a top-down approach to creating programs. Objects are derived from general classes, and then inheritance is used to create ever more specialized child-class objects for special purposes. OOP's use of inheritance makes code reusable and, as a result, substantially reduces the size of programs. OOP techniques reduce debugging time by encouraging the use of hundreds of small, simple methods, rather than a few large, complex procedures or functions as in standard structured programming.

One or more parent classes for the application reside at the top of the OOP design hierarchy. The QuickPascal user interface uses a parent window class and a parent event-handling class. These parent classes contain the information and methods basic to all windows, as well as information about the event-handling functions of the user interface. The inheritance of these general qualities and actions by child classes allows the building of all the objects needed to implement a consistent user interface.

Child classes inherit the properties of their parents without the writing of a lot of additional code. Using classes and objects also streamlines event handling. Window environments written in classical languages usually use very large CASE statements to handle all possible events for each window type. Microsoft Windows applications, for example, frequently sport CASE statements with a dozen or more elements. An object-oriented event handler lets the different window types inherit all the general event handling characteristics of their parents, and only adds the new code needed to handle special events. This inheritance usually makes code much more compact and efficient than the more conventional programming approach.

However, this is not always true. A standard OOP event handler might send a "redraw yourself" message to a window previously covered by a dialog box or pull-down menu. QuickPascal does not always follow this standard approach for one simple reason -- speed. Instead, QuickPascal's event handler sometimes stores in a buffer a copy of the image that lies underneath the dialog box. When the dialog box disappears, the event handler transfers the image stored in the buffer back onto the screen.

QuickPascal uses this buffer-image technique to make its user interface more responsive. Redrawing an image from a buffer updates only the portion of the screen that has changed. Telling a window object to redraw itself means redrawing the entire image, which is much more time consuming. The buffer-image technique is an "optimization" of OOP practice.

This optimization addresses the few problems that result from using OOP techniques. If creating a class and an associated method to perform a task requires more bytes or machine time than a standard procedure, sticking with a more traditional procedure may improve a program's performance.

For this reason, QuickPascal's user interface does not use objects and their associated methods to the exclusion of standard procedures. Where standard procedures complete a task with less programming overhead than a method, QuickPascal's interface uses conventional procedures.

The Parent Classes

QuickPascal's user interface code uses two elementary classes. The subclasses used to spawn the objects that comprise the user interface are derived from these classes. One parent class receives information from the keyboard and mouse (input), and the other displays information on the screen (output). These two parent classes comprise the top of the class hierarchy.

The primary elements of these classes include event handling for input, and a variety of windows, dialog boxes, and menus for screen output. These basic parts interact as indicated in Figure 1.

The two parent classes in the QuickPascal environment are called TWindow and TApplication. These names are familiar to anyone who has programmed in Object Pascal or with the Macintosh's MacApp class library. The T in these class names means, "type," and is a naming convention established by Apple Computer. The TWindow class and its descendants handle all screen drawing, including windows, menus, and dialog boxes. The TApplication class and its children interpret keyboard and mouse events and send them to the appropriate window object. All of the code classes in the QuickPascal user interface devolve from these parent classes, as shown in Figure 2.

The TWindow class (see Listing One, page 126) contains variables and methods basic to all window functions. These events include opening, drawing, moving, mounting, unmounting, printing, coloring, scrolling, responding to mouse events, and closing windows. Mounting refers to bringing a window to the front of the screen and making it the active window.

TWindow serves only as a parent to other classes. QuickPascal's user interface code declares no objects directly from TWindow. TWindow's child classes -- TDialogBox, TDocument, TMenuBar, and TSubMenu--create their own distinct types of windows, and all of the QuickPascal interface window objects are derived from these child classes.

The TWindow definition contains mouse handling methods that are comprised simply of BEGIN and END statements. The child classes of TWindow inherit these blank methods and override them with their own mouse handling method definitions. This polymorphism allows a TApplication object to send the same message to any TWindow object (described shortly.) The type of the TWindow-derived object -- dialog box, document, or menu -- doesn't matter. Each of these objects specifies how to deal with these mouse-event messages.

The TWindow Child Classes

Most of the dialog box objects used by the QuickPascal user interface come from the TWindow child class TDialog Box. This child class inherits all the qualities of TWindow, and adds new methods for drawing buttons, edit fields, and list boxes (see Listing Two, page 126). It stores the window's current state (open, closed, waiting for input, and so on) in its instance variables.

Because the QuickPascal interface uses some specialized dialog boxes, several other child classes exist to deal with those selected cases. One of these child classes is TFileDialogBox.

While inheriting all the method functionality it needs from TDialogBox, TFileDialogBoxadds two more instance variables. These variables store the default and current document extension and the path to the current directory. These new variables make the file dialog box especially convenient to use. Without them, a user would have to reenter the path name every time a file dialog box opened, and the default document extension would always be the same regardless of the most recently used extension.

TPrintDialogBox also adds instance variables to store pointers to the correct document and to the text block location. They tell QuickPascal what window to print from and what text to print in the active window.

Help dialog boxes use TDialogBox's methods, but need instance variables to describe the help topic called and to indicate the help page, the first line of the page, and the number of lines on a page.

The BP in TBPDialogBox means "Break Point." This object is used by QuickPascal to let the user set break points in the code they are editing. The break point dialog box adds three instance variables to the ones it inherits from TDialogBox. The additional variables store a break point location, the condition on which to halt execution, and the type of break point (conditional and unconditional).

Dialog boxes that need custom keyboard and mouse actions use the TModifyDialogBox class. Each of the methods in this class overrides the methods of its parent.

TDocument and its children produce the screen items traditionally considered windows: The program editing windows, the QuickPascal Advisor help window, and the debug window. TDocument and its children control all editing windows. The TDocument class clearly shows the advantages of OOP. It greatly simplifies the moderately complex task of creating a custom text editor. TDocument builds on TWindow's basic actions by adding the methods necessary for editing text. TDocument's child classes then modify the editor features they inherit to suit their own needs.

The TDocument class treats documents (program files, help files, and others) as a series of lines made up of separate characters. TDocument uses instance variables to track the relevant data about a document. The source code for TDocument demonstrates how easy it can be to design a custom file format. All that's required is the declaration of a new child of TDocument with an altered data structure. QuickPascal will read files that use this new structure as long as the GetPhysLine method still returns a single line of text.

THelp Window is identical to TDocument except that it overrides the GetPhysLine method. THelpWindow's version of GetPhysLine retrieves data from QuickPascal's help (.HLP) files.

THelpWindow also makes minor changes to the GetChar and LeftMousePress methods to keep any user activities from altering the contents of the help screen. All other window-related activities function normally.

The TEditWindow class describes QuickPascal's standard editing window, in which users write their programs. The methods of this class override TDocument's GetChar, LeftMousePress, and DrawScreen methods. This class includes three new methods, which take care of opening files, closing files, and saving data to a file.

A menu bar is a specialized window. TMenuBar inherits all of TWindow's methods without modification, with one exception: TMenuBar alters the DrawWindow method to handle special Menu cases, such as graying out of menu items that cannot be accessed, colors, menu text, and so on. TMenuBar also uses a pointer to any TMainAppli objects, the principal object performing menu management.

A submenu is the pull-down portion of a menu. It is the part that appears when a user clicks on an item in the menu bar. Like TMenuBar, TSubMenu overrides the DrawWindow method it inherits from TWindow. It also uses a pointer to an object of type TMainAppli to obtain other menu management functions.

The TApplication Chidren

The TApplication class is a good example of a parent class serving as the basis for other classes. QuickPascal's user interface declares objects from the child classes of TApplication, but does not declare any objects directly from TApplication itself.

When a window object of the TWindow subclass TDialogBox (or one of its descendants) opens, an object of class TDialogBoxAppli also opens to handle events within a dialog box. TDialogBoxAppli overrides its inherited GetEvents, HandleEvents, and DrawStatusLinemethods in order to handle events special to dialog boxes. TDialogBoxAppli also adds an instance variable that points to the type of dialog box created.

Dialog box objects from both window and application classes exist only as long as the dialog box remains open. When the dialog box closes, QuickPascal disposes of both objects.

Like TApplication, TMainAppli illustrates one of the principles of good OOP design. TMainAppli serves primarily as a foundation for its child, EditAppli, but remains general enough to allow for easy future expansion. If Microsoft decides to add another type of window to the QuickPascal user interface, TMainAppli will make it easy to add a new event handling class for that window.

EditAppli handles the bulk of the events in the QuickPascal environment. It keeps track of the number of open windows, plus the handle of the active window, and watches for menu activity. EditAppli's objects work with the window objects for TDocument, TMenuBar, and TSubMenu. EditAppli inherits almost all its methods and instance variables from TMainAppli.

Interacting Objects

TWindow and its child classes work in tandem with the TApplication classes. Every TWindow-derived object has a companion TApplication-derived object that handles the events for that window. A TApplication object can be associated with more than one TWindow object. TApplication and its children have instance variables that store mouse events, and timer events, and the current state of the keyboard. The children's methods acquire and dispatch these various events.

TApplication's child classes manage special event handling features (such as TDialogBoxAppli not allowing mouse clicks outside of a dialog box). TApplication, however, has methods to process all the normal user activities that occur in a window and passes those methods on to its child classes through inheritance.

The TApplication classes and TWindow classes work closely together. When QuickPascal opens and initializes a new TApplication-derived object and its corresponding TWindow-derived object(s), the methods set up a loop between the objects. The TApplication object's GetEvents method waits until either a keyboard or mouse event occurs. When something happens, GetEvents sets its instance variables and then ends. The TApplication object then transfers control to its associated TWindow object, activating its HandleEvents method. The TWindow-derived object's methods (My Window.LmousePress, for example) checks the state of the window's instance variables and responds to the event that just occurred. This usually results in a call back to one of the TWindow-derived methods, but can also mean transferring control to the compiler or to some other activity.

How It All Works

A look at a fragment of the QuickPascal interface code provides a glimpse into this object-oriented universe. Listing Three (page 127) shows a class definition, an instance of an object, and the use of that object within a Pascal procedure. The first part of Listing Three illustrates how child classes become defined, inheriting behavior from their parent classes and overriding the methods of those arents where necessary.

The two instances of EditWindow in the definition of class EditWindow set up a linked list of edit windows that can be open at the same time. EditWindow-class objects use (among others) the OpenFile method, which specifies what happens when an object of this class needs to open a file. OpenFile names the object of type EditWindow for which it can open a file (in this case, Edit).

As the code fragment in Listing Three shows, a class is first defined by describing its methods and naming its instance variables. Next, an object of that class is created. This process is similar to declaring a variable. Then, some memory for the named object is allocated using the New keyword and the name of the object. Finally, a message is sent to the object by calling one of its methods from within the main body of a program.

Using this technique, code modules that are easy to debug and modify can be built. The internal operation of an object can later be changed without worrying about "breaking" the rest of your program in its interaction with the changed object. Also, new versions of existing objects can be created by defining a child class with new or overriding methods. The building blocks that classes and objects provide make creating programs a lot less risky than it used to be.

_THE QUICKPASCAL IN QUICKPASCAL_ by Joseph Mouhanna and Michael Vose

[LISTING ONE]



{Top Window Object}
TWindow         =   OBJECT
   Client  :   Rect;
   Coord   :   Rect;
   TCoord   :   Rect;
   Handle  :   word;
   Shadow  :   boolean;
   Col     :   tWindowColors;
   Captured:   boolean;
   Class   :   WindowClasses;
   Bitmap   :   pointer;

   TimeOutState: byte;
   ForceTimeOut: boolean;
   CursMode:   CursorModes;
   CursX   :   word;
   CursY   :   word;

procedure TWindow.OpenWindow (Class: WindowClasses; x,y,w,h: word;
      Col: tWindowColors; Shadow: boolean);
procedure TWindow.DrawWindow (Clip: lRect);
procedure TWindow.CloseWindow;
procedure TWindow.MoveWindow (x,y: word);
procedure TWindow.ChangeWindow (x,y,w,h: word);
procedure TWindow.MountWindow;
procedure TWindow.UnMountWindow;
procedure TWindow.GetClientArea (var Client: Rect);
procedure TWindow.WrtChars (var C; Len,x,y: word);
procedure TWindow.WrtCharsAttr (var C; Attr,Len,x,y: word);
procedure TWindow.WrtExtendedCharsAttr (var C; Attr1,Attr2,Len,Width,x,y: word);
procedure TWindow.WrtNAttr (Attr,Len,x,y: word);
procedure TWindow.WrtCells (var C; Len,x,y: word);
procedure TWindow.WrtFrame (F: FramePartsSet; Attr,x,y,w,h: word);
procedure TWindow.WrtSeparator (RightCorner: boolean; Attr,y: word);
procedure TWindow.ScrollUp (Lines, Attr,x,y,w,h: word);
procedure TWindow.ScrollDown (Lines, Attr,x,y,w,h: word);
procedure TWindow.ScrollLeft (Rows, Attr,x,y,w,h: word);
procedure TWindow.ScrollRight (Rows, Attr,x,y,w,h: word);
procedure TWindow.CaptureMouse;
procedure TWindow.UnCaptureMouse;
procedure TWindow.PressLMouse (DbleClicked: boolean; x,y: integer);
procedure TWindow.ReleaseLMouse (x,y: integer);
procedure TWindow.PressRMouse (DbleClicked: boolean; x,y: integer);
procedure TWindow.ReleaseRMouse (x,y: integer);
procedure TWindow.MoveMouse (x,y: integer);
procedure TWindow.PosCursor (x,y: word);
procedure TWindow.SetCursorMode (Mode: CursorModes);
procedure TWindow.GetChar (Key: word);
procedure TWindow.TimeOut;
procedure TWindow.RefreshStatusLine;
end;







[LISTING TWO]


TDialogBox =  object (TWindow)
              First       :   pDialogBoxItem;
              Current     :   pDialogBoxItem;
              Command     :   function (Dialog: TDialogBox; ID:word): boolean;
              ID     :   word;
              HelpNO      :   word;
              UndoID      :   word;
              ValidID     :   word;
              Appli       :   TDialogBoxAppli;
              MouseAction :   MouseActions;
              TitleColor  :   boolean;
{$IFDEF DEBUG}
             HeapPtr      :   pointer;
{$ENDIF}

procedure TDialogBox.OpenDialogBox (CheckPrevW  : boolean;
w,h         : word;
Command     : pointer;
HelpNO      : word;
UndoID      : word;
ValidID     : word);

procedure TDialogBox.Call (var ID: word);
procedure TDialogbox.GetChar (Key: word); override;
procedure TDialogBox.CloseWindow; override;
procedure TDialogBox.DrawWindow (Clip: lRect); override;
procedure TDialogBox.PressLMouse (DbleClicked: boolean; x,y: integer); override;
procedure TDialogBox.PressRMouse (DbleClicked: boolean; x,y: integer); override;
procedure TDialogBox.ReleaseLMouse (x,y: integer); override;
procedure TDialogBox.MoveMouse (x,y: integer); override;
procedure TDialogBox.SelectDialogBoxItem (ID: word);
procedure TDialogBox.TimeOut; override;
procedure TDialogBox.DelDialogBoxItem (ID: word);
procedure TDialogBox.AddComment (
          CommID      : word;
          AltID       : word;
          x,y,w       : word;
          Text        : String;
          justif      : Justifications);
procedure TDialogBox.AddFrame (
          FrameID     : word;
          AltID       : word;
          x,y,w,h     : word;
          Text        : String);
procedure TDialogBox.AddSysButton (
          TabID       : word;
          SysID       : SysButtons;
          justif      : Justifications);
procedure TDialogBox.AddDlgText (
          TabID       : word;
          DlgID       : word;
          Size        : word;
          x,y,w       : word;
          Text        : String);
procedure TDialogBox.AddPushButton (
          TabID       : word;
          PushID      : word;
          x,y         : word;
          State       : boolean);
procedure TDialogBox.AddRadioButton (
          TabID       : word;
          GroupID     : word;
          RadioID     : word;
          LinkID      : word;
          x,y         : word;
          State       : boolean;
          Trace       : pointer);
procedure TDialogBox.AddListBox (
          TabID       : word;
          ListID      : word;
          Columns     : word;
          x,y,w,h     : word;
          LinkID      : word;
          Trace       : pointer);
procedure TDialogBox.DrawComment (
          p           : pDialogBoxItem);
procedure TDialogBox.DrawFrame (
          F           : FramePartsSet;
          p           : pDialogBoxItem);
procedure TDialogBox.DrawSysButton (
     Hilite      : boolean;
          p           : pDialogBoxItem);
procedure TDialogBox.DrawDlgText (
          p           : pDialogBoxItem);
procedure TDialogBox.DrawPushButton (
          p           : pDialogBoxItem);
procedure TDialogBox.DrawRadioButton (
          p           : pDialogBoxItem);
procedure TDialogBox.DrawListBox (
          p           : pDialogBoxItem);
procedure TDialogBox.SetComment (
          CommID      : word;
          Text        : String);
procedure TDialogBox.GetComment (
          CommID      : word;
      var Text        : String);
procedure TDialogBox.GetDlgText (
          DlgID       : word;
      var Text        : string);
procedure TDialogBox.SetDlgText (
          DlgID       : word;
          Text        : String);
procedure TDialogBox.SetLockState (
          DlgID       : word;
          Lock        : Boolean);
procedure TDialogBox.GetPushButton (
          PushID      : word;
      var State       : boolean);
procedure TDialogBox.SetPushButton (
          PushID      : word;
            State       : boolean);
procedure TDialogBox.GetRadioButton (
          GroupID     : word;
      var ID          : word);
procedure TDialogBox.SetRadioButton (
          GroupID     : word;
          ID          : word);
procedure TDialogBox.GetTextListBox (
          ListID      : word;
      var Text        : string);
procedure TDialogBox.GetOffsetListBox (
          ListID      : word;
      var Offset      : word);
procedure TDialogBox.SetTextListBox (
          ListID      : word;
          offset      : word;
          Text        : string);
procedure TDialogBox.SetOffsetListBox (
          ListID      : longint;
          offset      : word);
procedure TDialogBox.ClearListBox (
          ListID      : dword);
function TDialogBox.AddListBoxItem (
          ListID      : dword;
          Text        : string): boolean;
end;





[LISTING THREE]


EditWindow      =   object (TDocument)
                  ewNext      :   EditWindow;
                  ewPrev      :   EditWindow;
                  ewWDW       :   word;
                  ewDOC       :   word;
                  ewType      :   DocTypes;
                  ewUndo      :     record
                  X           :   byte;
                   Y           :   word;
                    Len         :     word;
                   Buf         :     MemHandle
                  end;

                ewFirstMark ,
                ewCursMark   :   record
                  XY: word;
                  dX: word
                 end;

procedure EditWindow.OpenFile (
var Status   :   QPErrors;
Col      :   tWindowColors;
DOC      :   word;
WDW      :   word;
Locked           :   boolean;
   dtType   :   DocTypes;
   IsNew   :   boolean;
   Time   :   LongInt;
   Coord   :   lRect;
   Title   :   PathStr;
   TextHandle:     MemHandle;
   TextSize:   word);

procedure EditWindow.DuplicateWindow;
procedure EditWindow.SaveFile (var Status: QPErrors; FileName: PathStr;
                                                           DefaultExt: ExtStr);
procedure EditWindow.CloseWindow; override;
procedure EditWindow.CloseDocument;
function EditWindow.NbLines: word; override;
function EditWindow.NbColumns: word; override;
function EditWindow.GetLine (Column,Line,Len: word; var Buffer): word; override;
function EditWindow.GetLineLength (Line: word): word; override;
procedure EditWindow.GetPhysLine (Column,Line,Len: word; var Cells); override;
procedure EditWindow.GetChar (Key: word); override;
function EditWindow.CopyBlock2ClipBoard: boolean; override;
procedure EditWindow.MountWindow; override;
procedure EditWindow.UnMountWindow; override;
procedure EditWindow.ModifyLine (Line: word;  PosX,PosY:  word);
procedure EditWindow.ModifyEOF (Line: word;  PosX,PosY:  word);
end;

{=====================
An instance of an object of type EditWindow; here, the object Edit is passed
to the procedure InternalReplaceChar.

procedure InternalReplaceChar (
                            Edit        :   EditWindow; { <== object declared}
                            AtX,AtY    :   word;
                            Ch          :   Char);
var
   Pt      : pFileText;
   Pl      : pFileLines;
   Pi      : pFileLinesInfo;
begin {InternalReplaceChar}
    with Documents[Edit.ewDOC] do begin
       __SaveLineForUndo (Edit, AtX,AtY);
       FarLockMem2 (dLines,Pl, dText,Pt);
       if __CheckAndExpandIfInTab (
         EditorError,
         Edit.ewDOC,
          Pt, Pl,
          AtX, AtX, AtY) then begin
      Pt^[Pl^[AtY]+AtX] := Ch;
          OneSourceModified := true;
          dUsed := true;
          dTime := GetDateTime;
          FarUnLockMem2 (dLines,dText);
          if dMarkModif then begin
          {$IFDEF DEBUG}
      Assertion (dInfo <> NullMemHandle, __LINE__, SourceName);
          {$ENDIF}
             FarLockMem1 (dInfo, Pi);
             Pi^[AtY] := 0;
             FarUnLockMem1 (dInfo)
          end
       end
       else
           ExtendedErrorBox (false, EditorError, true, dFileName)
    end
end;  {InternalReplaceChar}
*)

{================
The Openfile [method?] [procedure declaration?].
================}

{-----------------------------------------------------------------------}
{ OpenFile}
{-----------------------------------------------------------------------}
procedure OpenFile (
   var Status         :   QPErrors;
   var Edit         :   EditWindow;
      Mode          :   OpenFileModes;
      dtType         :   DocTypes;
             FileName      :   PathStr;
             DefaultExt      :   ExtStr);
label Error;
var
    H      :   FileHandle;
    Size   :   dword;
    wdw    :   word;
    doc    :   word;
    Time   :   LongInt;
    Ht      :   MemHandle;
    Pt     :   pFileText;
    Dummy   :   QPErrors;
    Locked   :   boolean;
begin {OpenFile}
{Check if it's possible to open a new document}
    Edit := NIL; H := 0; Ht := NullMemHandle;
    if CheckFileNameNotAlreadyLoaded (FileName, DefaultExt) <> 0 then begin
        Status := ErrFileAlreadyLoaded;
        goto Error
    end;


{
===================
Using the OpenFile procedure
===================}

    New (Edit);    {Memory Allocated for the object Edit}
    Edit.OpenFile (Status,
                 wcEditWindows,
                Doc,
                wdw,
                Locked,
                dtType,
                Mode = ofUntitled,
                Time,
                NULL,
                FileName,
                Ht,
                Size);
    if Status <> ErrNone then begin
Error:
        if Ht <> NullMemHandle then FarFreeMem (Ht);
       if H <> 0 then DosCloseFile (Dummy, H);
       if Edit <> NIL then begin
          Dispose (Edit); Edit := NIL
       end;
       {ExtendedErrorBox (false, Status, true, FileName)}
    end
end;  {OpenFile}











Copyright © 1989, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.