_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann [LISTING ONE] PROGRAM HCalc; { By Jeff Duntemann; Update of 10/31/91 } { Requires Turbo Pascal 6.0! } USES App,Dialogs,Objects,Views,Menus,Drivers, FInput, { By Allen Bauer; on CompuServe BPROGA } Mortgage; { By Jeff Duntemann; from DDJ 10/91 } CONST cmNewMortgage = 199; cmExtraPrin = 198; cmCloseAll = 197; cmCloseBC = 196; cmPrintSummary = 195; WindowCount : Integer = 0; TYPE MortgageDialogData = RECORD PrincipalData : Real; InterestData : Real; PeriodsData : Integer; END; ExtraPrincipalDialogData = RECORD PaymentNumber : Integer; ExtraDollars : Real; END; THouseCalcApp = OBJECT(TApplication) InitDialog : PDialog; { Dialog for initializing a mortgage } ExtraDialog : PDialog; { Dialog for entering extra principal } CONSTRUCTOR Init; PROCEDURE InitMenuBar; VIRTUAL; PROCEDURE CloseAll; PROCEDURE HandleEvent(VAR Event : TEvent); VIRTUAL; PROCEDURE NewMortgage; END; PMortgageTopInterior = ^TMortgageTopInterior; TMortgageTopInterior = OBJECT(TView) Mortgage : PMortgage; CONSTRUCTOR Init(VAR Bounds : TRect); PROCEDURE Draw; VIRTUAL; END; PMortgageBottomInterior = ^TMortgageBottomInterior; TMortgageBottomInterior = OBJECT(TScroller) { Points to Mortgage object owned by TMortgageView } Mortgage : PMortgage; CONSTRUCTOR Init(VAR Bounds : TRect; AHScrollBar, AVScrollbar : PScrollBar); PROCEDURE Draw; VIRTUAL; END; PMortgageView = ^TMortgageView; TMortgageView = OBJECT(TWindow) Mortgage : TMortgage; CONSTRUCTOR Init(VAR Bounds : TRect; ATitle : TTitleStr; ANumber : Integer; InitMortgageData : MortgageDialogData); PROCEDURE HandleEvent(Var Event : TEvent); VIRTUAL; PROCEDURE ExtraPrincipal; PROCEDURE PrintSummary; DESTRUCTOR Done; VIRTUAL; END; CONST DefaultMortgageData : MortgageDialogData = (PrincipalData : 100000; InterestData : 10.0; PeriodsData : 360); VAR HouseCalc : THouseCalcApp; { This is the application object itself } {------------------------------} { METHODS: THouseCalcApp } {------------------------------} CONSTRUCTOR THouseCalcApp.Init; VAR R : TRect; aView : PView; BEGIN TApplication.Init; { Always call the parent's constructor first! } { Create the dialog for initializing a mortgage: } R.Assign(20,5,60,16); InitDialog := New(PDialog,Init(R,'Define Mortgage Parameters')); WITH InitDialog^ DO BEGIN { First item in the dialog box is input line for principal: } R.Assign(3,3,13,4); aView := New(PFInputLine,Init(R,8,DRealSet,DReal,0)); Insert(aView); R.Assign(2,2,12,3); Insert(New(PLabel,Init(R,'Principal',aView))); { Next is the input line for interest rate: } R.Assign(17,3,26,4); aView := New(PFInputLine,Init(R,6,DRealSet,DReal,3)); Insert(aView); R.Assign(16,2,25,3); Insert(New(PLabel,Init(R,'Interest',aView))); R.Assign(26,3,27,4); { Add a static text "%" sign } Insert(New(PStaticText,Init(R,'%'))); { Up next is the input line for number of periods: } R.Assign(31,3,36,4); aView := New(PFInputLine,Init(R,3,DUnsignedSet,DInteger,0)); Insert(aView); R.Assign(29,2,37,3); Insert(New(PLabel,Init(R,'Periods',aView))); { These are standard buttons for the OK and Cancel commands: } R.Assign(8,8,16,10); Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault))); R.Assign(22,8,32,10); Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal))); END; { Create the dialog for adding additional principal to a payment: } R.Assign(20,5,60,16); ExtraDialog := New(PDialog,Init(R,'Apply Extra Principal to Mortgage')); WITH ExtraDialog^ DO BEGIN { First item in the dialog is the payment number to which } { we're going to apply the extra principal: } R.Assign(9,3,18,4); aView := New(PFInputLine,Init(R,6,DUnsignedSet,DInteger,0)); Insert(aView); R.Assign(3,2,12,3); Insert(New(PLabel,Init(R,'Payment #',aView))); { Next item in the dialog box is input line for extra principal: } R.Assign(23,3,33,4); aView := New(PFInputLine,Init(R,8,DRealSet,DReal,2)); Insert(aView); R.Assign(20,2,35,3); Insert(New(PLabel,Init(R,'Extra Principal',aView))); { These are standard buttons for the OK and Cancel commands: } R.Assign(8,8,16,10); Insert(New(PButton,Init(R,'~O~K',cmOK,bfDefault))); R.Assign(22,8,32,10); Insert(New(PButton,Init(R,'Cancel',cmCancel,bfNormal))); END; END; { This method sends out a broadcast message to all views. Only the { mortgage windows know how to respond to it, so when cmCloseBC is { issued, only the mortgage windows react--by closing. } PROCEDURE THouseCalcApp.CloseAll; VAR Who : Pointer; BEGIN Who := Message(Desktop,evBroadcast,cmCloseBC,@Self); END; PROCEDURE THouseCalcApp.HandleEvent(VAR Event : TEvent); BEGIN TApplication.HandleEvent(Event); IF Event.What = evCommand THEN BEGIN CASE Event.Command OF cmNewMortgage : NewMortgage; cmCloseAll : CloseAll; ELSE Exit; END; { CASE } ClearEvent(Event); END; END; PROCEDURE THouseCalcApp.NewMortgage; VAR Code : Integer; R : TRect; Control : Word; ThisMortgage : PMortgageView; InitMortgageData : MortgageDialogData; BEGIN { First we need a dialog to get the intial mortgage values from } { the user. The dialog appears *before* the mortgage window! } WITH InitMortgageData DO BEGIN PrincipalData := 100000; InterestData := 10.0; PeriodsData := 360; END; InitDialog^.SetData(InitMortgageData); Control := Desktop^.ExecView(InitDialog); IF Control <> cmCancel THEN { Create a new mortgage object: } BEGIN R.Assign(5,5,45,20); Inc(WindowCount); { Get data from the initial mortgage dialog: } InitDialog^.GetData(InitMortgageData); { Call the constructor for the mortgage window: } ThisMortgage := New(PMortgageView,Init(R,'Mortgage',WindowCount, InitMortgageData)); { Insert the mortgage window into the desktop: } Desktop^.Insert(ThisMortgage); END; END; PROCEDURE THouseCalcApp.InitMenuBar; VAR R : TRect; BEGIN GetExtent(R); R.B.Y := R.A.Y + 1; { Define 1-line menu bar } MenuBar := New(PMenuBar,Init(R,NewMenu( NewSubMenu('~M~ortgage',hcNoContext,NewMenu( NewItem('~N~ew','F6',kbF6,cmNewMortgage,hcNoContext, NewItem('~E~xtra Principal ','',0,cmExtraPrin,hcNoContext, NewItem('~C~lose all','F7',kbF7,cmCloseAll,hcNoContext, NewItem('E~x~it','Alt-X',kbAltX,cmQuit,hcNoContext, NIL))))), NIL) ))); END; {---------------------------------} { METHODS: TMortgageTopInterior } {---------------------------------} CONSTRUCTOR TMortgageTopInterior.Init(VAR Bounds : TRect); BEGIN TView.Init(Bounds); { Call ancestor's constructor } GrowMode := gfGrowHiX; { Permits pane to grow in X but not Y } END; PROCEDURE TMortgageTopInterior.Draw; VAR YRun : Integer; Color : Byte; B : TDrawBuffer; STemp : String[20]; BEGIN Color := GetColor(1); MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces } MoveStr(B,' Principal Interest Periods',Color); WriteLine(0,0,Size.X,1,B); MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces } { Here we convert payment data to strings for display: } Str(Mortgage^.Principal:7:2,STemp); MoveStr(B[2],STemp,Color); { At beginning of buffer B } Str(Mortgage^.Interest*100:7:2,STemp); MoveStr(B[14],STemp,Color); { At position 14 of buffer B } Str(Mortgage^.Periods:4,STemp); MoveStr(B[27],STemp,Color); { At position 27 of buffer B } WriteLine(0,1,Size.X,1,B); MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces } MoveStr(B, ' Extra Principal Interest', Color); WriteLine(0,2,Size.X,1,B); MoveChar(B,' ',Color,Size.X); { Clear the buffer to spaces } MoveStr(B, 'Paymt # Prin. Int. Balance Principal So far So far ', Color); WriteLine(0,3,Size.X,1,B); END; {------------------------------------} { METHODS: TMortgageBottomInterior } {------------------------------------} CONSTRUCTOR TMortgageBottomInterior.Init(VAR Bounds : TRect; AHScrollBar, AVScrollBar : PScrollBar); BEGIN { Call ancestor's constructor: } TScroller.Init(Bounds,AHScrollBar,AVScrollBar); GrowMode := gfGrowHiX + gfGrowHiY; Options := Options OR ofFramed; END; PROCEDURE TMortgageBottomInterior.Draw; VAR Color : Byte; B : TDrawBuffer; YRun : Integer; STemp : String[20]; BEGIN Color := GetColor(1); FOR YRun := 0 TO Size.Y-1 DO BEGIN MoveChar(B,' ',Color,80); { Clear the buffer to spaces } Str(Delta.Y+YRun+1:4,STemp); MoveStr(B,STemp+':',Color); { At beginning of buffer B } { Here we convert payment data to strings for display: } Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayPrincipal:7:2,STemp); MoveStr(B[6],STemp,Color); { At beginning of buffer B } Str(Mortgage^.Payments^[Delta.Y+YRun+1].PayInterest:7:2,STemp); MoveStr(B[15],STemp,Color); { At position 15 of buffer B } Str(Mortgage^.Payments^[Delta.Y+YRun+1].Balance:10:2,STemp); MoveStr(B[24],STemp,Color); { At position 24 of buffer B } { There isn't an extra principal value for every payment, so } { display the value only if it is nonzero: } STemp := ''; IF Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal > 0 THEN Str(Mortgage^.Payments^[Delta.Y+YRun+1].ExtraPrincipal:10:2,STemp); MoveStr(B[37],STemp,Color); { At position 37 of buffer B } Str(Mortgage^.Payments^[Delta.Y+YRun+1].PrincipalSoFar:10:2,STemp); MoveStr(B[50],STemp,Color); { At position 50 of buffer B } Str(Mortgage^.Payments^[Delta.Y+YRun+1].InterestSoFar:10:2,STemp); MoveStr(B[64],STemp,Color); { At position 64 of buffer B } { Here we write the line to the window, taking into account the } { state of the X scroll bar: } WriteLine(0,YRun,Size.X,1,B[Delta.X]); END; END; {------------------------------} { METHODS: TMortgageView } {------------------------------} CONSTRUCTOR TMortgageView.Init(VAR Bounds : TRect; ATitle : TTitleStr; ANumber : Integer; InitMortgageData : MortgageDialogData); VAR TopInterior : PMortgageTopInterior; BottomInterior : PMortgageBottomInterior; HScrollBar,VScrollBar : PScrollBar; R,S : TRect; BEGIN TWindow.Init(Bounds,ATitle,ANumber); { Call ancestor's constructor } { Call the Mortgage object's constructor using dialog data: } WITH InitMortgageData DO Mortgage.Init(PrincipalData, InterestData / 100, PeriodsData, 12); { Here we set up a window with *two* interiors, one scrollable, one } { static. It's all in the way that you define the bounds, mostly: } GetClipRect(Bounds); { Get bounds for interior of view } Bounds.Grow(-1,-1); { Shrink those bounds by 1 for both X & Y } { Define a rectangle to embrace the upper of the two interiors: } R.Assign(Bounds.A.X,Bounds.A.Y,Bounds.B.X,Bounds.A.Y+4); TopInterior := New(PMortgageTopInterior,Init(R)); TopInterior^.Mortgage := @Mortgage; Insert(TopInterior); { Define a rectangle to embrace the lower of two interiors: } R.Assign(Bounds.A.X,Bounds.A.Y+5,Bounds.B.X,Bounds.B.Y); { Create scroll bars for both mouse & keyboard input: } VScrollBar := StandardScrollBar(sbVertical + sbHandleKeyboard); { We have to adjust vertical bar to fit bottom interior: } VScrollBar^.Origin.Y := R.A.Y; { Adjust top Y value } VScrollBar^.Size.Y := R.B.Y - R.A.Y; { Adjust size } { The horizontal scroll bar, on the other hand, is standard: } HScrollBar := StandardScrollBar(sbHorizontal + sbHandleKeyboard); { Create bottom interior object with scroll bars: } BottomInterior := New(PMortgageBottomInterior,Init(R,HScrollBar,VScrollBar)); { Make copy of pointer to mortgage object: } BottomInterior^.Mortgage := @Mortgage; { Set the limits for the scroll bars: } BottomInterior^.SetLimit(80,InitMortgageData.PeriodsData); { Insert the interior into the window: } Insert(BottomInterior); END; PROCEDURE TMortgageView.HandleEvent(Var Event : TEvent); BEGIN TWindow.HandleEvent(Event); IF Event.What = evCommand THEN BEGIN CASE Event.Command OF cmExtraPrin : ExtraPrincipal; cmPrintSummary : PrintSummary; ELSE Exit; END; { CASE } ClearEvent(Event); END ELSE IF Event.What = evBroadcast THEN CASE Event.Command OF cmCloseBC : Done END; { CASE } END; PROCEDURE TMortgageView.ExtraPrincipal; VAR Control : Word; ExtraPrincipalData : ExtraPrincipalDialogData; BEGIN { Execute the "extra principal" dialog box: } Control := Desktop^.ExecView(HouseCalc.ExtraDialog); IF Control <> cmCancel THEN { Update the active mortgage window: } BEGIN { Get data from the extra principal dialog: } HouseCalc.ExtraDialog^.GetData(ExtraPrincipalData); Mortgage.Payments^[ExtraPrincipalData.PaymentNumber].ExtraPrincipal := ExtraPrincipalData.ExtraDollars; Mortgage.Recalc; { Recalculate the amortization table... } Redraw; { ...and redraw the mortgage window } END; END; PROCEDURE TMortgageView.PrintSummary; BEGIN END; DESTRUCTOR TMortgageView.Done; BEGIN Mortgage.Done; { Dispose of the mortgage object's memory } TWindow.Done; { Call parent's destructor to dispose of window } END; BEGIN HouseCalc.Init; HouseCalc.Run; HouseCalc.Done; END. [THE FOLLOWING IS SOURCE FOR FINPUT.PAS] unit FInput; {$X+} { This unit implements a derivative of TInputLine that supports several data types dynamically. It also provides formatted input for all the numerical types, keystroke filtering and uppercase conversion, field justification, and range checking. When the field is initialized, many filtering and uppercase converions are implemented pertinent to the particular data type. The CheckRange and ErrorHandler methods should be overridden if the user wants to implement then. This is just an initial implementation and comments are welcome. You can contact me via Compuserve. (76066,3202) I am releasing this into the public domain and anyone can use or modify it for their own personal use. Copyright (c) 1990 by Allen Bauer (76066,3202) 1.1 - fixed input validation functions This is version 1.2 - fixed DataSize method to include reals. fixed Draw method to not format the data while the view is selected. } interface uses Objects, Drivers, Dialogs; type VKeys = set of char; PFInputLine = ^TFInputLine; TFInputLine = object(TInputLine) ValidKeys : VKeys; DataType,Decimals : byte; imMode : word; Validated, ValidSent : boolean; constructor Init(var Bounds: TRect; AMaxLen: integer; ChrSet: VKeys;DType, Dec: byte); constructor Load(var S: TStream); procedure Store(var S: TStream); procedure HandleEvent(var Event: TEvent); virtual; procedure GetData(var Rec); virtual; procedure SetData(var Rec); virtual; function DataSize: word; virtual; procedure Draw; virtual; function CheckRange: boolean; virtual; procedure ErrorHandler; virtual; end; const imLeftJustify = $0001; imRightJustify = $0002; imConvertUpper = $0004; DString = 0; DChar = 1; DReal = 2; DByte = 3; DShortInt = 4; DInteger = 5; DLongInt = 6; DWord = 7; DDate = 8; DTime = 9; DRealSet : VKeys = [#1..#31,'+','-','0'..'9','.','E','e']; DSignedSet : VKeys = [#1..#31,'+','-','0'..'9']; DUnSignedSet : VKeys = [#1..#31,'0'..'9']; DCharSet : VKeys = [#1..#31,' '..'~']; DUpperSet : VKeys = [#1..#31,' '..'`','{'..'~']; DAlphaSet : VKeys = [#1..#31,'A'..'Z','a'..'z']; DFileNameSet : VKeys = [#1..#31,'!','#'..')','-'..'.','0'..'9','@'..'Z','^'..'{','}'..'~']; DPathSet : VKeys = [#1..#31,'!','#'..')','-'..'.','0'..':','@'..'Z','^'..'{','}'..'~','\']; DFileMaskSet : VKeys = [#1..#31,'!','#'..'*','-'..'.','0'..':','?'..'Z','^'..'{','}'..'~','\']; DDateSet : VKeys = [#1..#31,'0'..'9','/']; DTimeSet : VKeys = [#1..#31,'0'..'9',':']; cmValidateYourself = 5000; cmValidatedOK = 5001; procedure RegisterFInputLine; const RFInputLine : TStreamRec = ( ObjType: 20000; VmtLink: Ofs(typeof(TFInputLine)^); Load: @TFInputLine.Load; Store: @TFinputLine.Store ); implementation uses Views, MsgBox, StrFmt, Dos; function CurrentDate : string; var Year,Month,Day,DOW : word; DateStr : string[10]; begin GetDate(Year,Month,Day,DOW); DateStr := SFLongint(Month,2)+'/' +SFLongInt(Day,2)+'/' +SFLongInt(Year mod 100,2); for DOW := 1 to length(DateStr) do if DateStr[DOW] = ' ' then DateStr[DOW] := '0'; CurrentDate := DateStr; end; function CurrentTime : string; var Hour,Minute,Second,Sec100 : word; TimeStr : string[10]; begin GetTime(Hour,Minute,Second,Sec100); TimeStr := SFLongInt(Hour,2)+':' +SFLongInt(Minute,2)+':' +SFLongInt(Second,2); for Sec100 := 1 to length(TimeStr) do if TimeStr[Sec100] = ' ' then TimeStr[Sec100] := '0'; CurrentTime := TimeStr; end; procedure RegisterFInputLine; begin RegisterType(RFInputLine); end; constructor TFInputLine.Init(var Bounds: TRect; AMaxLen: integer; ChrSet: VKeys; DType, Dec: byte); begin if (DType in [DDate,DTime]) and (AMaxLen < 8) then AMaxLen := 8; TInputLine.Init(Bounds,AMaxLen); ValidKeys:= ChrSet; DataType := DType; Decimals := Dec; Validated := true; ValidSent := false; case DataType of DReal,DByte,DLongInt, DShortInt,DWord : imMode := imRightJustify; DChar,DString, DDate,DTime : imMode := imLeftJustify; end; if ValidKeys = DUpperSet then imMode := imMode or imConvertUpper; EventMask := EventMask or evMessage; end; constructor TFInputLine.Load(var S: TStream); begin TInputLine.Load(S); S.Read(ValidKeys, sizeof(VKeys)); S.Read(DataType, sizeof(byte)); S.Read(Decimals, sizeof(byte)); S.Read(imMode, sizeof(word)); S.Read(Validated, sizeof(boolean)); S.Read(ValidSent, sizeof(boolean)); end; procedure TFInputLine.Store(var S: TStream); begin TInputLine.Store(S); S.Write(ValidKeys, sizeof(VKeys)); S.Write(DataType, sizeof(byte)); S.Write(Decimals, sizeof(byte)); S.Write(imMode, sizeof(word)); S.Write(Validated, sizeof(boolean)); S.Write(ValidSent, sizeof(boolean)); end; procedure TFInputLine.HandleEvent(var Event: TEvent); var NewEvent: TEvent; begin case Event.What of evKeyDown : begin if (imMode and imConvertUpper) <> 0 then Event.CharCode := upcase(Event.CharCode); if not(Event.CharCode in [#0..#31]) then begin Validated := false; ValidSent := false; end; if (Event.CharCode <> #0) and not(Event.CharCode in ValidKeys) then ClearEvent(Event); end; evBroadcast: begin if (Event.Command = cmReceivedFocus) and (Event.InfoPtr <> @Self) and ((Owner^.State and sfSelected) <> 0) and not(Validated) and not(ValidSent) then begin NewEvent.What := evBroadcast; NewEvent.InfoPtr := @Self; NewEvent.Command := cmValidateYourself; PutEvent(NewEvent); ValidSent := true; end; if (Event.Command = cmValidateYourself) and (Event.InfoPtr = @Self) then begin if not CheckRange then begin ErrorHandler; Select; end else begin NewEvent.What := evBroadCast; NewEvent.InfoPtr := @Self; NewEvent.Command := cmValidatedOK; PutEvent(NewEvent); Validated := true; end; ValidSent := false; ClearEvent(Event); end; end; end; TInputLine.HandleEvent(Event); end; procedure TFInputLine.GetData(var Rec); var Code : integer; begin case DataType of Dstring, DDate, DTime : TInputLine.GetData(Rec); DChar : char(Rec) := Data^[1]; DReal : val(Data^, real(Rec) , Code); DByte : val(Data^, byte(Rec) , Code); DShortInt : val(Data^, shortint(Rec) , Code); DInteger : val(Data^, integer(Rec) , Code); DLongInt : val(Data^, longint(Rec) , Code); DWord : val(Data^, word(Rec) , Code); end; end; procedure TFInputLine.SetData(var Rec); begin case DataType of DString, DDate, DTime : TInputLine.SetData(Rec); DChar : Data^ := char(Rec); DReal : Data^ := SFDReal(real(Rec),MaxLen,Decimals); DByte : Data^ := SFLongInt(byte(Rec),MaxLen); DShortInt : Data^ := SFLongInt(shortint(Rec),MaxLen); DInteger : Data^ := SFLongInt(integer(Rec),MaxLen); DLongInt : Data^ := SFLongInt(longint(Rec),MaxLen); DWord : Data^ := SFLongInt(word(Rec),MaxLen); end; SelectAll(true); end; function TFInputLine.DataSize: word; begin case DataType of DString, DDate, DTime : DataSize := TInputLine.DataSize; DChar : DataSize := sizeof(char); DReal : DataSize := sizeof(real); DByte : DataSize := sizeof(byte); DShortInt : DataSize := sizeof(shortint); DInteger : DataSize := sizeof(integer); DLongInt : DataSize := sizeof(longint); DWord : DataSize := sizeof(word); else DataSize := TInputLine.DataSize; end; end; procedure TFInputLine.Draw; var RD : real; Code : integer; begin if not((State and sfSelected) <> 0) then case DataType of DReal : begin if Data^ = '' then Data^ := SFDReal(0.0,MaxLen,Decimals) else begin val(Data^, RD, Code); Data^ := SFDReal(RD,MaxLen,Decimals); end; end; DByte, DShortInt, DInteger, DLongInt, DWord : if Data^ = '' then Data^ := SFLongInt(0,MaxLen); DDate : if Data^ = '' then Data^ := CurrentDate; DTime : if Data^ = '' then Data^ := CurrentTime; end; if State and (sfFocused+sfSelected) <> 0 then begin if (imMode and imRightJustify) <> 0 then while (length(Data^) > 0) and (Data^[1] = ' ') do delete(Data^,1,1); end else begin if ((imMode and imRightJustify) <> 0) and (Data^ <> '') then while (length(Data^) < MaxLen) do insert(' ',Data^,1); if (imMode and imLeftJustify) <> 0 then while (length(Data^) > 0) and (Data^[1] = ' ') do delete(Data^,1,1); end; TInputLine.Draw; end; function TFInputLine.CheckRange: boolean; var MH,DM,YS : longint; Code : integer; MHs,DMs,YSs : string[2]; Delim : char; Ok : boolean; begin Ok := true; case DataType of DDate, DTime : begin if DataType = DDate then Delim := '/' else Delim := ':'; if pos(Delim,Data^) > 0 then begin MHs := copy(Data^,1,pos(Delim,Data^)); DMs := copy(Data^,pos(Delim,Data^)+1,2); delete(Data^,pos(Delim,Data^),1); YSs := copy(Data^,pos(Delim,Data^)+1,2); if length(MHs) < 2 then MHs := '0' + MHs; if length(DMs) < 2 then DMs := '0' + DMs; if length(YSs) < 2 then YSs := '0' + YSs; Data^ := MHs + DMs + YSs; end; if (length(Data^) >= 6) and (pos(Delim,Data^) = 0) then begin val(copy(Data^,1,2), MH, Code); if Code <> 0 then MH := 0; val(copy(Data^,3,2), DM, Code); if Code <> 0 then DM := 0; val(copy(Data^,5,2), YS, Code); if Code <> 0 then YS := 0; if DataType = DDate then begin if (MH > 12) or (MH < 1) or (DM > 31) or (DM < 1) then Ok := false; end else begin if (MH > 23) or (MH < 0) or (DM > 59) or (DM < 0) or (YS > 59) or (YS < 0) then Ok := false; end; insert(Delim,Data^,5); insert(Delim,Data^,3); end else Ok := false; end; DByte : begin val(Data^, MH, Code); if (Code <> 0) or (MH > 255) or (MH < 0) then Ok := false; end; DShortint : begin val(Data^, MH, Code); if (Code <> 0) or (MH < -127) or (MH > 127) then Ok := false; end; DInteger : begin val(Data^, MH, Code); if (Code <> 0) or (MH < -32768) or (MH > 32767) then Ok := false; end; DWord : begin val(Data^, MH, Code); if (Code <> 0) or (MH < 0) or (MH > 65535) then Ok := false; end; end; CheckRange := Ok; end; procedure TFInputLine.ErrorHandler; var MsgString : string[80]; Params : array[0..1] of longint; Event: TEvent; begin fillchar(Params,sizeof(params),#0); MsgString := ''; case DataType of DDate : MsgString := ' Invalid Date Format! Enter Date as MM/DD/YY '; DTime : MsgString := ' Invalid Time Format! Enter Time as HH:MM:SS '; DByte, DShortInt, DInteger, DWord : begin MsgString := ' Number must be between %d and %d '; case DataType of DByte : Params[1] := 255; DShortInt : begin Params[0] := -128; Params[1] := 127; end; DInteger : begin Params[0] := -32768; Params[1] := 32768; end; DWord : Params[1] := 65535; end; end; end; MessageBox(MsgString, @Params, mfError + mfOkButton); end; end.