Have you ever found yourself writing the same code over and over to perform some common task within event handlers? Yes! It's time for you to learn about programs within a program. Let's call those mini programs subroutines.
Intro to subroutinesSubroutines are an important part of any programming language, and Delphi is no exception. In Delphi, there are generally two types of subroutines: a function and a procedure. The usual difference between a function and a procedure is that a function can return a value, and a procedure generally will not do so. A function is normally called as a part of an expression.
Take a look at the following examples:
Once subroutines have been defined, we can call them one or more times:
procedure SayHello(const sWhat:string) ; begin ShowMessage('Hello ' + sWhat) ; end; function YearsOld(const BirthYear:integer): integer; var Year, Month, Day : Word; begin DecodeDate(Date, Year, Month, Day) ; Result := Year - BirthYear; end;
procedure TForm1.Button1Click(Sender: TObject) ; begin SayHello('Delphi User') ; end; procedure TForm1.Button2Click(Sender: TObject) ; begin SayHello('Zarko Gajic') ; ShowMessage('You are ' + IntToStr(YearsOld(1973)) + ' years old!') ; end;
Functions and ProceduresAs we can see, both functions and procedures act like mini programs. In particular, they can have their own type, constants and variable declarations inside them.
Take a closer look at a (miscellaneous) SomeCalc function:
Every procedure or function begins with a header that identifies the procedure or function and lists the parameters the routine uses, if any. The parameters are listed within parentheses. Each parameter has an identifying name and usually has a type. A semicolon separates parameters in a parameter list from one another.
function SomeCalc (const sStr: string; const iYear, iMonth: integer; var iDay:integer): boolean; begin ... end;
sStr, iYear and iMonth are called constant parameters. Constant parameters cannot be changed by the function (or procedure). The iDay is passed as a var parameter, and we can make changes to it, inside the subroutine.
Functions, since they return values, must have a return type declared at the end of the header. The return value of a function is given by the (final) assignment to its name. Since every function implicitly has a local variable Result of the same type as the functions return value, assigning to Result has the same effect as assigning to the name of the function.
Positioning and Calling SubroutinesSubroutines are always placed inside the implementation section of the unit. Such subroutines can be called (used) by any event handler or subroutine in the same unit that is defined after it.
Note: the uses clause of a unit tells you which units it can call. If we want a specific subroutine in a Unit1 to be usable by the event handlers or subroutines in another unit (say Unit2), we have to:
- Add Unit1 to the uses clause of Unit2
- Place a copy of the header of the subroutine in the interface section of the Unit1.
When we call a function (or a procedure) inside its own unit, we use its name with whatever parameters are needed. On other hand, if we call a global subroutine (defined in some other unit, e.g. MyUnit) we use the name of the unit followed by a period.
Note: functions or procedures can have their own subroutines embedded inside them. An embedded subroutine is local to the container subroutine and cannot be used by other parts of the program. Something like:
... //SayHello procedure is defined inside this unit SayHello('Delphi User') ; //YearsOld function is defined inside MyUnit unit Dummy := MyUnit.YearsOld(1973) ; ...
procedure TForm1.Button1Click(Sender: TObject) ; function IsSmall(const sStr:string):boolean; begin //IsSmall returns True if sStr is in lowercase, False otherwise Result:=LowerCase(sStr)=sStr; end; begin //IsSmall can only be uses inside Button1 OnClick event if IsSmall(Edit1.Text) then ShowMessage('All small caps in Edit1.Text') else ShowMessage('Not all small caps in Edit1.Text') ; end;